import 'package:drift/drift.dart'; import 'package:drift_postgres/drift_postgres.dart'; import 'package:postgres/postgres.dart' as pg; import 'dart:io'; // Импорт конвертеров (нужен для генерации кода) import 'converters.dart'; // Импорт всех таблиц import 'tables/users.dart'; import 'tables/auth.dart'; import 'tables/packs.dart'; import 'tables/relations.dart'; import 'tables/subscriptions.dart'; import 'tables/payments.dart'; import 'tables/tests.dart'; import 'tables/tasks.dart'; import 'tables/promo_codes.dart'; import 'tables/discounts.dart'; import 'tables/statistics.dart'; import 'tables/telegram.dart'; import 'tables/achievements.dart'; // Импорт DAOs import 'daos/user_dao.dart'; import 'daos/pack_dao.dart'; import 'daos/test_dao.dart'; import 'daos/payment_dao.dart'; import 'daos/subscription_dao.dart'; import 'daos/task_dao.dart'; import 'daos/promo_code_dao.dart'; import 'daos/discount_dao.dart'; import 'daos/statistics_dao.dart'; import 'daos/achievement_dao.dart'; import 'postgres_constants.dart'; // Сгенерированный код будет здесь part 'database.g.dart'; @DriftDatabase( tables: [ // User tables Users, UserDatas, // Auth tables Tokens, RefreshTokens, TelegramAuthCodes, // Pack tables CardPacks, GameCards, VoiceModels, // Relations UserPacks, PreviewCards, CardPackCards, CardVoices, // Subscription tables SubscriptionPlans, UserSubscriptions, // Payment tables Payments, // Test tables Tests, TestQuestions, TestPackRelations, TestStatistics, // Task tables Tasks, UserTasks, UserTaskProgresses, UserTaskResults, // Promo code tables PromoCodesCampaigns, PromoCodes, // Discount tables DiscountCampaigns, Discounts, DiscountUserDatas, // Statistics tables StudySessions, // Achievements tables UserAchievements, // Telegram tables ShareRequests, ], daos: [ UserDao, PackDao, TestDao, PaymentDao, SubscriptionDao, TaskDao, PromoCodeDao, DiscountDao, StatisticsDao, AchievementDao, ], ) class AppDatabase extends _$AppDatabase { AppDatabase(super.e); @override int get schemaVersion => 1; /// Factory для подключения к PostgreSQL static AppDatabase connect({ required String host, required int port, required String database, required String username, required String password, bool useSsl = false, }) { final endpoint = pg.Endpoint( host: host, port: port, database: database, username: username, password: password, ); final connection = PgDatabase( endpoint: endpoint, settings: pg.ConnectionSettings( sslMode: useSsl ? pg.SslMode.require : pg.SslMode.disable, connectTimeout: const Duration(seconds: 10), ), ); return AppDatabase(connection); } /// Factory для подключения из environment variables static AppDatabase fromEnvironment() { return connect( host: Platform.environment['DB_HOST'] ?? 'localhost', port: int.parse(Platform.environment['DB_PORT'] ?? '5432'), database: Platform.environment['DB_NAME'] ?? 'mnemo_cards_dev', username: Platform.environment['DB_USER'] ?? 'mnemo_user', password: Platform.environment['DB_PASSWORD'] ?? '', useSsl: Platform.environment['DB_SSL_MODE'] == 'require', ); } @override MigrationStrategy get migration => MigrationStrategy( onCreate: (Migrator m) async { print('Creating database schema...'); await m.createAll(); print('Database schema created successfully'); // Создать индексы для оптимизации await _createIndexes(); }, onUpgrade: (Migrator m, int from, int to) async { print('Migrating database from version $from to $to'); // Миграции при обновлении схемы // if (from < 2) { // await m.addColumn(users, users.phoneNumber); // } }, beforeOpen: (details) async { print('Opening database connection...'); // Проверка подключения final result = await customSelect('SELECT 1 as test').getSingle(); print('Database connection successful: ${result.data}'); // Включить foreign key constraints await customStatement('SET CONSTRAINTS ALL IMMEDIATE'); }, ); /// Создание индексов для оптимизации запросов Future _createIndexes() async { print('Creating indexes...'); // Users indexes await customStatement('CREATE INDEX IF NOT EXISTS idx_users_email ON users(email) WHERE email IS NOT NULL'); await customStatement('CREATE INDEX IF NOT EXISTS idx_users_admin ON users(admin) WHERE admin = true'); await customStatement('CREATE INDEX IF NOT EXISTS idx_users_not_deleted ON users(is_deleted) WHERE is_deleted = false'); // UserDatas indexes await customStatement('CREATE INDEX IF NOT EXISTS idx_user_datas_user_id ON user_datas(user_id)'); await customStatement('CREATE INDEX IF NOT EXISTS idx_user_datas_last_online ON user_datas(last_time_online DESC)'); // Auth indexes await customStatement('CREATE INDEX IF NOT EXISTS idx_tokens_user_id ON tokens(user_id)'); // Индекс на expires без предиката (NOW() не может быть использована в предикате индекса) await customStatement('CREATE INDEX IF NOT EXISTS idx_tokens_expires ON tokens(expires)'); await customStatement('CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens(user_id)'); await customStatement('CREATE INDEX IF NOT EXISTS idx_refresh_tokens_jti ON refresh_tokens(jti)'); await customStatement('CREATE INDEX IF NOT EXISTS idx_refresh_tokens_not_blacklisted ON refresh_tokens(is_blacklisted) WHERE is_blacklisted = false'); await customStatement('CREATE INDEX IF NOT EXISTS idx_telegram_codes_code ON telegram_auth_codes(code)'); await customStatement('CREATE INDEX IF NOT EXISTS idx_telegram_codes_not_used ON telegram_auth_codes(is_used) WHERE is_used = false'); // CardPacks indexes await customStatement('CREATE INDEX IF NOT EXISTS idx_packs_enabled ON card_packs(enabled) WHERE enabled = true'); await customStatement('CREATE INDEX IF NOT EXISTS idx_packs_order ON card_packs("order")'); // GameCards indexes await customStatement('CREATE INDEX IF NOT EXISTS idx_cards_original ON game_cards(original)'); // UserPacks indexes await customStatement('CREATE INDEX IF NOT EXISTS idx_user_packs_user_id ON user_packs(user_id)'); await customStatement('CREATE INDEX IF NOT EXISTS idx_user_packs_pack_id ON user_packs(pack_id)'); // Payments indexes await customStatement('CREATE INDEX IF NOT EXISTS idx_payments_user_id ON payments(user_id)'); await customStatement('CREATE INDEX IF NOT EXISTS idx_payments_status ON payments(status)'); await customStatement('CREATE INDEX IF NOT EXISTS idx_payments_created ON payments(date DESC)'); // Subscriptions indexes await customStatement('CREATE INDEX IF NOT EXISTS idx_subscriptions_user_id ON user_subscriptions(user_id)'); // Индекс на finish без предиката (NOW() не может быть использована в предикате индекса) await customStatement('CREATE INDEX IF NOT EXISTS idx_subscriptions_finish ON user_subscriptions(finish)'); // Tests indexes await customStatement('CREATE INDEX IF NOT EXISTS idx_test_questions_test_id ON test_questions(test_id)'); // StudySessions indexes await customStatement('CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON study_sessions(user_id)'); await customStatement('CREATE INDEX IF NOT EXISTS idx_sessions_started ON study_sessions(start_time DESC)'); // PromoCodes indexes await customStatement('CREATE INDEX IF NOT EXISTS idx_promo_codes_code ON promo_codes(code)'); await customStatement('CREATE INDEX IF NOT EXISTS idx_promo_codes_campaign_id ON promo_codes(campaign_id)'); print('Indexes created successfully'); } }