minio fix
Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Web App CI / test (push) Waiting to run
Web App CI / build (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions
Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Web App CI / test (push) Waiting to run
Web App CI / build (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions
This commit is contained in:
parent
9ed1a00a38
commit
118f0e2ede
4 changed files with 119 additions and 41 deletions
|
|
@ -4,18 +4,30 @@ import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||||
/// Extension для конвертации GameCard в GameCardDto для admin API
|
/// Extension для конвертации GameCard в GameCardDto для admin API
|
||||||
extension GameCardAdminExtension on GameCard {
|
extension GameCardAdminExtension on GameCard {
|
||||||
/// Конвертация GameCard в GameCardDto с учетом packId и конвертации изображений
|
/// Конвертация GameCard в GameCardDto с учетом packId и конвертации изображений
|
||||||
|
///
|
||||||
|
/// DEPRECATED: Use toGameCardDtoWithPresignedUrls instead
|
||||||
|
/// This method is kept for backwards compatibility
|
||||||
|
///
|
||||||
|
/// image/imageBack always contains UUID (objectId in MinIO)
|
||||||
|
/// imageUrl/imageBackUrl will contain URLs from convertImageToUrl
|
||||||
GameCardDto toGameCardDtoWithPack(
|
GameCardDto toGameCardDtoWithPack(
|
||||||
String? packId,
|
String? packId,
|
||||||
String? Function(String?, String?, String) convertImageToUrl,
|
String? Function(String?, String?, String) convertImageToUrl,
|
||||||
String? Function(String?, String?, String) convertImageBackToUrl,
|
String? Function(String?, String?, String) convertImageBackToUrl,
|
||||||
) {
|
) {
|
||||||
|
// Generate URLs from UUIDs
|
||||||
|
final imageUrl = convertImageToUrl(image, packId, id);
|
||||||
|
final imageBackUrl = convertImageBackToUrl(imageBack, packId, id);
|
||||||
|
|
||||||
return GameCardDto(
|
return GameCardDto(
|
||||||
id: id,
|
id: id,
|
||||||
original: original,
|
original: original,
|
||||||
translation: translation,
|
translation: translation,
|
||||||
mnemo: mnemo ?? '',
|
mnemo: mnemo ?? '',
|
||||||
image: convertImageToUrl(image, packId, id),
|
image: image, // Keep UUID in image field
|
||||||
imageBack: convertImageBackToUrl(imageBack, packId, id),
|
imageUrl: imageUrl, // Set URL in imageUrl
|
||||||
|
imageBack: imageBack, // Keep UUID in imageBack field
|
||||||
|
imageBackUrl: imageBackUrl, // Set URL in imageBackUrl
|
||||||
back: back,
|
back: back,
|
||||||
transcription: transcription ?? '',
|
transcription: transcription ?? '',
|
||||||
transcriptionMnemo: transcriptionMnemo,
|
transcriptionMnemo: transcriptionMnemo,
|
||||||
|
|
@ -23,19 +35,27 @@ extension GameCardAdminExtension on GameCard {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Асинхронная конвертация GameCard в GameCardDto с генерацией presigned URLs
|
/// Асинхронная конвертация GameCard в GameCardDto с генерацией presigned URLs
|
||||||
|
///
|
||||||
|
/// image/imageBack always contains UUID (objectId in MinIO)
|
||||||
|
/// imageUrl/imageBackUrl will contain presigned URLs
|
||||||
Future<GameCardDto> toGameCardDtoWithPresignedUrls(
|
Future<GameCardDto> toGameCardDtoWithPresignedUrls(
|
||||||
String? packId,
|
String? packId,
|
||||||
Future<String?> Function(String?, String?, String) convertImageToUrl,
|
Future<String?> Function(String?, String?, String) convertImageToUrl,
|
||||||
Future<String?> Function(String?, String?, String) convertImageBackToUrl,
|
Future<String?> Function(String?, String?, String) convertImageBackToUrl,
|
||||||
) async {
|
) async {
|
||||||
|
// Generate presigned URLs
|
||||||
|
final imageUrl = await convertImageToUrl(image, packId, id);
|
||||||
|
final imageBackUrl = await convertImageBackToUrl(imageBack, packId, id);
|
||||||
|
|
||||||
return GameCardDto(
|
return GameCardDto(
|
||||||
id: id,
|
id: id,
|
||||||
original: original,
|
original: original,
|
||||||
translation: translation,
|
translation: translation,
|
||||||
mnemo: mnemo ?? '',
|
mnemo: mnemo ?? '',
|
||||||
image: await convertImageToUrl(image, packId, id),
|
image: image, // Keep UUID in image field
|
||||||
imageBack: await convertImageBackToUrl(imageBack, packId, id),
|
imageUrl: imageUrl, // Set presigned URL in imageUrl
|
||||||
back: back,
|
imageBack: imageBack, // Keep UUID in imageBack field
|
||||||
|
imageBackUrl: imageBackUrl, // Set presigned URL in imageBackUrl
|
||||||
transcription: transcription ?? '',
|
transcription: transcription ?? '',
|
||||||
transcriptionMnemo: transcriptionMnemo,
|
transcriptionMnemo: transcriptionMnemo,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -278,8 +278,36 @@ class PacksApiV2 {
|
||||||
|
|
||||||
final packDto = await _packManager.getPackDto(packId, user);
|
final packDto = await _packManager.getPackDto(packId, user);
|
||||||
|
|
||||||
|
// Generate presigned URLs for all cards in the pack
|
||||||
|
// image/imageBack always contains UUID (objectId in MinIO)
|
||||||
|
// imageUrl/imageBackUrl will contain presigned URLs
|
||||||
|
// Presigned URLs now have 7 days expiration to avoid 403 errors on cached data
|
||||||
|
final cardsWithPresignedUrls = await Future.wait(
|
||||||
|
packDto.cards.map((card) async {
|
||||||
|
// Generate presigned URLs for images if they are object IDs (UUIDs)
|
||||||
|
final imageUrl = await _getPresignedUrlIfUuid(
|
||||||
|
card.image,
|
||||||
|
MinioConfig.cardImagesBucket,
|
||||||
|
);
|
||||||
|
final imageBackUrl = await _getPresignedUrlIfUuid(
|
||||||
|
card.imageBack,
|
||||||
|
MinioConfig.cardImagesBucket,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return card with presigned URLs in imageUrl/imageBackUrl
|
||||||
|
// Keep original UUID in image/imageBack
|
||||||
|
return card.copyWith(
|
||||||
|
imageUrl: imageUrl, // Set presigned URL in imageUrl
|
||||||
|
imageBackUrl: imageBackUrl, // Set presigned URL in imageBackUrl
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create updated pack DTO with cards that have presigned URLs
|
||||||
|
final updatedPackDto = packDto.copyWith(cards: cardsWithPresignedUrls);
|
||||||
|
|
||||||
// Include purchase status in response if user is authenticated
|
// Include purchase status in response if user is authenticated
|
||||||
final packJson = packDto.toJson();
|
final packJson = updatedPackDto.toJson();
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
final isAvailable = await _productAvailabilityManager.isPackAvailable(
|
final isAvailable = await _productAvailabilityManager.isPackAvailable(
|
||||||
|
|
@ -321,7 +349,35 @@ class PacksApiV2 {
|
||||||
|
|
||||||
// Return pack preview as buy page
|
// Return pack preview as buy page
|
||||||
final packDto = await _packManager.getPackDto(packId, user);
|
final packDto = await _packManager.getPackDto(packId, user);
|
||||||
final packJson = packDto.toJson();
|
|
||||||
|
// Generate presigned URLs for all cards in the pack
|
||||||
|
// image/imageBack always contains UUID (objectId in MinIO)
|
||||||
|
// imageUrl/imageBackUrl will contain presigned URLs
|
||||||
|
// Presigned URLs now have 7 days expiration to avoid 403 errors on cached data
|
||||||
|
final cardsWithPresignedUrls = await Future.wait(
|
||||||
|
packDto.cards.map((card) async {
|
||||||
|
// Generate presigned URLs for images if they are object IDs (UUIDs)
|
||||||
|
final imageUrl = await _getPresignedUrlIfUuid(
|
||||||
|
card.image,
|
||||||
|
MinioConfig.cardImagesBucket,
|
||||||
|
);
|
||||||
|
final imageBackUrl = await _getPresignedUrlIfUuid(
|
||||||
|
card.imageBack,
|
||||||
|
MinioConfig.cardImagesBucket,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Return card with presigned URLs in imageUrl/imageBackUrl
|
||||||
|
// Keep original UUID in image/imageBack
|
||||||
|
return card.copyWith(
|
||||||
|
imageUrl: imageUrl, // Set presigned URL in imageUrl
|
||||||
|
imageBackUrl: imageBackUrl, // Set presigned URL in imageBackUrl
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create updated pack DTO with cards that have presigned URLs
|
||||||
|
final updatedPackDto = packDto.copyWith(cards: cardsWithPresignedUrls);
|
||||||
|
final packJson = updatedPackDto.toJson();
|
||||||
|
|
||||||
// Include purchase status in response if user is authenticated
|
// Include purchase status in response if user is authenticated
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,13 @@ import '../../utils/card_image_utils.dart';
|
||||||
|
|
||||||
/// Widget that handles expired presigned URLs by automatically refreshing them
|
/// Widget that handles expired presigned URLs by automatically refreshing them
|
||||||
///
|
///
|
||||||
|
/// image/imageBack always contains UUID (objectId in MinIO)
|
||||||
|
/// imageUrl/imageBackUrl always contains presigned URL
|
||||||
|
///
|
||||||
/// When a presigned URL expires (403 error), this widget:
|
/// When a presigned URL expires (403 error), this widget:
|
||||||
/// 1. Detects the error
|
/// 1. Detects the error
|
||||||
/// 2. Checks if card.image contains an objectId (UUID)
|
/// 2. Uses card.image (UUID) to request a new presigned URL from the API
|
||||||
/// 3. Requests a new presigned URL from the API
|
/// 3. Updates the image URL and retries loading
|
||||||
/// 4. Updates the image URL and retries loading
|
|
||||||
class ExpiringCardImage extends StatefulWidget {
|
class ExpiringCardImage extends StatefulWidget {
|
||||||
const ExpiringCardImage({
|
const ExpiringCardImage({
|
||||||
super.key,
|
super.key,
|
||||||
|
|
@ -81,10 +83,26 @@ class _ExpiringCardImageState extends State<ExpiringCardImage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
String? _getObjectId() {
|
String? _getObjectId() {
|
||||||
if (widget.isBackImage) {
|
// image/imageBack always contains UUID (objectId in MinIO)
|
||||||
return widget.card.imageBack;
|
// imageUrl/imageBackUrl always contains presigned URL
|
||||||
|
final imageValue = widget.isBackImage
|
||||||
|
? widget.card.imageBack
|
||||||
|
: widget.card.image;
|
||||||
|
|
||||||
|
if (imageValue == null || imageValue.isEmpty) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
return widget.card.image;
|
|
||||||
|
// Verify it's a valid UUID
|
||||||
|
if (!_isUuid(imageValue)) {
|
||||||
|
log(
|
||||||
|
'Warning: image field does not contain valid UUID: $imageValue',
|
||||||
|
name: 'ExpiringCardImage',
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return imageValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isUuid(String? value) {
|
bool _isUuid(String? value) {
|
||||||
|
|
|
||||||
|
|
@ -5,27 +5,19 @@ import '../domain/config/api_config_v2.dart';
|
||||||
class CardImageUtils {
|
class CardImageUtils {
|
||||||
/// Get the display URL for a card's image
|
/// Get the display URL for a card's image
|
||||||
///
|
///
|
||||||
/// Priority:
|
/// image always contains UUID (objectId in MinIO)
|
||||||
/// 1. imageUrl (presigned URL from backend)
|
/// imageUrl always contains presigned URL
|
||||||
/// 2. image if it's a full URL (starts with http/https)
|
|
||||||
/// 3. image if it's a filename - convert to API endpoint URL
|
|
||||||
///
|
///
|
||||||
|
/// Returns imageUrl if available, otherwise falls back to API endpoint
|
||||||
/// Returns null if no image is available
|
/// Returns null if no image is available
|
||||||
static String? getCardImageUrl(GameCardDto card, String packId) {
|
static String? getCardImageUrl(GameCardDto card, String packId) {
|
||||||
// First priority: use presigned URL if available
|
// Use presigned URL if available (always preferred)
|
||||||
if (card.imageUrl != null && card.imageUrl!.isNotEmpty) {
|
if (card.imageUrl != null && card.imageUrl!.isNotEmpty) {
|
||||||
return card.imageUrl;
|
return card.imageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second priority: check if image is a full URL
|
// Fallback: use API endpoint if image (UUID) is available
|
||||||
final image = card.image;
|
if (card.image != null && card.image!.isNotEmpty) {
|
||||||
if (image != null && image.isNotEmpty) {
|
|
||||||
// If it's already a full URL, use it
|
|
||||||
if (image.startsWith('http://') || image.startsWith('https://')) {
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's a filename, convert to API endpoint URL
|
|
||||||
return ApiConfigV2.getCardImageUrl(packId, card.id);
|
return ApiConfigV2.getCardImageUrl(packId, card.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -34,27 +26,19 @@ class CardImageUtils {
|
||||||
|
|
||||||
/// Get the display URL for a card's back image
|
/// Get the display URL for a card's back image
|
||||||
///
|
///
|
||||||
/// Priority:
|
/// imageBack always contains UUID (objectId in MinIO)
|
||||||
/// 1. imageBackUrl (presigned URL from backend)
|
/// imageBackUrl always contains presigned URL
|
||||||
/// 2. imageBack if it's a full URL (starts with http/https)
|
|
||||||
/// 3. imageBack if it's a filename - convert to API endpoint URL
|
|
||||||
///
|
///
|
||||||
|
/// Returns imageBackUrl if available, otherwise falls back to API endpoint
|
||||||
/// Returns null if no back image is available
|
/// Returns null if no back image is available
|
||||||
static String? getCardImageBackUrl(GameCardDto card, String packId) {
|
static String? getCardImageBackUrl(GameCardDto card, String packId) {
|
||||||
// First priority: use presigned URL if available
|
// Use presigned URL if available (always preferred)
|
||||||
if (card.imageBackUrl != null && card.imageBackUrl!.isNotEmpty) {
|
if (card.imageBackUrl != null && card.imageBackUrl!.isNotEmpty) {
|
||||||
return card.imageBackUrl;
|
return card.imageBackUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second priority: check if imageBack is a full URL
|
// Fallback: use API endpoint if imageBack (UUID) is available
|
||||||
final imageBack = card.imageBack;
|
if (card.imageBack != null && card.imageBack!.isNotEmpty) {
|
||||||
if (imageBack != null && imageBack.isNotEmpty) {
|
|
||||||
// If it's already a full URL, use it
|
|
||||||
if (imageBack.startsWith('http://') || imageBack.startsWith('https://')) {
|
|
||||||
return imageBack;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If it's a filename, convert to API endpoint URL
|
|
||||||
return ApiConfigV2.getCardImageBackUrl(packId, card.id);
|
return ApiConfigV2.getCardImageBackUrl(packId, card.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue