diff --git a/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart b/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart index 8585da4..942b91b 100644 --- a/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart +++ b/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart @@ -27,12 +27,25 @@ class AdminCardsApiV2 { AdminCardsApiV2(this._db, this._minioService); /// Validates if a string is a valid UUID (object ID in MinIO) + /// Supports UUID with optional file extension (e.g., .mp3, .wav) bool _isValidUuid(String value) { - final uuidRegex = RegExp( - r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', + final trimmed = value.trim(); + // UUID with optional extension: UUID followed by optional .extension + final uuidWithExtensionRegex = RegExp( + r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}(\.[a-z0-9]+)?$', caseSensitive: false, ); - return uuidRegex.hasMatch(value.trim()); + return uuidWithExtensionRegex.hasMatch(trimmed); + } + + /// Extracts UUID from a string that may contain extension + /// Returns the full string (UUID + extension) if valid, or null + String? _extractObjectId(String value) { + final trimmed = value.trim(); + if (_isValidUuid(trimmed)) { + return trimmed; + } + return null; } Future _normalizeCardImageForDb({ @@ -876,7 +889,7 @@ class AdminCardsApiV2 { final voices = await _db.packDao.getCardVoices(cardId); final voicesDto = await Future.wait( voices.map((voice) async { - // Generate presigned URL if voiceUrl is a UUID (object ID) + // Generate presigned URL if voiceUrl is a UUID (object ID) with optional extension if (_isValidUuid(voice.voiceUrl)) { final presignedUrl = await _minioService.getPresignedUrl( bucket: MinioConfig.voiceAudioBucket, @@ -945,15 +958,16 @@ class AdminCardsApiV2 { ); } - // Validate that voiceUrl is a UUID (object ID from MinIO) - if (!_isValidUuid(requestDto.voiceUrl)) { + // Validate that voiceUrl is a UUID (object ID from MinIO) with optional extension + final objectId = _extractObjectId(requestDto.voiceUrl); + if (objectId == null) { return _json( ErrorResponse( error: 'Validation error', message: 'Invalid object ID format', field: 'voiceUrl', details: - 'The provided voice URL must be a valid UUID (object ID from MinIO).', + 'The provided voice URL must be a valid UUID (object ID from MinIO), optionally with file extension (e.g., .mp3).', ).toJson(), statusCode: 400, ); @@ -973,9 +987,6 @@ class AdminCardsApiV2 { ); } - // Use the object ID directly - final objectId = requestDto.voiceUrl; - // Create voice record with object ID print( '🔍 addCardVoice: Creating voice record with objectId $objectId for card $cardId',