mnemo_cards/mnemo_cards_backend/lib/tests/test_manager.dart
2025-11-16 14:25:27 +03:00

230 lines
7.2 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 '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();
});
}
}