mnemo_cards/mnemo_cards_backend/lib/tests/test_manager.dart

231 lines
7.2 KiB
Dart
Raw Normal View History

2025-11-16 11:25:27 +00:00
import 'dart:convert';
import 'dart:io';
import 'package:injectable/injectable.dart';
import 'package:isar/isar.dart';
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
import 'package:mnemo_cards_backend/packs/pack_manager.dart';
import 'package:mnemo_cards_backend/tests/generators/models/creation_test_data.dart';
import 'package:mnemo_cards_backend/tests/test_extension.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
import '../main.dart';
import '../packs/pack_dto_converter.dart';
import 'generators/question_generators/input_buttons_question_generator.dart';
import 'generators/pack_test_generator.dart';
import 'generators/question_generators/simple_question_generator.dart';
@lazySingleton
class TestManager {
final PackDtoConverter _packDtoConverter;
List<CreationTestData> _customCreationTestData = [];
TestManager(this._packDtoConverter);
Future<TestStatisticsDto?> _testStatisticsDto(
UserModel user, int testId) async {
return (await user.userData.value?.testsStatistics
.filter()
.test((q) => q.idEqualTo(testId))
.findFirst())
?.toDto();
}
Future<TestDto?> fetchTest(String id, UserModel user) async {
return (await isar.testModels.get(int.parse(id)))?.toDto(
statistics: await _testStatisticsDto(user, int.parse(id)),
);
}
Future<List<TestDto>> availableTests(UserModel userModel) async {
final tests = (await isar.testModels.where().findAll());
final dtos = await Future.wait(tests.map(
(test) async => test.toDto(
questions: [],
statistics: await _testStatisticsDto(userModel, test.id!),
),
));
return dtos.toList();
}
Future<List<TestDto>> fetchPackTests(
UserModel user,
CardPackModel model,
) async {
final pack = (await isar.cardPackModels.get(model.id!))!;
final Iterable<TestModel> testModels;
if (await _updateGeneratedTestsIfRequired(pack)) {
testModels = (await isar.cardPackModels.get(model.id!))!.tests;
} else {
testModels = pack.tests;
}
return (await Future.wait(
testModels.map(
(e) async => e.toDto(
statistics: await _testStatisticsDto(user, e.id!),
),
),
))
.toList();
}
Future<bool> _updateGeneratedTestsIfRequired(CardPackModel model) async {
final generated = model.tests.where((t) => t.version == 'generated');
if (generated.isEmpty) {
await updateGeneratedTests(model);
return true;
}
return false;
}
Future<void> refreshCustomCreationTestsData() async {
final dir = Directory('${PackManager.assetsDirectory.path}/tests/');
List<CreationTestData> data = [];
if (await dir.exists()) {
final files = dir.listSync().whereType<File>().toList();
await Future.wait(
files.map(
(file) => file
.readAsString()
.then((s) => data.add(
s.decode(CreationTestData.fromJson),
))
.catchError(
(err) {
print('Error when loading custom test data: ${file.path}');
print(err);
},
),
),
);
}
_customCreationTestData = data;
}
Future<void> updateGeneratedTests(CardPackModel model) async {
final oldGenerated =
model.tests.where((t) => t.version == 'generated').toList();
final cardPackDto = _packDtoConverter.toDto(model);
final packCreationTestData = CreationTestData.fromCardPackDto(cardPackDto);
final customCreationTestData = _customCreationTestData.where(
(data) => data.packId == cardPackDto.id,
);
final generator = PackTestGenerator(packCreationTestData);
final testDtos = [
await generator.generate(
name: 'Мини тест',
multiply: 1.0,
ratios: {
TestQuestionType.simple: 1.0,
TestQuestionType.input_buttons: 0.1,
},
),
for (final customData in customCreationTestData)
await PackTestGenerator(
customData,
color: customData.color ?? cardPackDto.color,
generators: {
TestQuestionType.simple: SimpleQuestionGenerator(
customData,
possibleTypes: {
SimpleQuestionType.original_translation,
SimpleQuestionType.translation_original,
SimpleQuestionType.audio_translation,
},
),
},
).generate(
name: customData.title,
multiply: 1.0,
ratios: {
TestQuestionType.simple: 1.0,
},
),
await PackTestGenerator(
packCreationTestData,
generators: {
TestQuestionType.simple: SimpleQuestionGenerator.images(
packCreationTestData,
),
TestQuestionType.input_buttons: InputButtonsQuestionGenerator.images(
packCreationTestData,
),
},
).generate(
name: 'Тест с картинками',
multiply: 2.0,
ratios: {
TestQuestionType.simple: 1.0,
TestQuestionType.input_buttons: 0.25,
},
),
await generator.generate(
name: 'Тест на написание',
multiply: 2.0,
ratios: {
TestQuestionType.input_buttons: 1.0,
},
),
await generator.generate(
name: 'Большой тест',
multiply: 1.5,
ratios: {
TestQuestionType.simple: 1.0,
TestQuestionType.input_buttons: 1.0,
},
),
];
isar.writeTxn(() async {
for (final test in testDtos) {
final testModel = test.toEmptyModel();
final questionModels = test.questions
.map(
(e) => TestQuestionModel(
questionType: e.questionType,
body: jsonEncode(e.toJson()),
),
)
.toList();
await isar.testQuestionModels.putAll(questionModels);
testModel.questions.addAll(questionModels);
testModel.packs.add(model);
await isar.testModels.put(testModel);
await testModel.packs.save();
await testModel.questions.save();
}
if (oldGenerated.isNotEmpty) {
for (final test in oldGenerated) {
await test.questions.load();
final questions =
test.questions.map((e) => e.id).whereNotNull().toList();
if (questions.isNotEmpty) {
await isar.testQuestionModels.deleteAll(questions);
}
await isar.testModels.delete(test.id!);
}
}
});
}
Future<void> addTest(TestDto testDto) async {
await isar.writeTxn(() async {
final questions = <TestQuestionModel>[];
for (final q in testDto.questions.where((q) => q.body != null)) {
final model = TestQuestionModel(
id: q.id,
questionType: q.questionType,
body: q.body!,
);
await isar.testQuestionModels.put(model);
questions.add(model);
}
final testModel = testDto.toEmptyModel();
await isar.testModels.put(testModel);
await testModel.questions
..addAll(questions)
..save();
});
}
}