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 _customCreationTestData = []; TestManager(this._packDtoConverter); Future _testStatisticsDto( UserModel user, int testId) async { return (await user.userData.value?.testsStatistics .filter() .test((q) => q.idEqualTo(testId)) .findFirst()) ?.toDto(); } Future fetchTest(String id, UserModel user) async { return (await isar.testModels.get(int.parse(id)))?.toDto( statistics: await _testStatisticsDto(user, int.parse(id)), ); } Future> 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> fetchPackTests( UserModel user, CardPackModel model, ) async { final pack = (await isar.cardPackModels.get(model.id!))!; final Iterable 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 _updateGeneratedTestsIfRequired(CardPackModel model) async { final generated = model.tests.where((t) => t.version == 'generated'); if (generated.isEmpty) { await updateGeneratedTests(model); return true; } return false; } Future refreshCustomCreationTestsData() async { final dir = Directory('${PackManager.assetsDirectory.path}/tests/'); List data = []; if (await dir.exists()) { final files = dir.listSync().whereType().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 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 addTest(TestDto testDto) async { await isar.writeTxn(() async { final questions = []; 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(); }); } }