id
Some checks are pending
Backend CI / test (push) Waiting to run
Backend 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:
Dmitry 2026-01-24 20:35:46 +03:00
parent 9521405ed9
commit 71e4926395

View file

@ -27,12 +27,25 @@ class AdminCardsApiV2 {
AdminCardsApiV2(this._db, this._minioService); AdminCardsApiV2(this._db, this._minioService);
/// Validates if a string is a valid UUID (object ID in MinIO) /// 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) { bool _isValidUuid(String value) {
final uuidRegex = RegExp( final trimmed = value.trim();
r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', // 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, 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<String> _normalizeCardImageForDb({ Future<String> _normalizeCardImageForDb({
@ -876,7 +889,7 @@ class AdminCardsApiV2 {
final voices = await _db.packDao.getCardVoices(cardId); final voices = await _db.packDao.getCardVoices(cardId);
final voicesDto = await Future.wait( final voicesDto = await Future.wait(
voices.map((voice) async { 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)) { if (_isValidUuid(voice.voiceUrl)) {
final presignedUrl = await _minioService.getPresignedUrl( final presignedUrl = await _minioService.getPresignedUrl(
bucket: MinioConfig.voiceAudioBucket, bucket: MinioConfig.voiceAudioBucket,
@ -945,15 +958,16 @@ class AdminCardsApiV2 {
); );
} }
// Validate that voiceUrl is a UUID (object ID from MinIO) // Validate that voiceUrl is a UUID (object ID from MinIO) with optional extension
if (!_isValidUuid(requestDto.voiceUrl)) { final objectId = _extractObjectId(requestDto.voiceUrl);
if (objectId == null) {
return _json( return _json(
ErrorResponse( ErrorResponse(
error: 'Validation error', error: 'Validation error',
message: 'Invalid object ID format', message: 'Invalid object ID format',
field: 'voiceUrl', field: 'voiceUrl',
details: 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(), ).toJson(),
statusCode: 400, statusCode: 400,
); );
@ -973,9 +987,6 @@ class AdminCardsApiV2 {
); );
} }
// Use the object ID directly
final objectId = requestDto.voiceUrl;
// Create voice record with object ID // Create voice record with object ID
print( print(
'🔍 addCardVoice: Creating voice record with objectId $objectId for card $cardId', '🔍 addCardVoice: Creating voice record with objectId $objectId for card $cardId',