fixes
This commit is contained in:
parent
7aa12d0c59
commit
ce9614f676
9 changed files with 21 additions and 250 deletions
|
|
@ -1,7 +1,5 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:drift/drift.dart' as d;
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:mnemo_cards_backend/api/authorize/acl_types.dart';
|
||||
import 'package:mnemo_cards_backend/api/authorize/access_service.dart';
|
||||
|
|
@ -10,7 +8,7 @@ import 'package:mnemo_cards_backend/database/database.dart' hide VoiceModel;
|
|||
import 'package:mnemo_cards_backend/database/database.dart' as drift show VoiceModel;
|
||||
import 'package:mnemo_cards_backend/packs/card_pack_drift_extension.dart';
|
||||
import 'package:mnemo_cards_backend/packs/card_image_storage.dart';
|
||||
import 'package:mnemo_cards_backend/packs/pack_manager.dart' show PackManager, PackManagerUtils;
|
||||
import 'package:mnemo_cards_backend/packs/pack_manager.dart' show PackManager;
|
||||
import 'package:mnemo_cards_backend/packs/voice_storage.dart';
|
||||
import 'package:mnemo_cards_backend/storage/minio_config.dart';
|
||||
import 'package:mnemo_cards_backend/storage/minio_service.dart';
|
||||
|
|
@ -511,25 +509,8 @@ class PacksApiV2 {
|
|||
return Response.found(imageValue);
|
||||
}
|
||||
|
||||
// Legacy: try to resolve from local file system
|
||||
final resolved = await CardImageStorage.tryResolveLocalFile(
|
||||
cardId: cardId,
|
||||
imageValue: imageValue,
|
||||
isBack: false,
|
||||
);
|
||||
|
||||
if (resolved == null) {
|
||||
print('Could not resolve image file for card $cardId');
|
||||
// Not a UUID and not a remote URL - image not found
|
||||
return _notFound('Image not found');
|
||||
}
|
||||
|
||||
return Response.ok(
|
||||
resolved.bytes,
|
||||
headers: {
|
||||
'Content-Type': resolved.contentType,
|
||||
'Cache-Control': 'public, max-age=86400', // Cache for 1 day
|
||||
},
|
||||
);
|
||||
} catch (e, s) {
|
||||
print('Error fetching card image: $e\n$s');
|
||||
return _internalServerError('Error loading image');
|
||||
|
|
@ -601,57 +582,8 @@ class PacksApiV2 {
|
|||
return Response.found(imageValue);
|
||||
}
|
||||
|
||||
// Legacy: try to resolve from local file system
|
||||
final resolved = await CardImageStorage.tryResolveLocalFile(
|
||||
cardId: cardId,
|
||||
imageValue: imageValue,
|
||||
isBack: true,
|
||||
);
|
||||
|
||||
if (resolved != null) {
|
||||
if (resolved.fileName != card.imageBack) {
|
||||
await _db.packDao.updateCard(
|
||||
card.copyWith(imageBack: d.Value(resolved.fileName)),
|
||||
);
|
||||
}
|
||||
|
||||
return Response.ok(
|
||||
resolved.bytes,
|
||||
headers: {
|
||||
'Content-Type': resolved.contentType,
|
||||
'Cache-Control': 'public, max-age=86400', // Cache for 1 day
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
final stored = await CardImageStorage.persistFromBase64(
|
||||
cardId: cardId,
|
||||
imageValue: imageValue,
|
||||
preferredFileName: null,
|
||||
isBack: true,
|
||||
);
|
||||
|
||||
if (stored == null) {
|
||||
// Not a UUID and not a remote URL - image not found
|
||||
return _notFound('Back image not found');
|
||||
}
|
||||
|
||||
await _db.packDao.updateCard(
|
||||
card.copyWith(imageBack: d.Value(stored.fileName)),
|
||||
);
|
||||
|
||||
final file = File('${PackManagerUtils.assetsDirectory.path}/cards/${stored.fileName}');
|
||||
if (!file.existsSync()) {
|
||||
return _notFound('Back image not found');
|
||||
}
|
||||
final bytes = await file.readAsBytes();
|
||||
|
||||
return Response.ok(
|
||||
bytes,
|
||||
headers: {
|
||||
'Content-Type': stored.contentType,
|
||||
'Cache-Control': 'public, max-age=86400', // Cache for 1 day
|
||||
},
|
||||
);
|
||||
} catch (e, s) {
|
||||
print('Error fetching card back image: $e\n$s');
|
||||
return _internalServerError('Error loading back image');
|
||||
|
|
@ -826,42 +758,7 @@ class PacksApiV2 {
|
|||
return _notFound('Voice file not found in storage');
|
||||
}
|
||||
|
||||
// Legacy: local voice file
|
||||
final resolved = await VoiceStorage.tryResolveLocalFile(
|
||||
voiceValue: voiceValue,
|
||||
);
|
||||
if (resolved != null) {
|
||||
return Response.ok(
|
||||
resolved.bytes,
|
||||
headers: {
|
||||
'Content-Type': resolved.contentType,
|
||||
'Cache-Control': 'public, max-age=86400',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Legacy base64 stored in DB: migrate to file and serve bytes.
|
||||
final stored = await VoiceStorage.persistFromBase64(
|
||||
voiceId: voice.id,
|
||||
voiceValue: voiceValue,
|
||||
);
|
||||
|
||||
if (stored != null) {
|
||||
await _db.packDao.updateVoiceUrl(voice.id, stored.fileName);
|
||||
final migratedResolved = await VoiceStorage.tryResolveLocalFile(
|
||||
voiceValue: stored.fileName,
|
||||
);
|
||||
if (migratedResolved != null) {
|
||||
return Response.ok(
|
||||
migratedResolved.bytes,
|
||||
headers: {
|
||||
'Content-Type': migratedResolved.contentType,
|
||||
'Cache-Control': 'public, max-age=86400',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Not a UUID and not a remote URL - voice file not found
|
||||
return _notFound('Voice file not found');
|
||||
} catch (e, s) {
|
||||
print('Error fetching voice file: $e\n$s');
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import 'package:mnemo_cards_backend/api/ads/ads_manager.dart';
|
|||
import 'package:mnemo_cards_backend/extensions.dart';
|
||||
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
||||
import 'package:mnemo_cards_backend/packs/card_model_extension.dart';
|
||||
import 'package:mnemo_cards_backend/packs/pack_manager_extensions.dart';
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
|
||||
import 'products_price_resolver.dart';
|
||||
|
|
@ -45,9 +44,7 @@ class PackDtoConverter {
|
|||
cards: model.cards.length,
|
||||
tests: model.tests.length,
|
||||
price: price,
|
||||
imageBase64: model.cover != null
|
||||
? await model.cover!.smallBase64Image
|
||||
: null,
|
||||
imageBase64: null, // Legacy: covers should be stored in MinIO, not read from disk
|
||||
color: model.color,
|
||||
isAvailable: available,
|
||||
// trail: 'asset:icons/gift.png',
|
||||
|
|
@ -95,9 +92,7 @@ class PackDtoConverter {
|
|||
.toDtosList(model.cardsOrder)
|
||||
.map(
|
||||
(dto) async => dto.copyWith(
|
||||
image: dto.image != null
|
||||
? await dto.image!.smallBase64Image
|
||||
: null,
|
||||
image: null, // Legacy: card images should be stored in MinIO, not read from disk
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -158,7 +153,7 @@ class PackDtoConverter {
|
|||
subtitle: model.subtitle,
|
||||
color: model.color,
|
||||
version: model.version,
|
||||
cover: model.cover != null ? await model.cover!.base64Image : null,
|
||||
cover: null, // Legacy: covers should be stored in MinIO, not read from disk
|
||||
size: model.size,
|
||||
googlePlayId: model.googlePlayId,
|
||||
rustoreId: model.rustoreId,
|
||||
|
|
@ -173,7 +168,7 @@ class PackDtoConverter {
|
|||
removeCardIds: null,
|
||||
removeTestIds: null,
|
||||
previewCards: model.previewCards
|
||||
.map((card) => card.id?.toString())
|
||||
.map((card) => card.id.toString())
|
||||
.whereNotNull()
|
||||
.toList(),
|
||||
order: model.order,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:mnemo_cards_backend/database/database.dart';
|
||||
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart' hide VoiceModel;
|
||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||
import 'pack_dto_converter.dart';
|
||||
import 'pack_manager_extensions.dart';
|
||||
import 'card_pack_drift_extension.dart';
|
||||
|
||||
@lazySingleton
|
||||
|
|
@ -76,53 +73,6 @@ class PackManager {
|
|||
return await pack.toDto(cards, voices, userModel);
|
||||
}
|
||||
|
||||
Future<List<String>> getPackPreviewImages(String packId) async {
|
||||
final pack = await getPack(packId);
|
||||
if (pack == null) {
|
||||
throw StateError('Pack not found');
|
||||
}
|
||||
|
||||
final previewCards = await _db.packDao.getPreviewCards(packId);
|
||||
final cards = previewCards.isNotEmpty ? previewCards : (await getCards(packId)).take(6).toList();
|
||||
|
||||
final images = <String>[];
|
||||
for (final card in previewCards) {
|
||||
if (card.image.isNotEmpty) {
|
||||
try {
|
||||
final image = await card.image.base64Image;
|
||||
images.add(image);
|
||||
} catch (e, s) {
|
||||
log('error while reading image', error: e, stackTrace: s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we don't have enough preview images, fill with empty strings
|
||||
while (images.length < 6) {
|
||||
images.add('');
|
||||
}
|
||||
|
||||
return images;
|
||||
}
|
||||
|
||||
Future<Map<String, Uint8List>> getPackImages(String packId) async {
|
||||
final cards = await getCards(packId);
|
||||
final result = <String, Uint8List>{};
|
||||
|
||||
for (final card in cards) {
|
||||
if (card.image.isNotEmpty) {
|
||||
try {
|
||||
result[card.id.toString()] =
|
||||
File('${PackManagerUtils.assetsDirectory.path}/cards/${card.image}')
|
||||
.readAsBytesSync();
|
||||
} catch (e, s) {
|
||||
log('error while reading image', error: e, stackTrace: s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Static utility functions for PackManager
|
||||
|
|
|
|||
|
|
@ -1,56 +0,0 @@
|
|||
// Extensions and utilities for PackManager
|
||||
|
||||
import 'dart:typed_data';
|
||||
import 'dart:io';
|
||||
import 'dart:convert';
|
||||
import 'package:image/image.dart';
|
||||
import 'pack_manager.dart';
|
||||
|
||||
enum _ImageSize {
|
||||
big,
|
||||
medium,
|
||||
small,
|
||||
extraSmall,
|
||||
}
|
||||
|
||||
extension on _ImageSize {
|
||||
int get width => switch (this) {
|
||||
_ImageSize.big => 1024,
|
||||
_ImageSize.medium => 512,
|
||||
_ImageSize.small => 320,
|
||||
_ImageSize.extraSmall => 192,
|
||||
};
|
||||
}
|
||||
|
||||
extension CoverStringExt on String? {
|
||||
Future<String> get base64Image => _base64Image();
|
||||
|
||||
Future<String> _base64Image([_ImageSize? size]) async {
|
||||
if (this == null) return '';
|
||||
try {
|
||||
final bytes = _getById(this!, 'covers', size);
|
||||
return base64Encode(bytes);
|
||||
} catch (e) {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
Uint8List _getById(String id, String type, _ImageSize? size) {
|
||||
try {
|
||||
return File('${PackManagerUtils.assetsDirectory.path}/$type/$id')
|
||||
.readAsBytesSync();
|
||||
} catch (e) {
|
||||
print('Error loading image $id of type $type: $e');
|
||||
return Uint8List(0);
|
||||
}
|
||||
}
|
||||
|
||||
Future<String> get smallBase64Image => _base64Image(_ImageSize.small);
|
||||
|
||||
Future<String> get extraSmallBase64Image =>
|
||||
_base64Image(_ImageSize.extraSmall);
|
||||
|
||||
Future<String> get mediumBase64Image => _base64Image(_ImageSize.medium);
|
||||
|
||||
Future<String> get bigBase64Image => _base64Image(null);
|
||||
}
|
||||
|
|
@ -27,7 +27,7 @@ class MinioConfig {
|
|||
factory MinioConfig.fromEnvironment() {
|
||||
return MinioConfig(
|
||||
endpoint: const String.fromEnvironment(
|
||||
'MINIO_ENDPOINT',
|
||||
'MINIO_SERVER_URL',
|
||||
defaultValue:
|
||||
'minio-rsso80cks4ck4oc44s0og80c.147.45.152.129.sslip.io',
|
||||
),
|
||||
|
|
|
|||
|
|
@ -823,11 +823,8 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
|
|||
|
||||
/// Изображение карточки (вынесено в отдельный метод для переиспользования)
|
||||
Widget _buildCardImage(GameCardDto card, double width, double height) {
|
||||
// Use presigned URL from DTO if available, fallback to building URL
|
||||
final imageUrl = card.imageUrl ??
|
||||
(card.image != null && card.image!.isNotEmpty
|
||||
? ApiConfigV2.getCardImageUrl(widget.packId, card.id)
|
||||
: null);
|
||||
// Use presigned URL from DTO
|
||||
final imageUrl = card.imageUrl;
|
||||
|
||||
if (imageUrl == null || imageUrl.isEmpty) {
|
||||
return Center(
|
||||
|
|
|
|||
|
|
@ -720,11 +720,8 @@ class _CardSide extends StatelessWidget {
|
|||
}
|
||||
|
||||
Widget _buildImage() {
|
||||
// Use presigned URL from DTO if available, fallback to building URL
|
||||
final imageUrl = card.imageUrl ??
|
||||
(card.image != null && card.image!.isNotEmpty
|
||||
? ApiConfigV2.getCardImageUrl(packId, card.id)
|
||||
: null);
|
||||
// Use presigned URL from DTO
|
||||
final imageUrl = card.imageUrl;
|
||||
|
||||
if (imageUrl == null || imageUrl.isEmpty) {
|
||||
return Container(
|
||||
|
|
|
|||
|
|
@ -565,11 +565,8 @@ class _CardSide extends StatelessWidget {
|
|||
}
|
||||
|
||||
Widget _buildImage() {
|
||||
// Use presigned URL from DTO if available, fallback to building URL
|
||||
final imageUrl = card.imageUrl ??
|
||||
(card.image != null && card.image!.isNotEmpty
|
||||
? ApiConfigV2.getCardImageUrl(packId, card.id)
|
||||
: null);
|
||||
// Use presigned URL from DTO
|
||||
final imageUrl = card.imageUrl;
|
||||
|
||||
if (imageUrl == null || imageUrl.isEmpty) {
|
||||
return Container(
|
||||
|
|
@ -603,11 +600,8 @@ class _CardSide extends StatelessWidget {
|
|||
}
|
||||
|
||||
Widget _buildImageBack() {
|
||||
// Use presigned URL from DTO if available, fallback to building URL
|
||||
final imageBackUrl = card.imageBackUrl ??
|
||||
(card.imageBack != null && card.imageBack!.isNotEmpty
|
||||
? ApiConfigV2.getCardImageBackUrl(packId, card.id)
|
||||
: null);
|
||||
// Use presigned URL from DTO
|
||||
final imageBackUrl = card.imageBackUrl;
|
||||
|
||||
if (imageBackUrl == null || imageBackUrl.isEmpty) {
|
||||
return Container(
|
||||
|
|
|
|||
|
|
@ -260,11 +260,8 @@ class PackCardItem extends StatelessWidget {
|
|||
/// Отображает изображение карточки
|
||||
/// Загружает изображение с бэкенда по URL
|
||||
Widget _buildImage(BuildContext context, double cardWidth, double cardHeight) {
|
||||
// Use presigned URL from DTO if available, fallback to building URL
|
||||
final imageUrl = card.imageUrl ??
|
||||
(card.image != null && card.image!.isNotEmpty
|
||||
? ApiConfigV2.getCardImageUrl(packId, card.id)
|
||||
: null);
|
||||
// Use presigned URL from DTO
|
||||
final imageUrl = card.imageUrl;
|
||||
|
||||
if (imageUrl == null || imageUrl.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
|
|
|
|||
Loading…
Reference in a new issue