diff --git a/mnemo_cards_backend/lib/database/daos/pack_dao.dart b/mnemo_cards_backend/lib/database/daos/pack_dao.dart index 47c5028..912178f 100644 --- a/mnemo_cards_backend/lib/database/daos/pack_dao.dart +++ b/mnemo_cards_backend/lib/database/daos/pack_dao.dart @@ -283,15 +283,22 @@ class PackDao extends DatabaseAccessor with _$PackDaoMixin { Future> getCardVoices(String cardId) async { print('🔍 DAO getCardVoices: Querying voices for card $cardId'); - // First, check if there are any voices in CardVoices table for this card + // First, check if there are any voices in VoiceModels table with direct cardId link + final directVoicesQuery = select(voiceModels) + ..where((v) => v.cardId.equals(cardId)); + final directVoices = await directVoicesQuery.get(); + print('🔍 DAO getCardVoices: Found ${directVoices.length} voices in VoiceModels with direct cardId link'); + + // Check if there are any voices in CardVoices table for this card final countExpr = cardVoices.cardId.count(); final countQuery = selectOnly(cardVoices) ..addColumns([countExpr]) ..where(cardVoices.cardId.equals(cardId)); final countResult = await countQuery.getSingle(); final count = countResult.read(countExpr) ?? 0; - print('🔍 DAO getCardVoices: Found $count entries in CardVoices table for card $cardId'); + print('🔍 DAO getCardVoices: Found $count entries in CardVoices junction table for card $cardId'); + // Query through junction table final query = select(voiceModels).join([ innerJoin( cardVoices, @@ -301,7 +308,13 @@ class PackDao extends DatabaseAccessor with _$PackDaoMixin { ]); final result = await query.map((row) => row.readTable(voiceModels)).get(); - print('🔍 DAO getCardVoices: Query returned ${result.length} voices'); + print('🔍 DAO getCardVoices: Query through junction table returned ${result.length} voices'); + + // If junction table query returns nothing but direct link exists, return direct voices + if (result.isEmpty && directVoices.isNotEmpty) { + print('⚠️ DAO getCardVoices: Junction table is empty but direct link exists! Using direct voices.'); + return directVoices; + } return result; } @@ -310,16 +323,44 @@ class PackDao extends DatabaseAccessor with _$PackDaoMixin { Future addVoiceToCard(String cardId, String voiceId) async { print('🔍 DAO addVoiceToCard: Linking voice $voiceId to card $cardId'); try { - await into(cardVoices).insert( + // Check if voice exists + final voice = await getVoiceById(voiceId); + if (voice == null) { + print('❌ DAO addVoiceToCard: Voice $voiceId does not exist!'); + throw Exception('Voice $voiceId does not exist'); + } + print('✅ DAO addVoiceToCard: Voice $voiceId exists with cardId=${voice.cardId}'); + + // Check if card exists + final card = await getCardById(cardId); + if (card == null) { + print('❌ DAO addVoiceToCard: Card $cardId does not exist!'); + throw Exception('Card $cardId does not exist'); + } + print('✅ DAO addVoiceToCard: Card $cardId exists'); + + // Insert into junction table + final result = await into(cardVoices).insert( CardVoicesCompanion.insert( cardId: cardId, voiceId: voiceId, ), mode: InsertMode.insertOrIgnore, ); - print('✅ DAO addVoiceToCard: Successfully linked voice $voiceId to card $cardId'); - } catch (e) { + print('✅ DAO addVoiceToCard: Insert result: $result'); + + // Verify the insert + final verifyQuery = select(cardVoices) + ..where((cv) => cv.cardId.equals(cardId) & cv.voiceId.equals(voiceId)); + final verifyResult = await verifyQuery.getSingleOrNull(); + if (verifyResult == null) { + print('❌ DAO addVoiceToCard: Verification failed! Link was not created.'); + } else { + print('✅ DAO addVoiceToCard: Successfully linked and verified voice $voiceId to card $cardId'); + } + } catch (e, stackTrace) { print('❌ DAO addVoiceToCard: Error linking voice $voiceId to card $cardId: $e'); + print('Stack trace: $stackTrace'); rethrow; } } diff --git a/mnemo_cards_web_v2/lib/utils/card_image_utils.dart b/mnemo_cards_web_v2/lib/utils/card_image_utils.dart new file mode 100644 index 0000000..750873d --- /dev/null +++ b/mnemo_cards_web_v2/lib/utils/card_image_utils.dart @@ -0,0 +1,69 @@ +import 'package:mnemo_cards_common/mnemo_cards_common.dart'; +import '../domain/config/api_config_v2.dart'; + +/// Utility functions for working with card images +class CardImageUtils { + /// Get the display URL for a card's image + /// + /// Priority: + /// 1. imageUrl (presigned URL from backend) + /// 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 null if no image is available + static String? getCardImageUrl( + GameCardDto card, + String packId, + ) { + // First priority: use presigned URL if available + if (card.imageUrl != null && card.imageUrl!.isNotEmpty) { + return card.imageUrl; + } + + // Second priority: check if image is a full URL + final image = card.image; + 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 null; + } + + /// Get the display URL for a card's back image + /// + /// Priority: + /// 1. imageBackUrl (presigned URL from backend) + /// 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 null if no back image is available + static String? getCardImageBackUrl( + GameCardDto card, + String packId, + ) { + // First priority: use presigned URL if available + if (card.imageBackUrl != null && card.imageBackUrl!.isNotEmpty) { + return card.imageBackUrl; + } + + // Second priority: check if imageBack is a full URL + final imageBack = card.imageBack; + 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 null; + } +}