import 'package:drift/drift.dart'; import 'package:drift_postgres/drift_postgres.dart'; import '../converters.dart' show generateUuid, JsonMapConverter; import 'packs.dart'; /// Таблица Tests - тесты class Tests extends Table { // ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД) TextColumn get id => text().withDefault(const CustomExpression('gen_random_uuid()::text'))(); TextColumn get name => text()(); TextColumn get color => text().nullable()(); TextColumn get cover => text().nullable()(); TextColumn get version => text().nullable()(); TextColumn get time => text().nullable()(); TextColumn get timeSubtitle => text().nullable()(); // Audit Column get createdAt => customType(PgTypes.timestampWithTimezone).withDefault(now())(); Column get updatedAt => customType(PgTypes.timestampWithTimezone).withDefault(now())(); BoolColumn get isDeleted => boolean().customConstraint('NOT NULL DEFAULT FALSE')(); // PostgreSQL использует нативный BOOLEAN Column get deletedAt => customType(PgTypes.timestampWithTimezone).nullable()(); @override Set get primaryKey => {id}; } /// Таблица TestQuestions - вопросы тестов class TestQuestions extends Table { // ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД) TextColumn get id => text().withDefault(const CustomExpression('gen_random_uuid()::text'))(); TextColumn get testId => text().references(Tests, #id, onDelete: KeyAction.cascade)(); // Порядок вопроса в тесте IntColumn get orderIndex => integer().withDefault(const Constant(0))(); // Тип вопроса (enum as string) TextColumn get questionType => text()(); // Ключевые поля вынесены из JSON TextColumn get word => text()(); // Слово/фраза для изучения TextColumn get answer => text()(); // Правильный ответ (ID кнопки) // Варианты ответов (JSON array кнопок) // [{"id": "btn1", "text": "apple", "image": null}, ...] TextColumn get options => text().withDefault(const Constant('[]'))(); // UI данные (image, text, audio, template для input_buttons) // {"image": "url", "text": "Question?", "audio": "url", "template": "___"} TextColumn get uiData => text().withDefault(const Constant('{}'))(); // Audit Column get createdAt => customType(PgTypes.timestampWithTimezone).withDefault(now())(); Column get updatedAt => customType(PgTypes.timestampWithTimezone).withDefault(now())(); BoolColumn get isDeleted => boolean().customConstraint('NOT NULL DEFAULT FALSE')(); Column get deletedAt => customType(PgTypes.timestampWithTimezone).nullable()(); @override Set get primaryKey => {id}; } /// Таблица TestPackRelations - связь Tests с CardPacks (many-to-many) class TestPackRelations extends Table { TextColumn get testId => text().references(Tests, #id, onDelete: KeyAction.cascade)(); TextColumn get packId => text().references(CardPacks, #id, onDelete: KeyAction.cascade)(); @override Set get primaryKey => {testId, packId}; } /// Таблица TestStatistics - статистика прохождения тестов class TestStatistics extends Table { // ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД) TextColumn get id => text().withDefault(const CustomExpression('gen_random_uuid()::text'))(); TextColumn get userId => text()(); // Reference to Users, but not FK to avoid circular deps TextColumn get testId => text().references(Tests, #id, onDelete: KeyAction.cascade)(); // Сводные метрики (вынесены для быстрого доступа и аналитики) IntColumn get totalQuestions => integer().withDefault(const Constant(0))(); IntColumn get correctAnswers => integer().withDefault(const Constant(0))(); IntColumn get incorrectAnswers => integer().withDefault(const Constant(0))(); IntColumn get skippedAnswers => integer().withDefault(const Constant(0))(); // Процент правильных ответов (0-100) RealColumn get scorePercentage => real().withDefault(const Constant(0.0))(); // Время прохождения в секундах IntColumn get timeSpentSeconds => integer().nullable()(); // Детальные результаты по каждому вопросу (JSON) // [{"questionId": "123", "correct": true, "timeSpent": 5, "answer": "a1"}] TextColumn get questionResults => text().withDefault(const Constant('[]'))(); // Дополнительные данные (по типам вопросов, streak и т.д.) TextColumn get metadata => text().withDefault(const Constant('{}'))(); // Когда завершен тест Column get completedAt => customType(PgTypes.timestampWithTimezone).withDefault(now())(); // Audit Column get createdAt => customType(PgTypes.timestampWithTimezone).withDefault(now())(); Column get updatedAt => customType(PgTypes.timestampWithTimezone).withDefault(now())(); @override Set get primaryKey => {id}; } // Конвертеры импортированы из converters.dart