mnemo_cards/mnemo_cards_backend/lib/database/daos/pack_dao.dart

357 lines
12 KiB
Dart
Raw Normal View History

2025-12-13 13:27:05 +00:00
import 'package:drift/drift.dart';
2025-12-13 23:35:14 +00:00
import 'package:drift_postgres/drift_postgres.dart';
2025-12-13 13:27:05 +00:00
import '../database.dart';
import '../tables/packs.dart';
import '../tables/relations.dart';
part 'pack_dao.g.dart';
@DriftAccessor(tables: [CardPacks, GameCards, VoiceModels, PreviewCards, CardPackCards, CardVoices])
class PackDao extends DatabaseAccessor<AppDatabase> with _$PackDaoMixin {
PackDao(super.db);
// ==================== CardPacks ====================
/// Получить пак по ID
2025-12-13 20:55:50 +00:00
Future<CardPack?> getPackById(String id) {
2025-12-13 13:27:05 +00:00
return (select(cardPacks)..where((p) => p.id.equals(id))).getSingleOrNull();
}
/// Получить все паки
Future<List<CardPack>> getAllPacks({
bool enabledOnly = false,
String? orderByField,
bool orderDesc = false,
}) {
final query = select(cardPacks);
if (enabledOnly) {
query.where((p) => p.enabled.equals(true));
}
query.where((p) => p.isDeleted.equals(false));
if (orderByField == 'order') {
if (orderDesc) {
query.orderBy([(p) => OrderingTerm.desc(p.order)]);
} else {
query.orderBy([(p) => OrderingTerm.asc(p.order)]);
}
} else {
query.orderBy([(p) => OrderingTerm.asc(p.order)]);
}
return query.get();
}
/// Создать пак
2025-12-13 20:55:50 +00:00
Future<String> createPack(CardPacksCompanion pack) async {
final inserted = await into(cardPacks).insertReturning(pack);
return inserted.id;
2025-12-13 13:27:05 +00:00
}
/// Обновить пак
Future<bool> updatePack(CardPack pack) {
return update(cardPacks).replace(pack);
}
/// Обновить пак частично
Future<void> updatePackPartial(CardPacksCompanion updates) {
2025-12-18 21:31:51 +00:00
if (!updates.id.present) throw ArgumentError('Pack ID is required');
2025-12-13 13:27:05 +00:00
final packId = updates.id.value;
return (update(cardPacks)..where((p) => p.id.equals(packId)))
2025-12-13 23:35:14 +00:00
.write(updates.copyWith(updatedAt: Value(PgDateTime(DateTime.now()))));
2025-12-13 13:27:05 +00:00
}
/// Удалить пак (soft delete)
2025-12-13 20:55:50 +00:00
Future<void> softDeletePack(String packId) {
2025-12-13 13:27:05 +00:00
return (update(cardPacks)..where((p) => p.id.equals(packId)))
.write(CardPacksCompanion(
isDeleted: const Value(true),
2025-12-13 23:35:14 +00:00
updatedAt: Value(PgDateTime(DateTime.now())),
2025-12-13 13:27:05 +00:00
));
}
/// Подсчитать паки
Future<int> countPacks({bool enabledOnly = false}) async {
final countExpr = cardPacks.id.count();
final query = selectOnly(cardPacks)..addColumns([countExpr]);
if (enabledOnly) {
query.where(cardPacks.enabled.equals(true));
}
query.where(cardPacks.isDeleted.equals(false));
return await query.map((row) => row.read(countExpr)!).getSingle();
}
// ==================== GameCards ====================
2025-12-17 00:41:47 +00:00
/// Получить карточку по ID (только активные)
2025-12-13 20:55:50 +00:00
Future<GameCard?> getCardById(String id) {
2025-12-17 00:41:47 +00:00
return (select(gameCards)
..where((c) => c.id.equals(id) & c.isDeleted.equals(false))
).getSingleOrNull();
2025-12-13 13:27:05 +00:00
}
2025-12-14 21:10:06 +00:00
/// Получить паки для карточки
/// Связь через CardPackCards (packId удален из GameCards)
Future<List<CardPack>> getPacksForCard(String cardId) async {
final query = select(cardPacks).join([
innerJoin(
cardPackCards,
cardPackCards.packId.equalsExp(cardPacks.id),
),
])..where(cardPackCards.cardId.equals(cardId) & cardPacks.isDeleted.equals(false));
final rows = await query.get();
return rows.map((row) => row.readTable(cardPacks)).toList();
}
2025-12-13 13:27:05 +00:00
/// Получить все карточки пака
2025-12-14 20:42:38 +00:00
/// Связь теперь только через CardPackCards (packId удален из GameCards)
2025-12-13 20:55:50 +00:00
Future<List<GameCard>> getPackCards(String packId) async {
2025-12-13 14:48:00 +00:00
final query = select(gameCards).join([
2025-12-14 20:42:38 +00:00
innerJoin(
2025-12-13 14:48:00 +00:00
cardPackCards,
cardPackCards.cardId.equalsExp(gameCards.id) &
cardPackCards.packId.equals(packId),
),
2025-12-14 20:42:38 +00:00
])..where(gameCards.isDeleted.equals(false));
2025-12-13 14:48:00 +00:00
final results = await query.get();
// Сортируем по order из junction table, если есть, иначе по ID
results.sort((a, b) {
final orderA = a.read(cardPackCards.order) ?? 999999;
final orderB = a.read(cardPackCards.order) ?? 999999;
if (orderA != orderB) return orderA.compareTo(orderB);
return a.readTable(gameCards).id.compareTo(b.readTable(gameCards).id);
});
return results.map((row) => row.readTable(gameCards)).toList();
2025-12-13 13:27:05 +00:00
}
/// Получить карточки по списку ID
2025-12-13 20:55:50 +00:00
Future<List<GameCard>> getCardsByIds(List<String> ids) {
2025-12-13 13:27:05 +00:00
if (ids.isEmpty) return Future.value([]);
return (select(gameCards)..where((c) => c.id.isIn(ids))).get();
}
2025-12-14 20:45:27 +00:00
/// Найти карточки по original (слову)
/// Возвращает все карточки с указанным original (может быть несколько)
Future<List<GameCard>> searchCardsByOriginal(String original) {
return (select(gameCards)
..where((c) => c.original.equals(original) & c.isDeleted.equals(false))
..limit(10) // Ограничиваем для производительности
).get();
}
2025-12-13 13:27:05 +00:00
/// Создать карточку
2025-12-13 20:55:50 +00:00
Future<String> createCard(GameCardsCompanion card) async {
final inserted = await into(gameCards).insertReturning(card);
return inserted.id;
2025-12-13 13:27:05 +00:00
}
/// Обновить карточку
Future<bool> updateCard(GameCard card) {
return update(gameCards).replace(card);
}
/// Удалить карточку (soft delete)
2025-12-13 20:55:50 +00:00
Future<void> softDeleteCard(String cardId) {
2025-12-13 13:27:05 +00:00
return (update(gameCards)..where((c) => c.id.equals(cardId)))
.write(GameCardsCompanion(
isDeleted: const Value(true),
2025-12-13 23:35:14 +00:00
updatedAt: Value(PgDateTime(DateTime.now())),
2025-12-13 13:27:05 +00:00
));
}
/// Удалить карточку (hard delete)
2025-12-13 20:55:50 +00:00
Future<void> deleteCard(String cardId) {
2025-12-13 13:27:05 +00:00
return (delete(gameCards)..where((c) => c.id.equals(cardId))).go();
}
/// Получить все карточки с пагинацией
Future<List<GameCard>> getAllCards({int? limit, int? offset}) {
final query = select(gameCards);
if (limit != null) {
query.limit(limit, offset: offset);
}
return query.get();
}
/// Подсчитать карточки
Future<int> countCards() async {
final countExpr = gameCards.id.count();
final query = selectOnly(gameCards)..addColumns([countExpr]);
return await query.map((row) => row.read(countExpr)!).getSingle();
}
/// Добавить карточку в пак
Future<void> addCardToPack({
2025-12-13 20:55:50 +00:00
required String packId,
required String cardId,
2025-12-13 13:27:05 +00:00
int order = 0,
}) async {
await into(cardPackCards).insert(
CardPackCardsCompanion.insert(
packId: packId,
cardId: cardId,
order: Value(order),
),
mode: InsertMode.insertOrIgnore,
);
}
/// Удалить карточку из пака
2025-12-13 20:55:50 +00:00
Future<void> removeCardFromPack(String packId, String cardId) async {
2025-12-13 13:27:05 +00:00
await (delete(cardPackCards)
..where((cpc) => cpc.packId.equals(packId) & cpc.cardId.equals(cardId))
).go();
}
/// Обновить порядок карточек в паке
2025-12-13 20:55:50 +00:00
Future<void> updatePackCardsOrder(String packId, List<String> cardIds) async {
2025-12-13 13:27:05 +00:00
await transaction(() async {
// Удалить старые связи
await (delete(cardPackCards)
..where((cpc) => cpc.packId.equals(packId))
).go();
// Создать новые связи с порядком
for (var i = 0; i < cardIds.length; i++) {
await into(cardPackCards).insert(
CardPackCardsCompanion.insert(
packId: packId,
cardId: cardIds[i],
order: Value(i),
),
);
}
});
}
// ==================== PreviewCards ====================
/// Получить preview карточки пака
2025-12-13 20:55:50 +00:00
Future<List<GameCard>> getPreviewCards(String packId) async {
2025-12-13 13:27:05 +00:00
final query = select(gameCards).join([
innerJoin(
db.previewCards,
db.previewCards.cardId.equalsExp(gameCards.id) &
db.previewCards.packId.equals(packId),
),
]);
final results = await query.get();
// Сортируем вручную по order из junction table
results.sort((a, b) {
final orderA = a.read(db.previewCards.order) ?? 0;
final orderB = b.read(db.previewCards.order) ?? 0;
return orderA.compareTo(orderB);
});
return results.map((row) => row.readTable(gameCards)).toList();
}
/// Установить preview карточки для пака
2025-12-13 20:55:50 +00:00
Future<void> setPreviewCards(String packId, List<String> cardIds) async {
2025-12-13 13:27:05 +00:00
await transaction(() async {
// Удалить старые preview карточки
await (delete(previewCards)
..where((pc) => pc.packId.equals(packId))
).go();
// Добавить новые
for (var i = 0; i < cardIds.length; i++) {
await into(previewCards).insert(
PreviewCardsCompanion.insert(
packId: packId,
cardId: cardIds[i],
order: Value(i),
),
);
}
});
}
// ==================== VoiceModels ====================
/// Получить голосовые модели карточки
2025-12-13 20:55:50 +00:00
Future<List<VoiceModel>> getCardVoices(String cardId) async {
2025-12-19 02:42:08 +00:00
print('🔍 DAO getCardVoices: Querying voices for card $cardId');
// First, 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');
2025-12-13 13:27:05 +00:00
final query = select(voiceModels).join([
innerJoin(
cardVoices,
cardVoices.voiceId.equalsExp(voiceModels.id) &
cardVoices.cardId.equals(cardId),
),
]);
2025-12-19 02:42:08 +00:00
final result = await query.map((row) => row.readTable(voiceModels)).get();
print('🔍 DAO getCardVoices: Query returned ${result.length} voices');
return result;
2025-12-13 13:27:05 +00:00
}
/// Добавить голосовую модель к карточке
2025-12-13 20:55:50 +00:00
Future<void> addVoiceToCard(String cardId, String voiceId) async {
2025-12-19 02:42:08 +00:00
print('🔍 DAO addVoiceToCard: Linking voice $voiceId to card $cardId');
try {
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: Error linking voice $voiceId to card $cardId: $e');
rethrow;
}
2025-12-13 13:27:05 +00:00
}
/// Удалить голосовую модель из карточки
2025-12-13 20:55:50 +00:00
Future<void> removeVoiceFromCard(String cardId, String voiceId) async {
2025-12-13 13:27:05 +00:00
await (delete(cardVoices)
..where((cv) => cv.cardId.equals(cardId) & cv.voiceId.equals(voiceId))
).go();
}
/// Создать голосовую модель
2025-12-13 20:55:50 +00:00
Future<String> createVoice(VoiceModelsCompanion voice) async {
final inserted = await into(voiceModels).insertReturning(voice);
return inserted.id;
2025-12-13 13:27:05 +00:00
}
/// Получить голосовую модель по ID
2025-12-13 20:55:50 +00:00
Future<VoiceModel?> getVoiceById(String id) {
2025-12-13 13:27:05 +00:00
return (select(voiceModels)..where((v) => v.id.equals(id))).getSingleOrNull();
}
2025-12-18 21:31:51 +00:00
/// Обновить путь/URL аудиофайла голосовой модели
Future<void> updateVoiceUrl(String voiceId, String voiceUrl) async {
await (update(voiceModels)..where((v) => v.id.equals(voiceId))).write(
VoiceModelsCompanion(voiceUrl: Value(voiceUrl)),
);
}
2025-12-14 18:49:59 +00:00
/// Удалить голосовую модель
Future<void> deleteVoice(String voiceId) async {
await (delete(voiceModels)..where((v) => v.id.equals(voiceId))).go();
}
2025-12-13 13:27:05 +00:00
}