mnemo_cards/mnemo_cards_backend/lib/database/tables/tests.dart
2026-01-24 19:07:29 +03:00

134 lines
5.8 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 '../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()();
// Время, когда тест перестанет быть доступен (soft limit)
// Когда expiresAt наступает, тест soft-delete (доступен только по прямой ссылке)
Column<PgDateTime> get expiresAt =>
customType(PgTypes.timestampWithTimezone).nullable()();
// Audit
Column<PgDateTime> get createdAt =>
customType(PgTypes.timestampWithTimezone).withDefault(now())();
Column<PgDateTime> get updatedAt =>
customType(PgTypes.timestampWithTimezone).withDefault(now())();
BoolColumn get isDeleted =>
boolean().customConstraint('NOT NULL DEFAULT FALSE')();
// PostgreSQL использует нативный BOOLEAN
Column<PgDateTime> get deletedAt =>
customType(PgTypes.timestampWithTimezone).nullable()();
@override
Set<Column> 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<PgDateTime> get createdAt =>
customType(PgTypes.timestampWithTimezone).withDefault(now())();
Column<PgDateTime> get updatedAt =>
customType(PgTypes.timestampWithTimezone).withDefault(now())();
BoolColumn get isDeleted =>
boolean().customConstraint('NOT NULL DEFAULT FALSE')();
Column<PgDateTime> get deletedAt =>
customType(PgTypes.timestampWithTimezone).nullable()();
@override
Set<Column> 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<Column> 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<PgDateTime> get completedAt =>
customType(PgTypes.timestampWithTimezone).withDefault(now())();
// Audit
Column<PgDateTime> get createdAt =>
customType(PgTypes.timestampWithTimezone).withDefault(now())();
Column<PgDateTime> get updatedAt =>
customType(PgTypes.timestampWithTimezone).withDefault(now())();
@override
Set<Column> get primaryKey => {id};
}
// Конвертеры импортированы из converters.dart