2025-12-13 13:27:05 +00:00
|
|
|
|
import 'package:drift/drift.dart';
|
|
|
|
|
|
import 'package:drift_postgres/drift_postgres.dart';
|
|
|
|
|
|
import 'package:postgres/postgres.dart' as pg;
|
|
|
|
|
|
import 'dart:io';
|
2025-12-17 00:41:47 +00:00
|
|
|
|
import 'dart:convert';
|
2025-12-13 13:27:05 +00:00
|
|
|
|
|
|
|
|
|
|
// Импорт конвертеров (нужен для генерации кода)
|
|
|
|
|
|
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';
|
2025-12-14 20:36:05 +00:00
|
|
|
|
import 'tables/word_statistics.dart';
|
|
|
|
|
|
import 'tables/audit.dart';
|
2025-12-13 13:27:05 +00:00
|
|
|
|
|
|
|
|
|
|
// Импорт 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';
|
2025-12-14 20:36:05 +00:00
|
|
|
|
import 'daos/word_statistics_dao.dart';
|
|
|
|
|
|
import 'daos/audit_dao.dart';
|
2025-12-13 13:27:05 +00:00
|
|
|
|
|
|
|
|
|
|
// Сгенерированный код будет здесь
|
|
|
|
|
|
part 'database.g.dart';
|
|
|
|
|
|
|
|
|
|
|
|
@DriftDatabase(
|
|
|
|
|
|
tables: [
|
|
|
|
|
|
// User tables
|
|
|
|
|
|
Users,
|
|
|
|
|
|
UserDatas,
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Auth tables
|
|
|
|
|
|
Tokens,
|
|
|
|
|
|
RefreshTokens,
|
|
|
|
|
|
TelegramAuthCodes,
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Pack tables
|
|
|
|
|
|
CardPacks,
|
|
|
|
|
|
GameCards,
|
|
|
|
|
|
VoiceModels,
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Relations
|
|
|
|
|
|
UserPacks,
|
|
|
|
|
|
PreviewCards,
|
|
|
|
|
|
CardPackCards,
|
|
|
|
|
|
CardVoices,
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Subscription tables
|
|
|
|
|
|
SubscriptionPlans,
|
|
|
|
|
|
UserSubscriptions,
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Payment tables
|
|
|
|
|
|
Payments,
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Test tables
|
|
|
|
|
|
Tests,
|
|
|
|
|
|
TestQuestions,
|
|
|
|
|
|
TestPackRelations,
|
|
|
|
|
|
TestStatistics,
|
|
|
|
|
|
|
|
|
|
|
|
// Task tables
|
|
|
|
|
|
Tasks,
|
|
|
|
|
|
UserTasks,
|
|
|
|
|
|
UserTaskProgresses,
|
|
|
|
|
|
UserTaskResults,
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Promo code tables
|
|
|
|
|
|
PromoCodesCampaigns,
|
|
|
|
|
|
PromoCodes,
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Discount tables
|
|
|
|
|
|
DiscountCampaigns,
|
|
|
|
|
|
Discounts,
|
|
|
|
|
|
DiscountUserDatas,
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Statistics tables
|
|
|
|
|
|
StudySessions,
|
2025-12-14 20:36:05 +00:00
|
|
|
|
WordStatistics,
|
2025-12-13 13:27:05 +00:00
|
|
|
|
|
|
|
|
|
|
// Achievements tables
|
|
|
|
|
|
UserAchievements,
|
|
|
|
|
|
|
|
|
|
|
|
// Telegram tables
|
|
|
|
|
|
ShareRequests,
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-14 20:36:05 +00:00
|
|
|
|
// Audit tables
|
|
|
|
|
|
AuditLogs,
|
2025-12-13 13:27:05 +00:00
|
|
|
|
],
|
|
|
|
|
|
daos: [
|
|
|
|
|
|
UserDao,
|
|
|
|
|
|
PackDao,
|
|
|
|
|
|
TestDao,
|
|
|
|
|
|
PaymentDao,
|
|
|
|
|
|
SubscriptionDao,
|
|
|
|
|
|
TaskDao,
|
|
|
|
|
|
PromoCodeDao,
|
|
|
|
|
|
DiscountDao,
|
|
|
|
|
|
StatisticsDao,
|
|
|
|
|
|
AchievementDao,
|
2025-12-14 20:36:05 +00:00
|
|
|
|
WordStatisticsDao,
|
|
|
|
|
|
AuditDao,
|
2025-12-13 13:27:05 +00:00
|
|
|
|
],
|
|
|
|
|
|
)
|
|
|
|
|
|
class AppDatabase extends _$AppDatabase {
|
|
|
|
|
|
AppDatabase(super.e);
|
|
|
|
|
|
|
|
|
|
|
|
@override
|
2025-12-18 20:40:48 +00:00
|
|
|
|
int get schemaVersion => 3;
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
/// 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,
|
|
|
|
|
|
);
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
final connection = PgDatabase(
|
|
|
|
|
|
endpoint: endpoint,
|
|
|
|
|
|
settings: pg.ConnectionSettings(
|
|
|
|
|
|
sslMode: useSsl ? pg.SslMode.require : pg.SslMode.disable,
|
|
|
|
|
|
connectTimeout: const Duration(seconds: 10),
|
|
|
|
|
|
),
|
|
|
|
|
|
);
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
return AppDatabase(connection);
|
|
|
|
|
|
}
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
/// 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',
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
@override
|
|
|
|
|
|
MigrationStrategy get migration => MigrationStrategy(
|
|
|
|
|
|
onCreate: (Migrator m) async {
|
|
|
|
|
|
print('Creating database schema...');
|
|
|
|
|
|
await m.createAll();
|
|
|
|
|
|
print('Database schema created successfully');
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Создать индексы для оптимизации
|
|
|
|
|
|
await _createIndexes();
|
|
|
|
|
|
},
|
|
|
|
|
|
onUpgrade: (Migrator m, int from, int to) async {
|
|
|
|
|
|
print('Migrating database from version $from to $to');
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-17 00:41:47 +00:00
|
|
|
|
// Миграция с версии 1 на 2: обновление структуры тестов
|
|
|
|
|
|
if (from < 2) {
|
|
|
|
|
|
await _migrateToV2(m);
|
|
|
|
|
|
}
|
2025-12-18 20:40:48 +00:00
|
|
|
|
|
|
|
|
|
|
// Миграция с версии 2 на 3: добавление telegram в users
|
|
|
|
|
|
if (from < 3) {
|
|
|
|
|
|
await _migrateToV3(m);
|
|
|
|
|
|
}
|
2025-12-13 13:27:05 +00:00
|
|
|
|
},
|
|
|
|
|
|
beforeOpen: (details) async {
|
|
|
|
|
|
print('Opening database connection...');
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Проверка подключения
|
|
|
|
|
|
final result = await customSelect('SELECT 1 as test').getSingle();
|
|
|
|
|
|
print('Database connection successful: ${result.data}');
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Включить foreign key constraints
|
|
|
|
|
|
await customStatement('SET CONSTRAINTS ALL IMMEDIATE');
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
/// Создание индексов для оптимизации запросов
|
|
|
|
|
|
Future<void> _createIndexes() async {
|
|
|
|
|
|
print('Creating indexes...');
|
2025-12-20 18:26:15 +00:00
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Users indexes
|
2025-12-20 18:26:15 +00:00
|
|
|
|
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_telegram ON users(telegram) WHERE telegram 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',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// UserDatas indexes
|
2025-12-20 18:26:15 +00:00
|
|
|
|
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)',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Auth indexes
|
2025-12-20 18:26:15 +00:00
|
|
|
|
await customStatement(
|
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_tokens_user_id ON tokens(user_id)',
|
|
|
|
|
|
);
|
2025-12-13 22:07:44 +00:00
|
|
|
|
// Индекс на expires без предиката (NOW() не может быть использована в предикате индекса)
|
2025-12-20 18:26:15 +00:00
|
|
|
|
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',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// CardPacks indexes
|
2025-12-20 18:26:15 +00:00
|
|
|
|
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")',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// GameCards indexes
|
2025-12-20 18:26:15 +00:00
|
|
|
|
await customStatement(
|
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_cards_original ON game_cards(original)',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// UserPacks indexes
|
2025-12-20 18:26:15 +00:00
|
|
|
|
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)',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Payments indexes
|
2025-12-20 18:26:15 +00:00
|
|
|
|
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)',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Subscriptions indexes
|
2025-12-20 18:26:15 +00:00
|
|
|
|
await customStatement(
|
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_subscriptions_user_id ON user_subscriptions(user_id)',
|
|
|
|
|
|
);
|
2025-12-13 22:07:44 +00:00
|
|
|
|
// Индекс на finish без предиката (NOW() не может быть использована в предикате индекса)
|
2025-12-20 18:26:15 +00:00
|
|
|
|
await customStatement(
|
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_subscriptions_finish ON user_subscriptions(finish)',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// Tests indexes
|
2025-12-20 18:26:15 +00:00
|
|
|
|
await customStatement(
|
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_test_questions_test_id ON test_questions(test_id)',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// StudySessions indexes
|
2025-12-20 18:26:15 +00:00
|
|
|
|
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)',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
// PromoCodes indexes
|
2025-12-20 18:26:15 +00:00
|
|
|
|
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)',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-13 13:27:05 +00:00
|
|
|
|
print('Indexes created successfully');
|
|
|
|
|
|
}
|
2025-12-17 00:41:47 +00:00
|
|
|
|
|
|
|
|
|
|
/// Миграция с версии 1 на версию 2
|
|
|
|
|
|
/// Обновление структуры тестов: добавление новых колонок и очистка старых данных
|
|
|
|
|
|
Future<void> _migrateToV2(Migrator m) async {
|
2025-12-20 18:26:15 +00:00
|
|
|
|
print(
|
|
|
|
|
|
'Starting migration to v2: updating test questions and statistics...',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2025-12-17 00:41:47 +00:00
|
|
|
|
try {
|
|
|
|
|
|
// 1. Добавляем новые колонки в test_questions
|
|
|
|
|
|
print('Adding new columns to test_questions...');
|
|
|
|
|
|
await customStatement(
|
|
|
|
|
|
'ALTER TABLE test_questions '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS order_index INTEGER DEFAULT 0, '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS word TEXT DEFAULT \'\', '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS answer TEXT DEFAULT \'\', '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS options TEXT DEFAULT \'[]\', '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS ui_data TEXT DEFAULT \'{}\'',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 2. Удаляем все старые вопросы (soft delete)
|
|
|
|
|
|
print('Soft-deleting all existing test questions...');
|
2026-01-06 21:33:51 +00:00
|
|
|
|
final now = PgDateTime(DateTime.now());
|
2026-01-08 13:02:47 +00:00
|
|
|
|
await (update(
|
|
|
|
|
|
testQuestions,
|
|
|
|
|
|
)..where((tq) => tq.isDeleted.equals(false))).write(
|
2026-01-06 21:33:51 +00:00
|
|
|
|
TestQuestionsCompanion(
|
|
|
|
|
|
isDeleted: const Value(true),
|
|
|
|
|
|
deletedAt: Value(now),
|
|
|
|
|
|
updatedAt: Value(now),
|
|
|
|
|
|
),
|
2025-12-17 00:41:47 +00:00
|
|
|
|
);
|
2025-12-20 18:26:15 +00:00
|
|
|
|
print(
|
|
|
|
|
|
'All old questions marked as deleted. Create new questions via admin panel.',
|
|
|
|
|
|
);
|
2025-12-17 00:41:47 +00:00
|
|
|
|
|
|
|
|
|
|
// 3. Удаляем старую колонку body
|
|
|
|
|
|
print('Dropping old body column...');
|
2025-12-20 18:26:15 +00:00
|
|
|
|
await customStatement(
|
|
|
|
|
|
'ALTER TABLE test_questions DROP COLUMN IF EXISTS body',
|
|
|
|
|
|
);
|
2025-12-17 00:41:47 +00:00
|
|
|
|
|
|
|
|
|
|
// 4. Добавляем новые колонки в test_statistics
|
|
|
|
|
|
print('Adding new columns to test_statistics...');
|
|
|
|
|
|
await customStatement(
|
|
|
|
|
|
'ALTER TABLE test_statistics '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS total_questions INTEGER DEFAULT 0, '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS correct_answers INTEGER DEFAULT 0, '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS incorrect_answers INTEGER DEFAULT 0, '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS skipped_answers INTEGER DEFAULT 0, '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS score_percentage REAL DEFAULT 0.0, '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS time_spent_seconds INTEGER, '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS question_results TEXT DEFAULT \'[]\', '
|
|
|
|
|
|
'ADD COLUMN IF NOT EXISTS metadata TEXT DEFAULT \'{}\'',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 5. Удаляем всю старую статистику (она будет создаваться заново)
|
|
|
|
|
|
print('Deleting old test statistics...');
|
2026-01-06 21:33:51 +00:00
|
|
|
|
await (delete(testStatistics)).go();
|
2025-12-20 18:26:15 +00:00
|
|
|
|
print(
|
|
|
|
|
|
'All old statistics deleted. New statistics will be collected automatically.',
|
|
|
|
|
|
);
|
2025-12-17 00:41:47 +00:00
|
|
|
|
|
|
|
|
|
|
// 6. Удаляем старую колонку results
|
|
|
|
|
|
print('Dropping old results column...');
|
2025-12-20 18:26:15 +00:00
|
|
|
|
await customStatement(
|
|
|
|
|
|
'ALTER TABLE test_statistics DROP COLUMN IF EXISTS results',
|
|
|
|
|
|
);
|
2025-12-17 00:41:47 +00:00
|
|
|
|
|
|
|
|
|
|
print('Migration to v2 completed successfully!');
|
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
|
print('Error during migration to v2: $e');
|
|
|
|
|
|
print('Stack trace: $stackTrace');
|
|
|
|
|
|
rethrow;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-18 20:40:48 +00:00
|
|
|
|
|
|
|
|
|
|
/// Миграция с версии 2 на версию 3
|
|
|
|
|
|
/// Добавление поля telegram в users и перенос старых telegram-логинов из email
|
|
|
|
|
|
Future<void> _migrateToV3(Migrator m) async {
|
|
|
|
|
|
print('Starting migration to v3: adding telegram to users...');
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
await customStatement(
|
|
|
|
|
|
'ALTER TABLE users ADD COLUMN IF NOT EXISTS telegram TEXT',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Раньше Telegram-username сохранялся в email. Переносим "похожие на username"
|
|
|
|
|
|
// значения в telegram и очищаем email.
|
|
|
|
|
|
await customStatement(
|
|
|
|
|
|
'UPDATE users '
|
|
|
|
|
|
'SET telegram = email, email = NULL '
|
|
|
|
|
|
'WHERE (telegram IS NULL OR telegram = \'\') '
|
|
|
|
|
|
'AND email IS NOT NULL AND email != \'\' '
|
|
|
|
|
|
'AND POSITION(\'@\' IN email) = 0',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// Индексы на existing db
|
|
|
|
|
|
await customStatement(
|
|
|
|
|
|
'CREATE INDEX IF NOT EXISTS idx_users_telegram ON users(telegram) WHERE telegram IS NOT NULL',
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
print('Migration to v3 completed successfully!');
|
|
|
|
|
|
} catch (e, stackTrace) {
|
|
|
|
|
|
print('Error during migration to v3: $e');
|
|
|
|
|
|
print('Stack trace: $stackTrace');
|
|
|
|
|
|
rethrow;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-13 13:27:05 +00:00
|
|
|
|
}
|