mnemo_cards/mnemo_cards_backend/lib/database/tables/tests.dart

135 lines
5.8 KiB
Dart
Raw Normal View History

2025-12-13 13:27:05 +00:00
import 'package:drift/drift.dart';
2025-12-13 23:35:14 +00:00
import 'package:drift_postgres/drift_postgres.dart';
2025-12-13 20:55:50 +00:00
import '../converters.dart' show generateUuid, JsonMapConverter;
2025-12-13 13:27:05 +00:00
import 'packs.dart';
/// Таблица Tests - тесты
class Tests extends Table {
2025-12-13 23:35:14 +00:00
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
2025-12-20 18:26:15 +00:00
TextColumn get id =>
text().withDefault(const CustomExpression('gen_random_uuid()::text'))();
2025-12-13 13:27:05 +00:00
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()();
2026-01-24 15:18:58 +00:00
// Время, когда тест перестанет быть доступен (soft limit)
// Когда expiresAt наступает, тест soft-delete (доступен только по прямой ссылке)
Column<PgDateTime> get expiresAt =>
customType(PgTypes.timestampWithTimezone).nullable()();
2025-12-20 18:26:15 +00:00
2025-12-13 13:27:05 +00:00
// Audit
2025-12-20 18:26:15 +00:00
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()();
2025-12-13 20:55:50 +00:00
@override
Set<Column> get primaryKey => {id};
2025-12-13 13:27:05 +00:00
}
/// Таблица TestQuestions - вопросы тестов
class TestQuestions extends Table {
2025-12-13 23:35:14 +00:00
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
2025-12-20 18:26:15 +00:00
TextColumn get id =>
text().withDefault(const CustomExpression('gen_random_uuid()::text'))();
TextColumn get testId =>
text().references(Tests, #id, onDelete: KeyAction.cascade)();
2025-12-17 00:41:47 +00:00
// Порядок вопроса в тесте
2025-12-20 18:26:15 +00:00
IntColumn get orderIndex => integer().withDefault(const Constant(0))();
2025-12-13 13:27:05 +00:00
// Тип вопроса (enum as string)
TextColumn get questionType => text()();
2025-12-20 18:26:15 +00:00
2025-12-17 00:41:47 +00:00
// Ключевые поля вынесены из JSON
2025-12-20 18:26:15 +00:00
TextColumn get word => text()(); // Слово/фраза для изучения
TextColumn get answer => text()(); // Правильный ответ (ID кнопки)
2025-12-17 00:41:47 +00:00
// Варианты ответов (JSON array кнопок)
// [{"id": "btn1", "text": "apple", "image": null}, ...]
2025-12-20 18:26:15 +00:00
TextColumn get options => text().withDefault(const Constant('[]'))();
2025-12-17 00:41:47 +00:00
// UI данные (image, text, audio, template для input_buttons)
// {"image": "url", "text": "Question?", "audio": "url", "template": "___"}
2025-12-20 18:26:15 +00:00
TextColumn get uiData => text().withDefault(const Constant('{}'))();
2025-12-13 13:27:05 +00:00
// Audit
2025-12-20 18:26:15 +00:00
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()();
2025-12-13 20:55:50 +00:00
@override
Set<Column> get primaryKey => {id};
2025-12-13 13:27:05 +00:00
}
/// Таблица TestPackRelations - связь Tests с CardPacks (many-to-many)
class TestPackRelations extends Table {
2025-12-20 18:26:15 +00:00
TextColumn get testId =>
text().references(Tests, #id, onDelete: KeyAction.cascade)();
TextColumn get packId =>
text().references(CardPacks, #id, onDelete: KeyAction.cascade)();
2025-12-13 13:27:05 +00:00
@override
Set<Column> get primaryKey => {testId, packId};
}
/// Таблица TestStatistics - статистика прохождения тестов
class TestStatistics extends Table {
2025-12-13 23:35:14 +00:00
// ID как String, но фактически UUID (генерируется PostgreSQL на стороне БД)
2025-12-20 18:26:15 +00:00
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)();
2025-12-17 00:41:47 +00:00
// Сводные метрики (вынесены для быстрого доступа и аналитики)
2025-12-20 18:26:15 +00:00
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))();
2025-12-17 00:41:47 +00:00
// Процент правильных ответов (0-100)
2025-12-20 18:26:15 +00:00
RealColumn get scorePercentage => real().withDefault(const Constant(0.0))();
2025-12-17 00:41:47 +00:00
// Время прохождения в секундах
2025-12-20 18:26:15 +00:00
IntColumn get timeSpentSeconds => integer().nullable()();
2025-12-17 00:41:47 +00:00
// Детальные результаты по каждому вопросу (JSON)
// [{"questionId": "123", "correct": true, "timeSpent": 5, "answer": "a1"}]
2025-12-20 18:26:15 +00:00
TextColumn get questionResults => text().withDefault(const Constant('[]'))();
2025-12-17 00:41:47 +00:00
// Дополнительные данные (по типам вопросов, streak и т.д.)
2025-12-20 18:26:15 +00:00
TextColumn get metadata => text().withDefault(const Constant('{}'))();
2025-12-17 00:41:47 +00:00
// Когда завершен тест
2025-12-20 18:26:15 +00:00
Column<PgDateTime> get completedAt =>
customType(PgTypes.timestampWithTimezone).withDefault(now())();
2025-12-13 13:27:05 +00:00
// Audit
2025-12-20 18:26:15 +00:00
Column<PgDateTime> get createdAt =>
customType(PgTypes.timestampWithTimezone).withDefault(now())();
Column<PgDateTime> get updatedAt =>
customType(PgTypes.timestampWithTimezone).withDefault(now())();
2025-12-13 20:55:50 +00:00
@override
Set<Column> get primaryKey => {id};
2025-12-13 13:27:05 +00:00
}
// Конвертеры импортированы из converters.dart