mnemo_cards/mnemo_cards_backend/lib/database/daos/pack_dao.dart
Dmitry d8a29eb84a
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
voices
2025-12-19 05:42:08 +03:00

356 lines
12 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:drift/drift.dart';
import 'package:drift_postgres/drift_postgres.dart';
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
Future<CardPack?> getPackById(String id) {
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();
}
/// Создать пак
Future<String> createPack(CardPacksCompanion pack) async {
final inserted = await into(cardPacks).insertReturning(pack);
return inserted.id;
}
/// Обновить пак
Future<bool> updatePack(CardPack pack) {
return update(cardPacks).replace(pack);
}
/// Обновить пак частично
Future<void> updatePackPartial(CardPacksCompanion updates) {
if (!updates.id.present) throw ArgumentError('Pack ID is required');
final packId = updates.id.value;
return (update(cardPacks)..where((p) => p.id.equals(packId)))
.write(updates.copyWith(updatedAt: Value(PgDateTime(DateTime.now()))));
}
/// Удалить пак (soft delete)
Future<void> softDeletePack(String packId) {
return (update(cardPacks)..where((p) => p.id.equals(packId)))
.write(CardPacksCompanion(
isDeleted: const Value(true),
updatedAt: Value(PgDateTime(DateTime.now())),
));
}
/// Подсчитать паки
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 ====================
/// Получить карточку по ID (только активные)
Future<GameCard?> getCardById(String id) {
return (select(gameCards)
..where((c) => c.id.equals(id) & c.isDeleted.equals(false))
).getSingleOrNull();
}
/// Получить паки для карточки
/// Связь через 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();
}
/// Получить все карточки пака
/// Связь теперь только через CardPackCards (packId удален из GameCards)
Future<List<GameCard>> getPackCards(String packId) async {
final query = select(gameCards).join([
innerJoin(
cardPackCards,
cardPackCards.cardId.equalsExp(gameCards.id) &
cardPackCards.packId.equals(packId),
),
])..where(gameCards.isDeleted.equals(false));
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();
}
/// Получить карточки по списку ID
Future<List<GameCard>> getCardsByIds(List<String> ids) {
if (ids.isEmpty) return Future.value([]);
return (select(gameCards)..where((c) => c.id.isIn(ids))).get();
}
/// Найти карточки по original (слову)
/// Возвращает все карточки с указанным original (может быть несколько)
Future<List<GameCard>> searchCardsByOriginal(String original) {
return (select(gameCards)
..where((c) => c.original.equals(original) & c.isDeleted.equals(false))
..limit(10) // Ограничиваем для производительности
).get();
}
/// Создать карточку
Future<String> createCard(GameCardsCompanion card) async {
final inserted = await into(gameCards).insertReturning(card);
return inserted.id;
}
/// Обновить карточку
Future<bool> updateCard(GameCard card) {
return update(gameCards).replace(card);
}
/// Удалить карточку (soft delete)
Future<void> softDeleteCard(String cardId) {
return (update(gameCards)..where((c) => c.id.equals(cardId)))
.write(GameCardsCompanion(
isDeleted: const Value(true),
updatedAt: Value(PgDateTime(DateTime.now())),
));
}
/// Удалить карточку (hard delete)
Future<void> deleteCard(String cardId) {
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({
required String packId,
required String cardId,
int order = 0,
}) async {
await into(cardPackCards).insert(
CardPackCardsCompanion.insert(
packId: packId,
cardId: cardId,
order: Value(order),
),
mode: InsertMode.insertOrIgnore,
);
}
/// Удалить карточку из пака
Future<void> removeCardFromPack(String packId, String cardId) async {
await (delete(cardPackCards)
..where((cpc) => cpc.packId.equals(packId) & cpc.cardId.equals(cardId))
).go();
}
/// Обновить порядок карточек в паке
Future<void> updatePackCardsOrder(String packId, List<String> cardIds) async {
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 карточки пака
Future<List<GameCard>> getPreviewCards(String packId) async {
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 карточки для пака
Future<void> setPreviewCards(String packId, List<String> cardIds) async {
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 ====================
/// Получить голосовые модели карточки
Future<List<VoiceModel>> 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
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');
final query = select(voiceModels).join([
innerJoin(
cardVoices,
cardVoices.voiceId.equalsExp(voiceModels.id) &
cardVoices.cardId.equals(cardId),
),
]);
final result = await query.map((row) => row.readTable(voiceModels)).get();
print('🔍 DAO getCardVoices: Query returned ${result.length} voices');
return result;
}
/// Добавить голосовую модель к карточке
Future<void> addVoiceToCard(String cardId, String voiceId) async {
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;
}
}
/// Удалить голосовую модель из карточки
Future<void> removeVoiceFromCard(String cardId, String voiceId) async {
await (delete(cardVoices)
..where((cv) => cv.cardId.equals(cardId) & cv.voiceId.equals(voiceId))
).go();
}
/// Создать голосовую модель
Future<String> createVoice(VoiceModelsCompanion voice) async {
final inserted = await into(voiceModels).insertReturning(voice);
return inserted.id;
}
/// Получить голосовую модель по ID
Future<VoiceModel?> getVoiceById(String id) {
return (select(voiceModels)..where((v) => v.id.equals(id))).getSingleOrNull();
}
/// Обновить путь/URL аудиофайла голосовой модели
Future<void> updateVoiceUrl(String voiceId, String voiceUrl) async {
await (update(voiceModels)..where((v) => v.id.equals(voiceId))).write(
VoiceModelsCompanion(voiceUrl: Value(voiceUrl)),
);
}
/// Удалить голосовую модель
Future<void> deleteVoice(String voiceId) async {
await (delete(voiceModels)..where((v) => v.id.equals(voiceId))).go();
}
}