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 with _$PackDaoMixin { PackDao(super.db); // ==================== CardPacks ==================== /// Получить пак по ID Future getPackById(String id) { return (select(cardPacks)..where((p) => p.id.equals(id))).getSingleOrNull(); } /// Получить все паки Future> 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 createPack(CardPacksCompanion pack) async { final inserted = await into(cardPacks).insertReturning(pack); return inserted.id; } /// Обновить пак Future updatePack(CardPack pack) { return update(cardPacks).replace(pack); } /// Обновить пак частично Future 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 softDeletePack(String packId) { return (update(cardPacks)..where((p) => p.id.equals(packId))) .write(CardPacksCompanion( isDeleted: const Value(true), updatedAt: Value(PgDateTime(DateTime.now())), )); } /// Подсчитать паки Future 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 getCardById(String id) { return (select(gameCards) ..where((c) => c.id.equals(id) & c.isDeleted.equals(false)) ).getSingleOrNull(); } /// Получить паки для карточки /// Связь через CardPackCards (packId удален из GameCards) Future> 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> 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> getCardsByIds(List ids) { if (ids.isEmpty) return Future.value([]); return (select(gameCards)..where((c) => c.id.isIn(ids))).get(); } /// Найти карточки по original (слову) /// Возвращает все карточки с указанным original (может быть несколько) Future> searchCardsByOriginal(String original) { return (select(gameCards) ..where((c) => c.original.equals(original) & c.isDeleted.equals(false)) ..limit(10) // Ограничиваем для производительности ).get(); } /// Создать карточку Future createCard(GameCardsCompanion card) async { final inserted = await into(gameCards).insertReturning(card); return inserted.id; } /// Обновить карточку Future updateCard(GameCard card) { return update(gameCards).replace(card); } /// Удалить карточку (soft delete) Future 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 deleteCard(String cardId) { return (delete(gameCards)..where((c) => c.id.equals(cardId))).go(); } /// Получить все карточки с пагинацией Future> getAllCards({int? limit, int? offset}) { final query = select(gameCards); if (limit != null) { query.limit(limit, offset: offset); } return query.get(); } /// Подсчитать карточки Future countCards() async { final countExpr = gameCards.id.count(); final query = selectOnly(gameCards)..addColumns([countExpr]); return await query.map((row) => row.read(countExpr)!).getSingle(); } /// Добавить карточку в пак Future 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 removeCardFromPack(String packId, String cardId) async { await (delete(cardPackCards) ..where((cpc) => cpc.packId.equals(packId) & cpc.cardId.equals(cardId)) ).go(); } /// Обновить порядок карточек в паке Future updatePackCardsOrder(String packId, List 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> 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 setPreviewCards(String packId, List 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> getCardVoices(String cardId) async { final query = select(voiceModels).join([ innerJoin( cardVoices, cardVoices.voiceId.equalsExp(voiceModels.id) & cardVoices.cardId.equals(cardId), ), ]); return query.map((row) => row.readTable(voiceModels)).get(); } /// Добавить голосовую модель к карточке Future addVoiceToCard(String cardId, String voiceId) async { await into(cardVoices).insert( CardVoicesCompanion.insert( cardId: cardId, voiceId: voiceId, ), mode: InsertMode.insertOrIgnore, ); } /// Удалить голосовую модель из карточки Future removeVoiceFromCard(String cardId, String voiceId) async { await (delete(cardVoices) ..where((cv) => cv.cardId.equals(cardId) & cv.voiceId.equals(voiceId)) ).go(); } /// Создать голосовую модель Future createVoice(VoiceModelsCompanion voice) async { final inserted = await into(voiceModels).insertReturning(voice); return inserted.id; } /// Получить голосовую модель по ID Future getVoiceById(String id) { return (select(voiceModels)..where((v) => v.id.equals(id))).getSingleOrNull(); } /// Обновить путь/URL аудиофайла голосовой модели Future updateVoiceUrl(String voiceId, String voiceUrl) async { await (update(voiceModels)..where((v) => v.id.equals(voiceId))).write( VoiceModelsCompanion(voiceUrl: Value(voiceUrl)), ); } /// Удалить голосовую модель Future deleteVoice(String voiceId) async { await (delete(voiceModels)..where((v) => v.id.equals(voiceId))).go(); } }