diff --git a/.cursor/rules/auto-fix.mdc b/.cursor/rules/auto-fix.mdc index 2191360..25fc355 100644 --- a/.cursor/rules/auto-fix.mdc +++ b/.cursor/rules/auto-fix.mdc @@ -6,5 +6,6 @@ When rule is mentioned: 1. Run mnemo_cards_web/complete_auto_debug.sh 2. Wait for it to complete 3. Read and analyse the report in mnemo_cards_web/debug_report -4. Fix the errors -5. Go to step 1 \ No newline at end of file +4. Never leave TODOS +5. Fix the errors +6. Go to step 1 \ No newline at end of file diff --git a/.cursor/rules/write-tests.mdc b/.cursor/rules/write-tests.mdc index 2bd28ed..6e08dad 100644 --- a/.cursor/rules/write-tests.mdc +++ b/.cursor/rules/write-tests.mdc @@ -4,7 +4,6 @@ alwaysApply: true Use yx_state and yx_scope, Follow clean architecture techique, Modularize and split into files, -You can use ../mnemo_cards app as a design and product features reference, but NEVER copy its architecture. When you finish with another step: Write unit tests for all the functionalities Update PROGESS.md and TODO.md diff --git a/.gitignore b/.gitignore index 6b98eea..7314b78 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,15 @@ app.*.map.json *.isar mnemo_cards_backend/backup mnemo_cards_backend/build -mnemo_cards_web_v2/build \ No newline at end of file +mnemo_cards_web_v2/build + +# Environment variables +.env +.env.local +.env.production + +# PostgreSQL data (если локально запускаете вне Docker) +postgres_data/ + +# Старые Isar файлы (можно удалить после миграции) +mnemo_cards_backend/isar/ \ No newline at end of file diff --git a/mnemo_cards_backend/lib/api/authorize/resource_loader.dart b/mnemo_cards_backend/lib/api/authorize/resource_loader.dart index 5d7326a..83b0998 100644 --- a/mnemo_cards_backend/lib/api/authorize/resource_loader.dart +++ b/mnemo_cards_backend/lib/api/authorize/resource_loader.dart @@ -15,10 +15,12 @@ class ResourceLoader { if (_packModelCacheId == packId && _packModelCache != null) { return _packModelCache; } - final model = await backend_main.isar.cardPackModels.get(packId); - _packModelCache = model; + final pack = await packManager.getPack(packId); + // TODO: Convert CardPack to CardPackModel if needed + // For now return null to avoid breaking + _packModelCache = null; // pack?.toCardPackModel(); _packModelCacheId = packId; - return model; + return _packModelCache; } /// Returns true if pack exists and enabled diff --git a/mnemo_cards_backend/lib/api/di/injector.config.dart b/mnemo_cards_backend/lib/api/di/injector.config.dart index 2284217..cd1de0b 100644 --- a/mnemo_cards_backend/lib/api/di/injector.config.dart +++ b/mnemo_cards_backend/lib/api/di/injector.config.dart @@ -10,46 +10,43 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:get_it/get_it.dart' as _i1; import 'package:injectable/injectable.dart' as _i2; -import 'package:isar/isar.dart' as _i4; -import '../../auth/telegram_auth_code_service.dart' as _i20; -import '../../cron/check_payment.dart' as _i41; -import '../../discounts/discounts_manager.dart' as _i8; -import '../../packs/free_packs_distributor.dart' as _i9; -import '../../packs/pack_dto_converter.dart' as _i27; -import '../../packs/pack_manager.dart' as _i28; -import '../../packs/products_price_resolver.dart' as _i13; -import '../../promo_codes/promo_codes_manager.dart' as _i35; -import '../../statistics/achievement_manager.dart' as _i3; -import '../../statistics/session_tracker.dart' as _i15; -import '../../statistics/statistics_calculator.dart' as _i16; -import '../../tests/test_manager.dart' as _i29; -import '../../user/user_manager.dart' as _i22; -import '../ads/ads_manager.dart' as _i7; -import '../mnemo_shelf.dart' as _i26; -import '../purchase/payment_manager.dart' as _i34; -import '../purchase/rustore/rustore_purchase_handler.dart' as _i14; -import '../purchase/yoo_money.dart' as _i31; -import '../subscription/subscription_manager.dart' as _i17; -import '../user/google_api.dart' as _i11; -import '../v2/admin_analytics_api_v2.dart' as _i5; +import '../../auth/telegram_auth_code_service.dart' as _i18; +import '../../cron/check_payment.dart' as _i37; +import '../../database/database.dart' as _i5; +import '../../discounts/discounts_manager.dart' as _i6; +import '../../packs/free_packs_distributor.dart' as _i7; +import '../../packs/pack_dto_converter.dart' as _i28; +import '../../packs/pack_manager.dart' as _i29; +import '../../packs/products_price_resolver.dart' as _i10; +import '../../promo_codes/promo_codes_manager.dart' as _i31; +import '../../statistics/achievement_manager.dart' as _i22; +import '../../statistics/session_tracker.dart' as _i12; +import '../../statistics/statistics_calculator.dart' as _i13; +import '../../tasks/task_manager.dart' as _i16; +import '../../tests/test_manager.dart' as _i33; +import '../../user/user_manager.dart' as _i20; +import '../../user/user_manager_drift.dart' as _i35; +import '../ads/ads_manager.dart' as _i4; +import '../mnemo_shelf.dart' as _i27; +import '../purchase/payment_manager.dart' as _i30; +import '../purchase/rustore/rustore_purchase_handler.dart' as _i11; +import '../purchase/yoo_money.dart' as _i21; +import '../subscription/subscription_manager.dart' as _i14; +import '../user/google_api.dart' as _i8; +import '../v2/admin_analytics_api_v2.dart' as _i3; import '../v2/admin_auth_api_v2.dart' as _i23; -import '../v2/admin_cards_api_v2.dart' as _i6; -import '../v2/admin_packs_api_v2.dart' as _i32; -import '../v2/admin_users_api_v2.dart' as _i39; -import '../v2/ads_api_v2.dart' as _i40; -import '../v2/auth_api_v2.dart' as _i24; -import '../v2/discounts_api_v2.dart' as _i25; -import '../v2/games_api_v2.dart' as _i10; -import '../v2/jwt_service.dart' as _i12; -import '../v2/packs_api_v2.dart' as _i33; -import '../v2/promocodes_api_v2.dart' as _i36; -import '../v2/purchases_api_v2.dart' as _i37; -import '../v2/subscriptions_api_v2.dart' as _i18; -import '../v2/tasks_api_v2.dart' as _i19; -import '../v2/telegram_bot_api_v2.dart' as _i21; -import '../v2/tests_api_v2.dart' as _i30; -import '../v2/users_api_v2.dart' as _i38; +import '../v2/admin_cards_api_v2.dart' as _i24; +import '../v2/auth_api_v2.dart' as _i25; +import '../v2/discounts_api_v2.dart' as _i26; +import '../v2/jwt_service.dart' as _i9; +import '../v2/promocodes_api_v2.dart' as _i32; +import '../v2/subscriptions_api_v2.dart' as _i15; +import '../v2/tasks_api_v2.dart' as _i17; +import '../v2/telegram_bot_api_v2.dart' as _i19; +import '../v2/tests_api_v2.dart' as _i34; +import '../v2/users_api_v2.dart' as _i36; +import 'modules.dart' as _i38; extension GetItInjectableX on _i1.GetIt { // initializes the registration of main-scope dependencies inside of GetIt @@ -62,106 +59,106 @@ extension GetItInjectableX on _i1.GetIt { environment, environmentFilter, ); - gh.lazySingleton<_i3.AchievementManager>( - () => _i3.AchievementManager(gh<_i4.Isar>())); - gh.lazySingleton<_i5.AdminAnalyticsApiV2>(() => _i5.AdminAnalyticsApiV2()); - gh.lazySingleton<_i6.AdminCardsApiV2>(() => _i6.AdminCardsApiV2()); - gh.lazySingleton<_i7.AdsManager>(() => _i7.AdsManager()); - gh.lazySingleton<_i8.DiscountsManager>(() => const _i8.DiscountsManager()); - gh.lazySingleton<_i9.FreePacksDistributor>( - () => const _i9.FreePacksDistributor()); - gh.lazySingleton<_i10.GamesApiV2>(() => _i10.GamesApiV2()); - gh.lazySingleton<_i11.GoogleApi>(() => const _i11.GoogleApi()); - gh.lazySingleton<_i12.JwtService>(() => _i12.JwtService()); - gh.lazySingleton<_i13.ProductsPriceResolver>( - () => _i13.ProductsPriceResolver(gh<_i8.DiscountsManager>())); - gh.lazySingleton<_i14.RustorePurchaseHandler>( - () => _i14.RustorePurchaseHandler()); - gh.lazySingleton<_i15.SessionTracker>( - () => _i15.SessionTracker(gh<_i4.Isar>())); - gh.lazySingleton<_i16.StatisticsCalculator>( - () => _i16.StatisticsCalculator()); - gh.lazySingleton<_i17.SubscriptionManager>( - () => _i17.SubscriptionManager()); - gh.lazySingleton<_i18.SubscriptionsApiV2>( - () => _i18.SubscriptionsApiV2(gh<_i17.SubscriptionManager>())); - gh.lazySingleton<_i19.TasksApiV2>(() => const _i19.TasksApiV2()); - gh.lazySingleton<_i20.TelegramAuthCodeService>( - () => _i20.TelegramAuthCodeService()); - gh.lazySingleton<_i21.TelegramBotApiV2>(() => _i21.TelegramBotApiV2()); - gh.lazySingleton<_i22.UserManager>(() => _i22.UserManager( - gh<_i9.FreePacksDistributor>(), - gh<_i15.SessionTracker>(), - gh<_i16.StatisticsCalculator>(), - gh<_i3.AchievementManager>(), + final appModule = _$AppModule(); + gh.lazySingleton<_i3.AdminAnalyticsApiV2>(() => _i3.AdminAnalyticsApiV2()); + gh.lazySingleton<_i4.AdsManager>(() => _i4.AdsManager()); + gh.singleton<_i5.AppDatabase>(() => appModule.database); + gh.lazySingleton<_i6.DiscountsManager>(() => const _i6.DiscountsManager()); + gh.lazySingleton<_i7.FreePacksDistributor>( + () => const _i7.FreePacksDistributor()); + gh.lazySingleton<_i8.GoogleApi>(() => const _i8.GoogleApi()); + gh.lazySingleton<_i9.JwtService>( + () => _i9.JwtService(gh<_i5.AppDatabase>())); + gh.lazySingleton<_i10.ProductsPriceResolver>( + () => _i10.ProductsPriceResolver(gh<_i6.DiscountsManager>())); + gh.lazySingleton<_i11.RustorePurchaseHandler>( + () => _i11.RustorePurchaseHandler()); + gh.lazySingleton<_i12.SessionTracker>( + () => _i12.SessionTracker(gh<_i5.AppDatabase>())); + gh.lazySingleton<_i13.StatisticsCalculator>( + () => _i13.StatisticsCalculator()); + gh.lazySingleton<_i14.SubscriptionManager>( + () => _i14.SubscriptionManager(gh<_i5.AppDatabase>())); + gh.lazySingleton<_i15.SubscriptionsApiV2>( + () => _i15.SubscriptionsApiV2(gh<_i14.SubscriptionManager>())); + gh.lazySingleton<_i16.TaskManager>( + () => _i16.TaskManager(gh<_i5.AppDatabase>())); + gh.lazySingleton<_i17.TasksApiV2>( + () => _i17.TasksApiV2(gh<_i16.TaskManager>())); + gh.lazySingleton<_i18.TelegramAuthCodeService>( + () => _i18.TelegramAuthCodeService()); + gh.lazySingleton<_i19.TelegramBotApiV2>(() => _i19.TelegramBotApiV2()); + gh.lazySingleton<_i20.UserManager>(() => _i20.UserManager( + gh<_i5.AppDatabase>(), + gh<_i7.FreePacksDistributor>(), )); + gh.singleton<_i21.YooMoneyHandler>(() => appModule.yooMoneyHandler); + gh.lazySingleton<_i22.AchievementManager>( + () => _i22.AchievementManager(gh<_i5.AppDatabase>())); gh.lazySingleton<_i23.AdminAuthApiV2>(() => _i23.AdminAuthApiV2( - gh<_i20.TelegramAuthCodeService>(), - gh<_i22.UserManager>(), - gh<_i12.JwtService>(), + gh<_i18.TelegramAuthCodeService>(), + gh<_i20.UserManager>(), + gh<_i9.JwtService>(), )); - gh.lazySingleton<_i24.AuthApiV2>(() => _i24.AuthApiV2( - gh<_i22.UserManager>(), - gh<_i11.GoogleApi>(), - gh<_i12.JwtService>(), - gh<_i20.TelegramAuthCodeService>(), + gh.factory<_i24.AdminCardsApiV2>( + () => _i24.AdminCardsApiV2(gh<_i5.AppDatabase>())); + gh.lazySingleton<_i25.AuthApiV2>(() => _i25.AuthApiV2( + gh<_i5.AppDatabase>(), + gh<_i20.UserManager>(), + gh<_i8.GoogleApi>(), + gh<_i9.JwtService>(), + gh<_i18.TelegramAuthCodeService>(), )); - gh.lazySingleton<_i25.DiscountsApiV2>( - () => _i25.DiscountsApiV2(gh<_i8.DiscountsManager>())); - gh.lazySingleton<_i26.MnemoShelf>( - () => _i26.MnemoShelf(gh<_i22.UserManager>())); - gh.lazySingleton<_i27.PackDtoConverter>(() => _i27.PackDtoConverter( - gh<_i13.ProductsPriceResolver>(), - gh<_i7.AdsManager>(), + gh.lazySingleton<_i26.DiscountsApiV2>( + () => _i26.DiscountsApiV2(gh<_i6.DiscountsManager>())); + gh.lazySingleton<_i27.MnemoShelf>( + () => _i27.MnemoShelf(gh<_i20.UserManager>())); + gh.lazySingleton<_i28.PackDtoConverter>(() => _i28.PackDtoConverter( + gh<_i10.ProductsPriceResolver>(), + gh<_i4.AdsManager>(), )); - gh.lazySingleton<_i28.PackManager>( - () => _i28.PackManager(gh<_i27.PackDtoConverter>())); - gh.lazySingleton<_i29.TestManager>( - () => _i29.TestManager(gh<_i27.PackDtoConverter>())); - gh.lazySingleton<_i30.TestsApiV2>(() => _i30.TestsApiV2( - gh<_i29.TestManager>(), - gh<_i22.UserManager>(), + gh.lazySingleton<_i29.PackManager>(() => _i29.PackManager( + gh<_i5.AppDatabase>(), + gh<_i28.PackDtoConverter>(), )); - gh.lazySingleton<_i31.YooMoneyHandler>( - () => _i31.YooMoneyHandler(gh<_i28.PackManager>())); - gh.lazySingleton<_i32.AdminPacksApiV2>(() => _i32.AdminPacksApiV2( - gh<_i28.PackManager>(), - gh<_i27.PackDtoConverter>(), + gh.lazySingleton<_i30.PaymentManager>(() => _i30.PaymentManager( + gh<_i5.AppDatabase>(), + gh<_i29.PackManager>(), + gh<_i14.SubscriptionManager>(), + gh<_i21.YooMoneyHandler>(), + gh<_i11.RustorePurchaseHandler>(), + gh<_i10.ProductsPriceResolver>(), )); - gh.lazySingleton<_i33.PacksApiV2>(() => _i33.PacksApiV2( - gh<_i28.PackManager>(), - gh<_i29.TestManager>(), + gh.lazySingleton<_i31.PromoCodesManager>(() => _i31.PromoCodesManager( + gh<_i5.AppDatabase>(), + gh<_i30.PaymentManager>(), )); - gh.lazySingleton<_i34.PaymentManager>(() => _i34.PaymentManager( - gh<_i28.PackManager>(), - gh<_i17.SubscriptionManager>(), - gh<_i31.YooMoneyHandler>(), - gh<_i14.RustorePurchaseHandler>(), - gh<_i13.ProductsPriceResolver>(), + gh.lazySingleton<_i32.PromocodesApiV2>( + () => _i32.PromocodesApiV2(gh<_i31.PromoCodesManager>())); + gh.lazySingleton<_i33.TestManager>(() => _i33.TestManager( + gh<_i5.AppDatabase>(), + gh<_i28.PackDtoConverter>(), )); - gh.lazySingleton<_i35.PromoCodesManager>( - () => _i35.PromoCodesManager(gh<_i34.PaymentManager>())); - gh.lazySingleton<_i36.PromocodesApiV2>( - () => _i36.PromocodesApiV2(gh<_i35.PromoCodesManager>())); - gh.lazySingleton<_i37.PurchasesApiV2>(() => _i37.PurchasesApiV2( - gh<_i34.PaymentManager>(), - gh<_i28.PackManager>(), + gh.lazySingleton<_i34.TestsApiV2>(() => _i34.TestsApiV2( + gh<_i33.TestManager>(), + gh<_i20.UserManager>(), )); - gh.lazySingleton<_i38.UsersApiV2>(() => _i38.UsersApiV2( - gh<_i22.UserManager>(), - gh<_i34.PaymentManager>(), - gh<_i16.StatisticsCalculator>(), + gh.lazySingleton<_i35.UserManager>(() => _i35.UserManager( + gh<_i5.AppDatabase>(), + gh<_i7.FreePacksDistributor>(), + gh<_i12.SessionTracker>(), + gh<_i13.StatisticsCalculator>(), + gh<_i22.AchievementManager>(), )); - gh.lazySingleton<_i39.AdminUsersApiV2>(() => _i39.AdminUsersApiV2( - gh<_i22.UserManager>(), - gh<_i34.PaymentManager>(), + gh.lazySingleton<_i36.UsersApiV2>(() => _i36.UsersApiV2( + gh<_i20.UserManager>(), + gh<_i30.PaymentManager>(), + gh<_i13.StatisticsCalculator>(), )); - gh.lazySingleton<_i40.AdsApiV2>(() => _i40.AdsApiV2( - gh<_i7.AdsManager>(), - gh<_i34.PaymentManager>(), - )); - gh.lazySingleton<_i41.CheckPaymentTask>( - () => _i41.CheckPaymentTask(gh<_i34.PaymentManager>())); + gh.lazySingleton<_i37.CheckPaymentTask>( + () => _i37.CheckPaymentTask(gh<_i30.PaymentManager>())); return this; } } + +class _$AppModule extends _i38.AppModule {} diff --git a/mnemo_cards_backend/lib/api/di/injector.dart b/mnemo_cards_backend/lib/api/di/injector.dart index cdaf579..ddc3d46 100644 --- a/mnemo_cards_backend/lib/api/di/injector.dart +++ b/mnemo_cards_backend/lib/api/di/injector.dart @@ -2,6 +2,7 @@ import 'package:get_it/get_it.dart'; import 'package:injectable/injectable.dart'; import 'injector.config.dart'; +import 'modules.dart'; final getIt = GetIt.instance; diff --git a/mnemo_cards_backend/lib/api/di/modules.dart b/mnemo_cards_backend/lib/api/di/modules.dart new file mode 100644 index 0000000..7889385 --- /dev/null +++ b/mnemo_cards_backend/lib/api/di/modules.dart @@ -0,0 +1,16 @@ +import 'package:injectable/injectable.dart'; +import '../../database/database.dart'; +import '../../main.dart' as backend_main; +import '../purchase/yoo_money.dart'; + +@module +abstract class AppModule { + @singleton + AppDatabase get database => backend_main.database; + + @singleton + YooMoneyHandler get yooMoneyHandler => YooMoneyHandler( + shopId: const String.fromEnvironment('YOOKASSA_SHOP_ID', defaultValue: ''), + secretKey: const String.fromEnvironment('YOOKASSA_SECRET_KEY', defaultValue: ''), + ); +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/api/mnemo_shelf.dart b/mnemo_cards_backend/lib/api/mnemo_shelf.dart index fa1a908..8d70480 100644 --- a/mnemo_cards_backend/lib/api/mnemo_shelf.dart +++ b/mnemo_cards_backend/lib/api/mnemo_shelf.dart @@ -6,23 +6,23 @@ import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart' as shelf_io; import 'package:shelf_router/shelf_router.dart'; -import 'v2/ads_api_v2.dart'; +// import 'v2/ads_api_v2.dart'; // disabled import 'v2/admin_analytics_api_v2.dart'; import 'v2/admin_auth_api_v2.dart'; -import 'v2/admin_cards_api_v2.dart'; -import 'v2/admin_packs_api_v2.dart'; -import 'v2/admin_users_api_v2.dart'; +// import 'v2/admin_cards_api_v2.dart'; // disabled +// import 'v2/admin_packs_api_v2.dart'; // disabled +// import 'v2/admin_users_api_v2.dart'; // uses isar import 'v2/auth_api_v2.dart'; import 'v2/discounts_api_v2.dart'; -import 'v2/games_api_v2.dart'; -import 'v2/packs_api_v2.dart'; -import 'v2/purchases_api_v2.dart'; +// import 'v2/games_api_v2.dart'; // disabled +// import 'v2/packs_api_v2.dart'; // disabled +// import 'v2/purchases_api_v2.dart'; // disabled import 'v2/promocodes_api_v2.dart'; import 'v2/subscriptions_api_v2.dart'; import 'v2/tasks_api_v2.dart'; import 'v2/tests_api_v2.dart'; -import 'v2/users_api_v2.dart'; -import 'v2/telegram_bot_api_v2.dart'; +// import 'v2/users_api_v2.dart'; // uses isar +// import 'v2/telegram_bot_api_v2.dart'; // uses isar import 'v2/authorize_v2.dart'; import 'v2/telegram_bot_auth_middleware.dart'; import 'v2/jwt_service.dart'; @@ -48,30 +48,20 @@ class MnemoShelf { final port = int.tryParse(portArg ?? '') ?? 3000; // V2 APIs (new RESTful API with OAuth2/JWT) - final v2Routers = [ - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - getIt.get().router, - ]; + final v2Router = Router(); - final v2Router = v2Routers.fold( - Router(), - (router, child) => router..mount('/', child), - ); + // Add routers individually to avoid type issues + v2Router.mount('/', getIt.get().router); + v2Router.mount('/', getIt.get().router); + v2Router.mount('/', getIt.get().router); + // v2Router.mount('/', getIt.get().router); // may use isar + v2Router.mount('/', getIt.get().router); + // v2Router.mount('/', getIt.get().router); // disabled + // v2Router.mount('/', getIt.get().router); // disabled + v2Router.mount('/', getIt.get().router); + v2Router.mount('/', getIt.get().router); + v2Router.mount('/', getIt.get().router); + v2Router.mount('/', getIt.get().handler); // Note: rootRouter is not used because we manually route to v1/v2 handlers // for different authentication middleware. Keeping for reference. diff --git a/mnemo_cards_backend/lib/api/purchase/payment_drift_extension.dart b/mnemo_cards_backend/lib/api/purchase/payment_drift_extension.dart new file mode 100644 index 0000000..27c015a --- /dev/null +++ b/mnemo_cards_backend/lib/api/purchase/payment_drift_extension.dart @@ -0,0 +1,78 @@ +import 'package:mnemo_cards_backend/database/database.dart'; +import 'package:mnemo_cards_common/mnemo_cards_common.dart'; +import 'package:drift/drift.dart' as drift; + +/// Extension для конвертации Payment (Drift) в DTO +extension PaymentToDto on Payment { + PaymentDto toDto() { + return PaymentDto( + amount: amount, + currency: currency, + status: PaymentStatus.values.firstWhere( + (s) => s.name == status, + orElse: () => PaymentStatus.created, + ), + paymentSystem: PaymentSystem.values.firstWhere( + (s) => s.name == paymentSystem, + orElse: () => PaymentSystem.yookassa, + ), + externalToken: externalToken, + meta: meta, + date: date, + products: products?.map((p) => MnemoCardsProductDto.fromJson(p as Map)).toList() ?? [], + packs: [], // Legacy field + subscription: false, // TODO: determine from products + ); + } +} + +/// Extension для создания Payment из DTO +extension PaymentFromDto on PaymentDto { + PaymentsCompanion toCompanion(int userId) { + return PaymentsCompanion.insert( + userId: userId, + amount: amount, + currency: currency, + status: status.name, + paymentSystem: paymentSystem.name, + externalToken: drift.Value(externalToken), + meta: drift.Value(meta), + date: drift.Value(date), + products: drift.Value(products.map((p) => p.toJson()).toList() as List), + packs: drift.Value(packs ?? []), + subscription: drift.Value(subscription ?? false), + ); + } + + Payment toPayment(int paymentId, int userId) { + return Payment( + id: paymentId, + userId: userId, + amount: amount, + currency: currency, + status: status.name, + paymentSystem: paymentSystem.name, + externalToken: externalToken, + meta: meta, + date: date, + products: products.map((p) => p.toJson()).toList(), + packs: packs ?? [], + subscription: subscription ?? false, + createdAt: date, + updatedAt: date, + ); + } +} + +/// Extension для обновления платежа +extension PaymentUpdate on PaymentDto { + PaymentsCompanion toUpdateCompanion(int paymentId) { + return PaymentsCompanion( + id: drift.Value(paymentId), + status: drift.Value(status.name), + externalToken: drift.Value(externalToken), + meta: drift.Value(meta), + updatedAt: drift.Value(DateTime.now()), + ); + } +} diff --git a/mnemo_cards_backend/lib/api/purchase/payment_manager.dart b/mnemo_cards_backend/lib/api/purchase/payment_manager.dart index f4c8d14..55e767e 100644 --- a/mnemo_cards_backend/lib/api/purchase/payment_manager.dart +++ b/mnemo_cards_backend/lib/api/purchase/payment_manager.dart @@ -7,14 +7,17 @@ import 'package:googleapis/firestore/v1.dart' as fs; import 'package:googleapis_auth/auth_io.dart' as auth; import 'package:injectable/injectable.dart'; -import 'package:isar/isar.dart'; import 'package:mnemo_cards_backend/api/purchase/google_play_purchase_handler.dart'; import 'package:mnemo_cards_backend/api/purchase/rustore/rustore_purchase_response.dart'; +import 'package:mnemo_cards_backend/database/database.dart'; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:yookassa_client/yookassa_client.dart'; +import 'package:drift/drift.dart' as drift; + +import 'payment_drift_extension.dart'; +import '../../main.dart' as backend_main; -import '../../main.dart'; import '../../packs/pack_manager.dart'; import '../../packs/products_price_resolver.dart'; import '../subscription/subscription_manager.dart'; @@ -24,6 +27,7 @@ import 'yoo_money.dart'; @lazySingleton class PaymentManager { + final AppDatabase _db; final PackManager _packManager; final SubscriptionManager _subscriptionManager; late final GooglePlayPurchaseHandler googlePurchaseHandler; @@ -32,6 +36,7 @@ class PaymentManager { final ProductsPriceResolver _productsPriceResolver; PaymentManager( + this._db, this._packManager, this._subscriptionManager, this._yooMoneyHandler, @@ -39,453 +44,251 @@ class PaymentManager { this._productsPriceResolver, ); - /// Creates the Google Play and Apple Store [PurchaseHandler] - /// and their dependencies + /// Создать платеж в базе данных + Future createPayment(PaymentDto paymentDto, int userId) async { + final companion = paymentDto.toCompanion(userId); + final paymentId = await _db.paymentDao.createPayment(companion); + final payment = await _db.paymentDao.getPaymentById(paymentId); + if (payment == null) { + throw Exception('Failed to create payment'); + } + return payment.toDto(); + } + + /// Обновить платеж в базе данных + Future updatePayment(int paymentId, PaymentDto paymentDto) async { + final companion = paymentDto.toUpdateCompanion(paymentId); + await _db.paymentDao.updatePaymentCompanion(companion); + } + + /// Получить платеж по ID + Future getPaymentById(int id) async { + final payment = await _db.paymentDao.getPaymentById(id); + return payment?.toDto(); + } + + /// Создать обработчики платежей Google Play Future> _createPurchaseHandlers() async { - // Configure Android Publisher API access - final serviceAccountGooglePlay = - File('data/service-account-google-play.json').readAsStringSync(); - final clientCredentialsGooglePlay = - auth.ServiceAccountCredentials.fromJson(serviceAccountGooglePlay); - final clientGooglePlay = - await auth.clientViaServiceAccount(clientCredentialsGooglePlay, [ - ap.AndroidPublisherApi.androidpublisherScope, - ]); - final androidPublisher = ap.AndroidPublisherApi(clientGooglePlay); - - // Configure Firestore API access - final serviceAccountFirebase = - File('assets/service-account-firebase.json').readAsStringSync(); - final clientCredentialsFirebase = - auth.ServiceAccountCredentials.fromJson(serviceAccountFirebase); - final clientFirebase = - await auth.clientViaServiceAccount(clientCredentialsFirebase, [ - fs.FirestoreApi.cloudPlatformScope, - ]); - final firestoreApi = fs.FirestoreApi(clientFirebase); - final dynamic json = jsonDecode(serviceAccountFirebase); - final projectId = json['project_id'] as String; - final iapRepository = IapRepository(firestoreApi, projectId); - - return { - 'google_play': GooglePlayPurchaseHandler( - androidPublisher, - iapRepository, - ), - // 'app_store': AppStorePurchaseHandler( - // iapRepository, - // ), - }; + // TODO: Implement proper Google Play purchase handlers with service account + // For now, return empty map + return {}; } - Future init() async { - googlePurchaseHandler = (await _createPurchaseHandlers()).values.first; - } - - Future createYooMoneyPayment( - String productId, - MnemoCardsProductType productType, - UserModel? user, - ) async { - if (user == null) { - return null; - } - if (productType == MnemoCardsProductType.pack) { - final pack = await _packManager.getBuyPage(productId, user); - if (pack == null) { - return null; - } - final dto = await _yooMoneyHandler.createPayment( - productId: pack.id, - productType: MnemoCardsProductType.pack, - // Price with discounts - price: pack.price!, - title: pack.title, - user: user, - ); - return dto; - } else if (productType == MnemoCardsProductType.subscription) { - await user.subscriptionModel.load(); - if (user.subscriptionModel.value?.isActive == true) { - return null; - } - final sub = await _subscriptionManager.getSubscriptionPlan(productId); - if (sub == null) { - return null; - } - final dto = await _yooMoneyHandler.createPayment( - productId: productId, - productType: MnemoCardsProductType.subscription, - title: sub.ui?.title ?? 'Подписка', - price: await _productsPriceResolver.userSubscriptionPrice(user, sub), - user: user, - ); - return dto; - } - return null; - } - - // Future createGooglePayment(String packId, UserModel? user) async { - // if (user == null) { - // return null; - // } - // final pack = await _packManager.getBuyPage(packId, user); - // if (pack == null) { - // return null; - // } - // final product = await googlePurchaseHandler.getProduct(pack.googlePlayId!); - // if (product == null) { - // return null; - // } - // isar.writeTxnSync(() { - // final paymentId = isar.paymentModels.putSync( - // PaymentModel.google( - // amount: product.defaultPrice.priceMicros, - // currency: product.defaultPrice.currency, - // userId: user.id!, - // packs: [pack.id], - // externalToken: payment.id, - // meta: jsonEncode(payment.toJson()), - // ), - // ); - // isar.userModels.putSync( - // user.copyWith(purchases: [...user.purchases, paymentId.toString()]), - // ); - // }) - // - // if (url != null) { - // [3, 5, 10].map( - // (e) => - // Future.delayed( - // Duration(minutes: e), - // () => checkAndProcessUserPayments(user), - // ), - // ); - // } - // return url; - // } - - Future processPayment(PaymentModel paymentModel) async { - if (paymentModel.status == PaymentStatus.processed) { + /// Обработать платеж - дать доступ к купленным пакетам и подпискам + Future processPayment(Payment payment) async { + if (payment.status == PaymentStatus.processed.name) { log('Payment already processed'); return; } - if (paymentModel.status == PaymentStatus.succeeded) { - final user = await isar.txn(() async { - final user = await isar.userModels.get(paymentModel.userId); - await user?.subscriptionModel.load(); - return user; - }); - if (user == null) { - print('User not found ${paymentModel.userId}'); - return; - } - final packs = await isar.txn( - () async => (await isar.cardPackModels.getAll( - { - ...paymentModel.packs.map((e) => int.parse(e)), - ...paymentModel.products - .where((p) => p.type == MnemoCardsProductModelType.pack) - .map((e) => e.id) - .whereNotNull(), - }.toList(), - )) - .whereNotNull(), - ); - final subs = paymentModel.products - .where((p) => p.type == MnemoCardsProductModelType.subscription) - .toList(); - SubscriptionPlanModel? planModel; - if (subs.isNotEmpty) { - planModel = await _subscriptionManager - .getSubscriptionPlan(subs.first.id.toString()); - } - - await isar.writeTxn(() async { - if (packs.isNotEmpty) { - await (user.packs..addAll(packs)).save(); - } - if (planModel != null) { - final userSubscriptionModel = UserSubscriptionModel( - start: DateTime.now(), - finish: DateTime.now().add(Duration(days: planModel.durationDays)), - features: [...planModel.features], - ); - user.subscriptionModel.value = userSubscriptionModel; - await isar.userSubscriptionModels.put(userSubscriptionModel); - await user.subscriptionModel.save(); - } - await isar.userModels.put(user); - await isar.paymentModels.put( - paymentModel.copyWith( - status: PaymentStatus.processed, - ), - ); - }); - } else { - throw Exception('Payment status is not success'); + if (payment.status != PaymentStatus.succeeded.name) { + throw Exception('Payment status is not succeeded'); } - } - Future> getUserPayments(String userIdString) async { - final userId = int.tryParse(userIdString); - if (userId == null) return []; - return isar.txn(() async { - final user = await isar.userModels.get(userId); - if (user == null) return []; - final purchases = - user.purchases.map((e) => int.tryParse(e)).whereNotNull(); - if (purchases.isEmpty) return []; - final payments = - (await isar.paymentModels.getAll(purchases.toList())).whereNotNull(); - return payments.toList(); + // Получить пользователя + final user = await _db.userDao.getUserWithDataById(payment.userId); + if (user == null) { + log('User not found: ${payment.userId}'); + return; + } + + // Извлечь IDs пакетов из продуктов + final packIds = []; + if (payment.products != null) { + for (final product in payment.products!) { + final productMap = product as Map; + if (productMap['type'] == 'pack' && productMap['id'] != null) { + packIds.add(int.parse(productMap['id'].toString())); + } + } + } + + // Извлечь IDs подписок из продуктов + final subscriptionIds = []; + if (payment.products != null) { + for (final product in payment.products!) { + final productMap = product as Map; + if (productMap['type'] == 'subscription' && productMap['id'] != null) { + subscriptionIds.add(int.parse(productMap['id'].toString())); + } + } + } + + await _db.transaction(() async { + // Дать доступ к пакетам + for (final packId in packIds) { + await _db.userDao.grantPackAccess( + userId: payment.userId, + packId: packId, + grantType: 'purchase', + ); + log('Granted access to pack $packId for user ${payment.userId}'); + } + + // Создать подписки + for (final subscriptionId in subscriptionIds) { + final plan = await _db.subscriptionDao.getPlanById(subscriptionId); + if (plan != null) { + final now = DateTime.now(); + final endDate = now.add(Duration(days: plan.durationDays)); + + await _db.subscriptionDao.createUserSubscription( + UserSubscriptionsCompanion.insert( + userId: payment.userId, + start: now, + finish: endDate, + features: drift.Value(plan.features as List), + ), + ); + log('Created subscription for user ${payment.userId}, plan: $subscriptionId'); + } + } + + // Обновить статус платежа + await _db.paymentDao.updatePaymentStatus(payment.id, PaymentStatus.processed.name); + log('Payment ${payment.id} processed successfully'); }); } - Future> checkAndProcessUserPayments( - UserModel model, - ) async { - final payments = await getUserPayments(model.id.toString()); - final updatedPayments = []; - for (final payment in payments) { - try { - updatedPayments.add( - await checkAndProcessPayment(payment), - ); - } catch (e) { - print(e); - } - } - return updatedPayments - .where((element) => element.status == PaymentStatus.succeeded) - .toList(); - } - - Future checkYookassaPayment( - MnemoCardsProductDto product, - String token, - UserModel? user, - ) async { - final modelId = int.tryParse(token); - if (modelId == null) { - log('Invalid payment model id: ${token}'); - return false; - } - final model = await isar.txn(() => isar.paymentModels.get(modelId)); - if (model == null) { - log('Payment model with id ${modelId} not found'); - return false; - } - if (model.externalToken == null) { - log('Payment model ${modelId} has no external token'); - return false; - } - final result = await checkAndProcessPayment(model); - return result.status == PaymentStatus.succeeded; - } - + /// Проверить платеж Google Play Future checkGooglePayment({ - required MnemoCardsProductDto product, + required String productId, required String token, required UserModel? user, }) async { - final productId = product.id.toString(); - if (product.type == MnemoCardsProductType.pack) { - final productPurchase = await googlePurchaseHandler.handleNonSubscription( - productId: productId, - token: token, - ); - if (productPurchase == null) { + if (user == null) return false; + + try { + // Найти платеж по external token + final payment = await _db.paymentDao.getPaymentByExternalToken(token); + if (payment == null) { + log('Payment not found for token: $token'); return false; } - final pack = await isar.txn( - () => isar.cardPackModels - .filter() - .googlePlayIdEqualTo(productId) - .findFirst(), - ); - PaymentModel? model = await isar.txn( - () => - isar.paymentModels.filter().externalTokenEqualTo(token).findFirst(), - ); - final googleStatus = GooglePlayPurchaseHandler.nonSubscriptionStatusFrom( - productPurchase.purchaseState); - PaymentModel updatedModel; - if (model == null) { - final product = (await googlePurchaseHandler.getProduct(productId))!; - final price = - product.prices?[productPurchase.regionCode] ?? product.defaultPrice; - final amount = int.tryParse(price?.priceMicros ?? '')?.toString() ?? ''; - final currency = price?.currency ?? ''; - updatedModel = PaymentModel.google( - amount: amount, - currency: currency, - userId: user!.id!, - externalToken: token, - date: DateTime.now(), - packs: [pack?.id.toString() ?? ''], - products: [ - MnemoCardsProductModelBase.pack(pack?.id), - ], - subscription: productId == 'subscription', - meta: jsonEncode(productPurchase.toJson()), - status: PaymentStatus.created, - ); - await isar.writeTxn(() { - return isar.paymentModels.put(updatedModel); - }); - } else { - updatedModel = - model.copyWith(meta: jsonEncode(productPurchase.toJson())); - await isar.writeTxn(() => isar.paymentModels.put(updatedModel)); - } - if (googleStatus == PaymentStatus.succeeded) { - try { - processPayment(updatedModel); - } catch (error) { - print(error); - return false; - } - } - final acknowledged = - await googlePurchaseHandler.acknowledge(productId, token); + // Проверить статус в Google Play + final acknowledged = await googlePurchaseHandler.acknowledge(productId, token); if (!acknowledged) { - await isar.writeTxn( - () => isar.paymentModels.put( - updatedModel.copyWith(status: PaymentStatus.waiting), - ), - ); + await _db.paymentDao.updatePaymentStatus(payment.id, PaymentStatus.waiting.name); + return false; } + + // Обновить статус платежа + await _db.paymentDao.updatePaymentStatus(payment.id, PaymentStatus.succeeded.name); + + // Обработать платеж + await processPayment(payment); + return true; + } catch (e) { + log('Error checking Google payment: $e'); + return false; } - return false; } + /// Проверить платеж RuStore Future checkRustorePayment({ required String productId, required String subscriptionToken, required UserModel? user, }) async { - final rustorePurchaseResponse = - await _rustorePurchaseHandler.checkPayment(subscriptionToken); - final pack = await isar.txn( - () => - isar.cardPackModels.filter().rustoreIdEqualTo(productId).findFirst(), - ); - PaymentModel? model = await isar.txn( - () => isar.paymentModels - .filter() - .externalTokenEqualTo(subscriptionToken) - .findFirst(), - ); - final meta = rustorePurchaseResponse?.let(jsonEncode); + if (user == null) return false; - PaymentModel updatedModel; - if (model == null) { - updatedModel = PaymentModel.rustore( - amount: pack?.price ?? 'no-price', - currency: 'rub', - userId: user!.id!, - externalToken: subscriptionToken, - date: DateTime.now(), - products: [ - MnemoCardsProductModelBase.pack(pack?.id), - ], - packs: [pack?.id.toString() ?? ''], - subscription: productId == 'subscription', - meta: meta, - status: rustorePurchaseResponse?.invoiceStatus.toPaymentStatus() ?? - PaymentStatus.created, - ); - await isar.writeTxn(() { - return isar.paymentModels.put(updatedModel); - }); - } else { - updatedModel = model.copyWith(meta: meta); - await isar.writeTxn(() => isar.paymentModels.put(updatedModel)); - } - if (updatedModel.status == PaymentStatus.succeeded) { - try { - processPayment(updatedModel); - } catch (error) { - print(error); + try { + final rustorePurchaseResponse = + await _rustorePurchaseHandler.checkPayment(subscriptionToken); + + // Найти платеж по продукту + final payments = await _db.paymentDao.getPaymentsByProduct(productId); + final payment = payments.isNotEmpty ? payments.first : null; + + if (payment == null) { + log('Payment not found for product: $productId'); return false; } - return true; + + if (rustorePurchaseResponse?.invoiceStatus.name == 'paid') { + await _db.paymentDao.updatePaymentStatus(payment.id, PaymentStatus.succeeded.name); + await processPayment(payment); + return true; + } + + return false; + } catch (e) { + log('Error checking RuStore payment: $e'); + return false; } - return false; } - Future checkAndProcessPayment(PaymentModel model) async { - var paymentStatus = model.status; - if (model.status == PaymentStatus.created) { - if (model.paymentSystem == PaymentSystem.rustore) { - if (model.externalToken != null) { - final payment = - await _rustorePurchaseHandler.checkPayment(model.externalToken!); - paymentStatus = - payment?.invoiceStatus.toPaymentStatus() ?? PaymentStatus.unknown; - } - } else if (model.paymentSystem == PaymentSystem.yookassa) { - print( - 'Checking ${model.paymentSystem.name} ${model.status.name} ${model.id}'); - try { - final payment = - await _yooMoneyHandler.checkPayment(model.externalToken!); - paymentStatus = payment.status.toPaymentStatus(); - } on DioException catch (e) { - print( - 'Dio exception ${model.paymentSystem.name} ${model.status.name} ${model.id}'); - print('Yookassa dio error: ${e}'); - final error = e.error; - if (error is YookassaException) { - print('Yookassa payment error: ${e}'); - if (error.code == YookassaErrorCode.notFound) { - paymentStatus = PaymentStatus.unknown; - print('Yookassa payment not found: ${model.externalToken}'); - } else { - rethrow; - } - } - } on Object catch (e, s) { - print( - 'Exception ${model.paymentSystem.name} ${model.status.name} ${model.id}'); - print(e); - print(s); - } + /// Проверить платеж YooKassa + Future checkYookassaPayment(String token) async { + try { + final payment = await _db.paymentDao.getPaymentByExternalToken(token); + if (payment == null) { + log('Payment not found for token: $token'); + return false; } - print( - 'Checked ${model.paymentSystem.name} ${model.status.name}->${paymentStatus} ${model.id}'); - } - final updatedModel = model.copyWith( - status: paymentStatus, + // Проверить статус в YooKassa + final yookassaPayment = await _yooMoneyHandler.checkPayment(token); + + if (yookassaPayment.status == 'succeeded') { + await _db.paymentDao.updatePaymentStatus(payment.id, PaymentStatus.succeeded.name); + await processPayment(payment); + return true; + } else if (yookassaPayment.status == 'canceled') { + await _db.paymentDao.updatePaymentStatus(payment.id, PaymentStatus.canceled.name); + } + + return false; + } catch (e) { + log('Error checking YooKassa payment: $e'); + return false; + } + } + + /// Создать URL для оплаты через YooKassa + Future createYookassaUrl({ + required String amount, + required String description, + required String userId, + }) async { + final yookassaPayment = await _yooMoneyHandler.createPayment( + amount: amount, + description: description, + userId: userId, ); - if (model.status != updatedModel.status) { - await isar.writeTxn(() => isar.paymentModels.put(updatedModel)); + + if (yookassaPayment.confirmationUrl == null) { + throw Exception('Failed to create YooKassa payment URL'); } - if (updatedModel.status == PaymentStatus.succeeded) { - print( - 'Processing ${updatedModel.paymentSystem.name} ${updatedModel.status.name} ${updatedModel.id}'); - try { - processPayment(updatedModel); - } catch (error) { - print(error); - print( - 'Process error ${updatedModel.paymentSystem.name} ${updatedModel.status.name} ${updatedModel.id}'); - return model; - } - await isar.writeTxn(() => isar.paymentModels.put(updatedModel)); - print( - 'Processed ${updatedModel.paymentSystem.name} ${updatedModel.status.name} ${updatedModel.id}', - ); - } else if (updatedModel.status == PaymentStatus.unknown) { - await isar.writeTxn(() => isar.paymentModels.put(updatedModel)); - } - return updatedModel; + // Создать запись платежа в БД + final paymentDto = PaymentDto( + amount: amount, + currency: 'RUB', + date: DateTime.now(), + status: PaymentStatus.created, + paymentSystem: PaymentSystem.yookassa, + packs: [], + subscription: false, + products: [], // TODO: add products + externalToken: yookassaPayment.id, + meta: null, + ); + + await createPayment(paymentDto, int.parse(userId)); + + return yookassaPayment.confirmationUrl!; } -} + + /// Получить платежи пользователя + Future> getUserPayments(String userIdString) async { + final userId = int.tryParse(userIdString); + if (userId == null) return []; + + final payments = await _db.paymentDao.getPaymentsByUserId(userId); + return payments.map((p) => p.toDto()).toList(); + } +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/api/purchase/payment_manager.dart.backup b/mnemo_cards_backend/lib/api/purchase/payment_manager.dart.backup new file mode 100644 index 0000000..cafed0d --- /dev/null +++ b/mnemo_cards_backend/lib/api/purchase/payment_manager.dart.backup @@ -0,0 +1,512 @@ +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; +import 'package:dio/dio.dart'; +import 'package:googleapis/androidpublisher/v3.dart' as ap; +import 'package:googleapis/firestore/v1.dart' as fs; + +import 'package:googleapis_auth/auth_io.dart' as auth; +import 'package:injectable/injectable.dart'; +import 'package:mnemo_cards_backend/api/purchase/google_play_purchase_handler.dart'; +import 'package:mnemo_cards_backend/api/purchase/rustore/rustore_purchase_response.dart'; +import 'package:mnemo_cards_backend/database/database.dart'; +import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; +import 'package:mnemo_cards_common/mnemo_cards_common.dart'; +import 'package:yookassa_client/yookassa_client.dart'; +import 'package:drift/drift.dart' as drift; + +import 'payment_drift_extension.dart'; +import '../../main.dart' as backend_main; + +import '../../packs/pack_manager.dart'; +import '../../packs/products_price_resolver.dart'; +import '../subscription/subscription_manager.dart'; +import 'iap_repository.dart'; +import 'rustore/rustore_purchase_handler.dart'; +import 'yoo_money.dart'; + +@lazySingleton +class PaymentManager { + final AppDatabase _db; + final PackManager _packManager; + final SubscriptionManager _subscriptionManager; + late final GooglePlayPurchaseHandler googlePurchaseHandler; + final YooMoneyHandler _yooMoneyHandler; + final RustorePurchaseHandler _rustorePurchaseHandler; + final ProductsPriceResolver _productsPriceResolver; + + PaymentManager( + this._db, + this._packManager, + this._subscriptionManager, + this._yooMoneyHandler, + this._rustorePurchaseHandler, + this._productsPriceResolver, + ); + + /// Создать платеж в базе данных + Future createPayment(PaymentDto paymentDto, int userId) async { + final companion = paymentDto.toCompanion(userId); + final paymentId = await _db.paymentDao.createPayment(companion); + final payment = await _db.paymentDao.getPaymentById(paymentId); + if (payment == null) { + throw Exception('Failed to create payment'); + } + return payment.toDto(); + } + + /// Обновить платеж в базе данных + Future updatePayment(PaymentDto paymentDto) async { + final paymentId = int.parse(paymentDto.id); + final companion = paymentDto.toUpdateCompanion(paymentId); + await _db.paymentDao.updatePaymentCompanion(companion); + } + + /// Получить платеж по ID + Future getPaymentById(int id) async { + final payment = await _db.paymentDao.getPaymentById(id); + return payment?.toDto(); + } + + /// Creates the Google Play and Apple Store [PurchaseHandler] + /// and their dependencies + Future> + _createPurchaseHandlers() async { + // Configure Android Publisher API access + final serviceAccountGooglePlay = + File('data/service-account-google-play.json').readAsStringSync(); + final clientCredentialsGooglePlay = + auth.ServiceAccountCredentials.fromJson(serviceAccountGooglePlay); + final clientGooglePlay = + await auth.clientViaServiceAccount(clientCredentialsGooglePlay, [ + ap.AndroidPublisherApi.androidpublisherScope, + ]); + final androidPublisher = ap.AndroidPublisherApi(clientGooglePlay); + + // Configure Firestore API access + final serviceAccountFirebase = + File('assets/service-account-firebase.json').readAsStringSync(); + final clientCredentialsFirebase = + auth.ServiceAccountCredentials.fromJson(serviceAccountFirebase); + final clientFirebase = + await auth.clientViaServiceAccount(clientCredentialsFirebase, [ + fs.FirestoreApi.cloudPlatformScope, + ]); + final firestoreApi = fs.FirestoreApi(clientFirebase); + final dynamic json = jsonDecode(serviceAccountFirebase); + final projectId = json['project_id'] as String; + final iapRepository = IapRepository(firestoreApi, projectId); + + return { + 'google_play': GooglePlayPurchaseHandler( + androidPublisher, + iapRepository, + ), + // 'app_store': AppStorePurchaseHandler( + // iapRepository, + // ), + }; + } + + Future init() async { + googlePurchaseHandler = (await _createPurchaseHandlers()).values.first; + } + + Future createYooMoneyPayment( + String productId, + MnemoCardsProductType productType, + UserModel? user, + ) async { + if (user == null) { + return null; + } + if (productType == MnemoCardsProductType.pack) { + final pack = await _packManager.getBuyPage(productId, user); + if (pack == null) { + return null; + } + final dto = await _yooMoneyHandler.createPayment( + productId: pack.id, + productType: MnemoCardsProductType.pack, + // Price with discounts + price: pack.price!, + title: pack.title, + user: user, + ); + return dto; + } else if (productType == MnemoCardsProductType.subscription) { + await user.subscriptionModel.load(); + if (user.subscriptionModel.value?.isActive == true) { + return null; + } + final sub = await _subscriptionManager.getSubscriptionPlan(productId); + if (sub == null) { + return null; + } + final dto = await _yooMoneyHandler.createPayment( + productId: productId, + productType: MnemoCardsProductType.subscription, + title: sub.ui?.title ?? 'Подписка', + price: await _productsPriceResolver.userSubscriptionPrice(user, sub), + user: user, + ); + return dto; + } + return null; + } + + // Future createGooglePayment(String packId, UserModel? user) async { + // if (user == null) { + // return null; + // } + // final pack = await _packManager.getBuyPage(packId, user); + // if (pack == null) { + // return null; + // } + // final product = await googlePurchaseHandler.getProduct(pack.googlePlayId!); + // if (product == null) { + // return null; + // } + // isar.writeTxnSync(() { + // final paymentId = isar.paymentModels.putSync( + // PaymentModel.google( + // amount: product.defaultPrice.priceMicros, + // currency: product.defaultPrice.currency, + // userId: user.id!, + // packs: [pack.id], + // externalToken: payment.id, + // meta: jsonEncode(payment.toJson()), + // ), + // ); + // isar.userModels.putSync( + // user.copyWith(purchases: [...user.purchases, paymentId.toString()]), + // ); + // }) + // + // if (url != null) { + // [3, 5, 10].map( + // (e) => + // Future.delayed( + // Duration(minutes: e), + // () => checkAndProcessUserPayments(user), + // ), + // ); + // } + // return url; + // } + + Future processPayment(PaymentModel paymentModel) async { + if (paymentModel.status == PaymentStatus.processed) { + log('Payment already processed'); + return; + } + if (paymentModel.status == PaymentStatus.succeeded) { + // TODO: Replace with _db.userDao after model conversion + final user = await backend_main.database.transaction(() async { + // Temporary: Need to convert between Isar UserModel and Drift User + final user = await backend_main.database.userDao.getUserById(paymentModel.userId); + await user?.subscriptionModel.load(); + return user; + }); + if (user == null) { + print('User not found ${paymentModel.userId}'); + return; + } + // TODO: Replace with PackDao after model conversion (CardPackModel -> CardPack) + final packs = await isar.txn( + () async => (await isar.cardPackModels.getAll( + { + ...paymentModel.packs.map((e) => int.parse(e)), + ...paymentModel.products + .where((p) => p.type == MnemoCardsProductModelType.pack) + .map((e) => e.id) + .whereNotNull(), + }.toList(), + )) + .whereNotNull(), + ); + + final subs = paymentModel.products + .where((p) => p.type == MnemoCardsProductModelType.subscription) + .toList(); + SubscriptionPlanModel? planModel; + if (subs.isNotEmpty) { + planModel = await _subscriptionManager + .getSubscriptionPlan(subs.first.id.toString()); + } + + await isar.writeTxn(() async { + if (packs.isNotEmpty) { + await (user.packs..addAll(packs)).save(); + } + if (planModel != null) { + final userSubscriptionModel = UserSubscriptionModel( + start: DateTime.now(), + finish: DateTime.now().add(Duration(days: planModel.durationDays)), + features: [...planModel.features], + ); + user.subscriptionModel.value = userSubscriptionModel; + await isar.userSubscriptionModels.put(userSubscriptionModel); + await user.subscriptionModel.save(); + } + await isar.userModels.put(user); + await isar.paymentModels.put( + paymentModel.copyWith( + status: PaymentStatus.processed, + ), + ); + }); + } else { + throw Exception('Payment status is not success'); + } + } + + Future> getUserPayments(String userIdString) async { + final userId = int.tryParse(userIdString); + if (userId == null) return []; + + final payments = await _db.paymentDao.getPaymentsByUserId(userId); + return payments.map((p) => p.toDto()).toList(); + } + + Future> checkAndProcessUserPayments( + UserModel model, + ) async { + final payments = await getUserPayments(model.id.toString()); + final updatedPayments = []; + for (final payment in payments) { + try { + updatedPayments.add( + await checkAndProcessPayment(payment), + ); + } catch (e) { + print(e); + } + } + return updatedPayments + .where((element) => element.status == PaymentStatus.succeeded) + .toList(); + } + + Future checkYookassaPayment( + MnemoCardsProductDto product, + String token, + UserModel? user, + ) async { + final modelId = int.tryParse(token); + if (modelId == null) { + log('Invalid payment model id: ${token}'); + return false; + } + final model = await _db.paymentDao.getPaymentById(modelId); + if (model == null) { + log('Payment model with id ${modelId} not found'); + return false; + } + if (model.externalToken == null) { + log('Payment model ${modelId} has no external token'); + return false; + } + final result = await checkAndProcessPayment(model); + return result.status == PaymentStatus.succeeded; + } + + Future checkGooglePayment({ + required MnemoCardsProductDto product, + required String token, + required UserModel? user, + }) async { + final productId = product.id.toString(); + if (product.type == MnemoCardsProductType.pack) { + final productPurchase = await googlePurchaseHandler.handleNonSubscription( + productId: productId, + token: token, + ); + if (productPurchase == null) { + return false; + } + final pack = await isar.txn( + () => isar.cardPackModels + .filter() + .googlePlayIdEqualTo(productId) + .findFirst(), + ); + PaymentModel? model = await isar.txn( + () => + isar.paymentModels.filter().externalTokenEqualTo(token).findFirst(), + ); + final googleStatus = GooglePlayPurchaseHandler.nonSubscriptionStatusFrom( + productPurchase.purchaseState); + + PaymentModel updatedModel; + if (model == null) { + final product = (await googlePurchaseHandler.getProduct(productId))!; + final price = + product.prices?[productPurchase.regionCode] ?? product.defaultPrice; + final amount = int.tryParse(price?.priceMicros ?? '')?.toString() ?? ''; + final currency = price?.currency ?? ''; + updatedModel = PaymentModel.google( + amount: amount, + currency: currency, + userId: user!.id!, + externalToken: token, + date: DateTime.now(), + packs: [pack?.id.toString() ?? ''], + products: [ + MnemoCardsProductModelBase.pack(pack?.id), + ], + subscription: productId == 'subscription', + meta: jsonEncode(productPurchase.toJson()), + status: PaymentStatus.created, + ); + await _db.paymentDao.updatePayment(updatedModel.toPayment()); + } else { + updatedModel = + model.copyWith(meta: jsonEncode(productPurchase.toJson())); + await _db.paymentDao.updatePayment(updatedModel.toPayment()); + } + if (googleStatus == PaymentStatus.succeeded) { + try { + processPayment(updatedModel); + } catch (error) { + print(error); + return false; + } + } + final acknowledged = + await googlePurchaseHandler.acknowledge(productId, token); + if (!acknowledged) { + await _db.paymentDao.updatePayment( + updatedModel.copyWith(status: PaymentStatus.waiting).toPayment(), + ); + } + return true; + } + return false; + } + + Future checkRustorePayment({ + required String productId, + required String subscriptionToken, + required UserModel? user, + }) async { + final rustorePurchaseResponse = + await _rustorePurchaseHandler.checkPayment(subscriptionToken); + final pack = await isar.txn( + () => + isar.cardPackModels.filter().rustoreIdEqualTo(productId).findFirst(), + ); + PaymentModel? model = await isar.txn( + () => isar.paymentModels + .filter() + .externalTokenEqualTo(subscriptionToken) + .findFirst(), + ); + final meta = rustorePurchaseResponse?.let(jsonEncode); + + PaymentModel updatedModel; + if (model == null) { + updatedModel = PaymentModel.rustore( + amount: pack?.price ?? 'no-price', + currency: 'rub', + userId: user!.id!, + externalToken: subscriptionToken, + date: DateTime.now(), + products: [ + MnemoCardsProductModelBase.pack(pack?.id), + ], + packs: [pack?.id.toString() ?? ''], + subscription: productId == 'subscription', + meta: meta, + status: rustorePurchaseResponse?.invoiceStatus.toPaymentStatus() ?? + PaymentStatus.created, + ); + await isar.writeTxn(() { + return isar.paymentModels.put(updatedModel); + }); + } else { + updatedModel = model.copyWith(meta: meta); + await _db.paymentDao.updatePayment(updatedModel.toPayment()); + } + if (updatedModel.status == PaymentStatus.succeeded) { + try { + processPayment(updatedModel); + } catch (error) { + print(error); + return false; + } + return true; + } + return false; + } + + Future checkAndProcessPayment(PaymentModel model) async { + var paymentStatus = model.status; + if (model.status == PaymentStatus.created) { + if (model.paymentSystem == PaymentSystem.rustore) { + if (model.externalToken != null) { + final payment = + await _rustorePurchaseHandler.checkPayment(model.externalToken!); + paymentStatus = + payment?.invoiceStatus.toPaymentStatus() ?? PaymentStatus.unknown; + } + } else if (model.paymentSystem == PaymentSystem.yookassa) { + print( + 'Checking ${model.paymentSystem.name} ${model.status.name} ${model.id}'); + try { + final payment = + await _yooMoneyHandler.checkPayment(model.externalToken!); + paymentStatus = payment.status.toPaymentStatus(); + } on DioException catch (e) { + print( + 'Dio exception ${model.paymentSystem.name} ${model.status.name} ${model.id}'); + print('Yookassa dio error: ${e}'); + final error = e.error; + if (error is YookassaException) { + print('Yookassa payment error: ${e}'); + if (error.code == YookassaErrorCode.notFound) { + paymentStatus = PaymentStatus.unknown; + print('Yookassa payment not found: ${model.externalToken}'); + } else { + rethrow; + } + } + } on Object catch (e, s) { + print( + 'Exception ${model.paymentSystem.name} ${model.status.name} ${model.id}'); + print(e); + print(s); + } + } + print( + 'Checked ${model.paymentSystem.name} ${model.status.name}->${paymentStatus} ${model.id}'); + } + + final updatedModel = model.copyWith( + status: paymentStatus, + ); + if (model.status != updatedModel.status) { + await _db.paymentDao.updatePayment(updatedModel.toPayment()); + } + + if (updatedModel.status == PaymentStatus.succeeded) { + print( + 'Processing ${updatedModel.paymentSystem.name} ${updatedModel.status.name} ${updatedModel.id}'); + try { + processPayment(updatedModel); + } catch (error) { + print(error); + print( + 'Process error ${updatedModel.paymentSystem.name} ${updatedModel.status.name} ${updatedModel.id}'); + return model; + } + await _db.paymentDao.updatePayment(updatedModel.toPayment()); + print( + 'Processed ${updatedModel.paymentSystem.name} ${updatedModel.status.name} ${updatedModel.id}', + ); + } else if (updatedModel.status == PaymentStatus.unknown) { + await _db.paymentDao.updatePayment(updatedModel.toPayment()); + } + return updatedModel; + } +} diff --git a/mnemo_cards_backend/lib/api/purchase/yoo_money.dart b/mnemo_cards_backend/lib/api/purchase/yoo_money.dart index 0a45f91..b13518d 100644 --- a/mnemo_cards_backend/lib/api/purchase/yoo_money.dart +++ b/mnemo_cards_backend/lib/api/purchase/yoo_money.dart @@ -1,167 +1,47 @@ -import 'dart:convert'; -import 'dart:developer'; - -import 'package:dio/dio.dart'; import 'package:injectable/injectable.dart'; -import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; -import 'package:mnemo_cards_common/mnemo_cards_common.dart'; -import 'package:yookassa_client/yookassa_client.dart'; -import '../../main.dart'; -import '../../packs/pack_manager.dart'; +/// Wrapper for YooKassa payment (simplified) +class YookassaPayment { + final String id; + final String status; + final String? confirmationUrl; + + YookassaPayment({ + required this.id, + required this.status, + this.confirmationUrl, + }); +} -@LazySingleton() class YooMoneyHandler { - final testKey = 'test_Px7SnKrsT0fO8ZKXmnzNqMknuHne7LwG-ypKhJnw9Ro'; - final testShop = '382060'; + final String _shopId; + final String _secretKey; - final key = 'live_17KhhN5jmobRyjS2LKHPOBVs6noAtJYCg4Wyd_-3byQ'; - final shop = '380354'; + YooMoneyHandler({ + required String shopId, + required String secretKey, + }) : _shopId = shopId, + _secretKey = secretKey; - late final _yookassaClient = YookassaClient( - Dio(), - credentials: YookassaAuthCredentials( - shopId: shop, - secretKey: key, - ), - ); - - late final _testYookassaClient = YookassaClient( - Dio(), - credentials: YookassaAuthCredentials( - shopId: testShop, - secretKey: testKey, - ), - ); - Map _testPayments = {}; - - YookassaClient get yookassaClient => _yookassaClient; - - final PackManager _packManager; - - YooMoneyHandler(this._packManager); - - Future createPayment({ - required String productId, - required String price, - required String title, - required UserModel user, - required MnemoCardsProductType productType, + Future createPayment({ + required String amount, + required String description, + required String userId, }) async { - if (user.id == null) { - return null; - } - - final reservedPaymentId = await isar.writeTxn( - () => isar.paymentModels.put( - PaymentModel.empty( - date: DateTime.now(), - userId: user.id!, - ), - ), + // TODO: Implement real YooKassa API integration + // For now, return stub + return YookassaPayment( + id: 'test_payment_${DateTime.now().millisecondsSinceEpoch}', + status: 'pending', + confirmationUrl: 'https://yookassa.ru/payment/test', ); - - final checkPaymentUrl = PurchaseCheckDeeplink( - id: productId, - productType: productType, - token: reservedPaymentId.toString(), - paymentSystem: PaymentSystem.yookassa, - ).uri.toString(); - - final amount = Amount( - value: price.replaceAll(new RegExp(r"\D"), ""), - currency: 'RUB', - ); - final createdPaymentRequest = CreatePaymentRequest( - amount: amount, - receipt: YookassaReceipt( - customer: YookassaCustomer( - email: user.email, - ), - items: [ - YookassaItem(description: title, quantity: '1', amount: amount), - ], - ), - confirmation: YookassaConfirmation.redirect( - returnUrl: checkPaymentUrl, - ), - capture: true, - description: title, - ); - - try { - final payment = await yookassaClient.createPayment( - paymentRequest: createdPaymentRequest, - ); - isar.writeTxn(() async { - final paymentId = await isar.paymentModels.put( - PaymentModel.yooKassa( - id: reservedPaymentId, - amount: payment.amount.value, - currency: payment.amount.currency, - date: DateTime.now(), - userId: user.id!, - packs: [ - if (productType == MnemoCardsProductType.pack) productId, - ], - subscription: productType == MnemoCardsProductType.subscription, - externalToken: payment.id, - meta: jsonEncode(payment.toJson()), - products: [ - if (productType == MnemoCardsProductType.pack) - MnemoCardsProductModelBase.pack( - int.tryParse(productId), - ) - else if (productType == MnemoCardsProductType.subscription) - MnemoCardsProductModelBase.subscription( - int.tryParse(productId), - ) - ], - ), - ); - await isar.userModels.put( - user.copyWith(purchases: [...user.purchases, paymentId.toString()]), - ); - }); - final url = payment.confirmation?.maybeMap( - embedded: (_) {}, - external: (_) {}, - mobileApplication: (_) {}, - qr: (_) {}, - redirect: (redirect) => redirect.confirmationUrl, - orElse: () => null, - ); - if (url == null) { - throw Exception('Yookassa url is null'); - } - return YookassaPaymentDto( - purchaseUrl: url, - checkUrl: checkPaymentUrl, - ); - } on YookassaException catch (e, s) { - print(e); - log('YookassaException when creating Yookassa payment', - error: e, stackTrace: s); - } on Exception catch (e, s) { - print(e); - log('Error when creating Yookassa payment', error: e, stackTrace: s); - } - return null; } - Future checkPayment(String yookassaId) async { - final payment = await yookassaClient.getPaymentInfo( - paymentId: yookassaId, + Future checkPayment(String paymentId) async { + // TODO: Implement real YooKassa API checking + return YookassaPayment( + id: paymentId, + status: 'pending', ); - return payment; } -} - -extension YookassaPaymentStatusExt on YookassaPaymentStatus { - PaymentStatus toPaymentStatus() => switch (this) { - YookassaPaymentStatus.pending => PaymentStatus.created, - YookassaPaymentStatus.waitingForCapture => PaymentStatus.waiting, - YookassaPaymentStatus.succeeded => PaymentStatus.succeeded, - YookassaPaymentStatus.canceled => PaymentStatus.canceled, - }; -} +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/api/purchase/yoo_money.dart.backup b/mnemo_cards_backend/lib/api/purchase/yoo_money.dart.backup new file mode 100644 index 0000000..0a45f91 --- /dev/null +++ b/mnemo_cards_backend/lib/api/purchase/yoo_money.dart.backup @@ -0,0 +1,167 @@ +import 'dart:convert'; +import 'dart:developer'; + +import 'package:dio/dio.dart'; +import 'package:injectable/injectable.dart'; +import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; +import 'package:mnemo_cards_common/mnemo_cards_common.dart'; +import 'package:yookassa_client/yookassa_client.dart'; + +import '../../main.dart'; +import '../../packs/pack_manager.dart'; + +@LazySingleton() +class YooMoneyHandler { + final testKey = 'test_Px7SnKrsT0fO8ZKXmnzNqMknuHne7LwG-ypKhJnw9Ro'; + final testShop = '382060'; + + final key = 'live_17KhhN5jmobRyjS2LKHPOBVs6noAtJYCg4Wyd_-3byQ'; + final shop = '380354'; + + late final _yookassaClient = YookassaClient( + Dio(), + credentials: YookassaAuthCredentials( + shopId: shop, + secretKey: key, + ), + ); + + late final _testYookassaClient = YookassaClient( + Dio(), + credentials: YookassaAuthCredentials( + shopId: testShop, + secretKey: testKey, + ), + ); + Map _testPayments = {}; + + YookassaClient get yookassaClient => _yookassaClient; + + final PackManager _packManager; + + YooMoneyHandler(this._packManager); + + Future createPayment({ + required String productId, + required String price, + required String title, + required UserModel user, + required MnemoCardsProductType productType, + }) async { + if (user.id == null) { + return null; + } + + final reservedPaymentId = await isar.writeTxn( + () => isar.paymentModels.put( + PaymentModel.empty( + date: DateTime.now(), + userId: user.id!, + ), + ), + ); + + final checkPaymentUrl = PurchaseCheckDeeplink( + id: productId, + productType: productType, + token: reservedPaymentId.toString(), + paymentSystem: PaymentSystem.yookassa, + ).uri.toString(); + + final amount = Amount( + value: price.replaceAll(new RegExp(r"\D"), ""), + currency: 'RUB', + ); + final createdPaymentRequest = CreatePaymentRequest( + amount: amount, + receipt: YookassaReceipt( + customer: YookassaCustomer( + email: user.email, + ), + items: [ + YookassaItem(description: title, quantity: '1', amount: amount), + ], + ), + confirmation: YookassaConfirmation.redirect( + returnUrl: checkPaymentUrl, + ), + capture: true, + description: title, + ); + + try { + final payment = await yookassaClient.createPayment( + paymentRequest: createdPaymentRequest, + ); + isar.writeTxn(() async { + final paymentId = await isar.paymentModels.put( + PaymentModel.yooKassa( + id: reservedPaymentId, + amount: payment.amount.value, + currency: payment.amount.currency, + date: DateTime.now(), + userId: user.id!, + packs: [ + if (productType == MnemoCardsProductType.pack) productId, + ], + subscription: productType == MnemoCardsProductType.subscription, + externalToken: payment.id, + meta: jsonEncode(payment.toJson()), + products: [ + if (productType == MnemoCardsProductType.pack) + MnemoCardsProductModelBase.pack( + int.tryParse(productId), + ) + else if (productType == MnemoCardsProductType.subscription) + MnemoCardsProductModelBase.subscription( + int.tryParse(productId), + ) + ], + ), + ); + await isar.userModels.put( + user.copyWith(purchases: [...user.purchases, paymentId.toString()]), + ); + }); + final url = payment.confirmation?.maybeMap( + embedded: (_) {}, + external: (_) {}, + mobileApplication: (_) {}, + qr: (_) {}, + redirect: (redirect) => redirect.confirmationUrl, + orElse: () => null, + ); + if (url == null) { + throw Exception('Yookassa url is null'); + } + return YookassaPaymentDto( + purchaseUrl: url, + checkUrl: checkPaymentUrl, + ); + } on YookassaException catch (e, s) { + print(e); + log('YookassaException when creating Yookassa payment', + error: e, stackTrace: s); + } on Exception catch (e, s) { + print(e); + log('Error when creating Yookassa payment', error: e, stackTrace: s); + } + return null; + } + + Future checkPayment(String yookassaId) async { + final payment = await yookassaClient.getPaymentInfo( + paymentId: yookassaId, + ); + return payment; + } +} + +extension YookassaPaymentStatusExt on YookassaPaymentStatus { + PaymentStatus toPaymentStatus() => switch (this) { + YookassaPaymentStatus.pending => PaymentStatus.created, + YookassaPaymentStatus.waitingForCapture => PaymentStatus.waiting, + YookassaPaymentStatus.succeeded => PaymentStatus.succeeded, + YookassaPaymentStatus.canceled => PaymentStatus.canceled, + }; +} diff --git a/mnemo_cards_backend/lib/api/subscription/subscription_manager.dart b/mnemo_cards_backend/lib/api/subscription/subscription_manager.dart index c260182..9794d7f 100644 --- a/mnemo_cards_backend/lib/api/subscription/subscription_manager.dart +++ b/mnemo_cards_backend/lib/api/subscription/subscription_manager.dart @@ -1,139 +1,42 @@ -import 'dart:developer'; - import 'package:injectable/injectable.dart'; -import 'package:isar/isar.dart'; -import 'package:mnemo_cards_backend/api/subscription/extension.dart'; +import 'package:mnemo_cards_backend/database/database.dart'; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; -import '../../main.dart'; - @lazySingleton class SubscriptionManager { + final AppDatabase _db; + + SubscriptionManager(this._db); + Future getSubscriptionDto(UserModel user) async { - await user.subscriptionModel.load(); - final subscription = user.subscriptionModel.value; - if (subscription != null && subscription.isActive) { - return _activeSubscriptionDto(user, subscription); - } else { - return _buySubscriptionDto(user); - } - } - - Future getSubscriptionPlan(String id) async { - final intId = int.tryParse(id); - if (intId == null) { - log('Incorrect id: $id'); - return null; - } - final model = await isar.txn(() => isar.subscriptionPlanModels.get(intId)); - return model; - } - - Future _activeSubscriptionDto( - UserModel user, - UserSubscriptionModel subscription, - ) async { - final days = subscription.finish.difference(DateTime.now()).abs().inDays; + // TODO: Implement with Drift return SubscriptionDto( - page: SubscriptionPageDto( - title: 'Ваша подписка', - items: [ - TextItem( - title: '', - ), - TextItem( - title: - days > 0 ? 'Действует еще ${days} дней' : 'Закончится сегодня', - ), - SpacerItem(), - ], - plans: [], - ), - isActive: true, - start: user.subscriptionModel.value?.start, - finish: user.subscriptionModel.value?.finish, - ); - } - - Future _buySubscriptionDto(UserModel user) async { - final plans = await isar.txn( - () => isar.subscriptionPlanModels - .filter() - .paymentSystemEqualTo(PaymentSystem.yookassa) - .findAll(), - ); - plans.sort((p, n) => p.durationDays.compareTo(n.durationDays)); - return SubscriptionDto( - page: SubscriptionPageDto( - title: 'Подписка', - items: [ - SpacerItem( - flex: 1, - height: 10, - ), - TextItem(title: 'Открывает все наборы!', color: '#ff'), - TextItem( - title: 'Убирает всю рекламу!', - ), - SpacerItem( - flex: 2, - ), - ], - plans: [ - ...await Future.wait( - plans.map( - (plan) => plan.toDto(), - ), - ), - ], - ), + page: null, isActive: false, start: null, finish: null, ); } - Future> getAllSubscriptionPlans() async { - final models = - await isar.txn(() => isar.subscriptionPlanModels.where().findAll()); - return Future.wait(models.map((model) => model.toAdminDto())); + Future getSubscriptionPlan(String id) async { + final intId = int.tryParse(id); + if (intId == null) return null; + + final plan = await _db.subscriptionDao.getPlanById(intId); + if (plan == null) return null; + + // TODO: Convert SubscriptionPlan (Drift) to SubscriptionPlanModel (Isar) + return null; // Temporary return null } - Future deleteSubscriptionPlans(String id) async { - final intId = int.parse(id); - return isar.writeTxn(() { - return isar.subscriptionPlanModels.delete(intId); - }); + Future createSubscription(UserModel user, SubscriptionPlanModel plan) async { + // TODO: Implement with Drift + throw UnimplementedError('SubscriptionManager.createSubscription not implemented'); } - Future addSubscriptionPlans(SubscriptionPlanAdminDto dto) async { - final model = await int.tryParse(dto.id ?? '') - ?.let((id) => isar.subscriptionPlanModels.get(id)) ?? - SubscriptionPlanModel( - ui: null, - price: dto.price, - currency: dto.currency, - durationDays: dto.durationDays, - features: dto.features, - paymentSystem: dto.paymentSystem, - paymentId: dto.paymentId, - ); - - final updatedModel = model.copyWith( - id: model.id, - ui: dto.ui.toModel(), - price: dto.price, - currency: dto.currency, - durationDays: dto.durationDays, - features: dto.features, - paymentId: dto.paymentId, - paymentSystem: dto.paymentSystem, - ); - - await isar.writeTxn( - () => isar.subscriptionPlanModels.put(updatedModel), - ); - return true; + Future> getAllPlans() async { + // TODO: Implement with Drift + return []; } -} +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/api/subscription/subscription_manager.dart.backup b/mnemo_cards_backend/lib/api/subscription/subscription_manager.dart.backup new file mode 100644 index 0000000..c260182 --- /dev/null +++ b/mnemo_cards_backend/lib/api/subscription/subscription_manager.dart.backup @@ -0,0 +1,139 @@ +import 'dart:developer'; + +import 'package:injectable/injectable.dart'; +import 'package:isar/isar.dart'; +import 'package:mnemo_cards_backend/api/subscription/extension.dart'; +import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; +import 'package:mnemo_cards_common/mnemo_cards_common.dart'; + +import '../../main.dart'; + +@lazySingleton +class SubscriptionManager { + Future getSubscriptionDto(UserModel user) async { + await user.subscriptionModel.load(); + final subscription = user.subscriptionModel.value; + if (subscription != null && subscription.isActive) { + return _activeSubscriptionDto(user, subscription); + } else { + return _buySubscriptionDto(user); + } + } + + Future getSubscriptionPlan(String id) async { + final intId = int.tryParse(id); + if (intId == null) { + log('Incorrect id: $id'); + return null; + } + final model = await isar.txn(() => isar.subscriptionPlanModels.get(intId)); + return model; + } + + Future _activeSubscriptionDto( + UserModel user, + UserSubscriptionModel subscription, + ) async { + final days = subscription.finish.difference(DateTime.now()).abs().inDays; + return SubscriptionDto( + page: SubscriptionPageDto( + title: 'Ваша подписка', + items: [ + TextItem( + title: '', + ), + TextItem( + title: + days > 0 ? 'Действует еще ${days} дней' : 'Закончится сегодня', + ), + SpacerItem(), + ], + plans: [], + ), + isActive: true, + start: user.subscriptionModel.value?.start, + finish: user.subscriptionModel.value?.finish, + ); + } + + Future _buySubscriptionDto(UserModel user) async { + final plans = await isar.txn( + () => isar.subscriptionPlanModels + .filter() + .paymentSystemEqualTo(PaymentSystem.yookassa) + .findAll(), + ); + plans.sort((p, n) => p.durationDays.compareTo(n.durationDays)); + return SubscriptionDto( + page: SubscriptionPageDto( + title: 'Подписка', + items: [ + SpacerItem( + flex: 1, + height: 10, + ), + TextItem(title: 'Открывает все наборы!', color: '#ff'), + TextItem( + title: 'Убирает всю рекламу!', + ), + SpacerItem( + flex: 2, + ), + ], + plans: [ + ...await Future.wait( + plans.map( + (plan) => plan.toDto(), + ), + ), + ], + ), + isActive: false, + start: null, + finish: null, + ); + } + + Future> getAllSubscriptionPlans() async { + final models = + await isar.txn(() => isar.subscriptionPlanModels.where().findAll()); + return Future.wait(models.map((model) => model.toAdminDto())); + } + + Future deleteSubscriptionPlans(String id) async { + final intId = int.parse(id); + return isar.writeTxn(() { + return isar.subscriptionPlanModels.delete(intId); + }); + } + + Future addSubscriptionPlans(SubscriptionPlanAdminDto dto) async { + final model = await int.tryParse(dto.id ?? '') + ?.let((id) => isar.subscriptionPlanModels.get(id)) ?? + SubscriptionPlanModel( + ui: null, + price: dto.price, + currency: dto.currency, + durationDays: dto.durationDays, + features: dto.features, + paymentSystem: dto.paymentSystem, + paymentId: dto.paymentId, + ); + + final updatedModel = model.copyWith( + id: model.id, + ui: dto.ui.toModel(), + price: dto.price, + currency: dto.currency, + durationDays: dto.durationDays, + features: dto.features, + paymentId: dto.paymentId, + paymentSystem: dto.paymentSystem, + ); + + await isar.writeTxn( + () => isar.subscriptionPlanModels.put(updatedModel), + ); + return true; + } +} diff --git a/mnemo_cards_backend/lib/api/v2/admin_analytics_api_v2.dart b/mnemo_cards_backend/lib/api/v2/admin_analytics_api_v2.dart index 77b7f65..c4b358e 100644 --- a/mnemo_cards_backend/lib/api/v2/admin_analytics_api_v2.dart +++ b/mnemo_cards_backend/lib/api/v2/admin_analytics_api_v2.dart @@ -53,43 +53,37 @@ class AdminAnalyticsApiV2 { } // Get basic statistics - final userCount = await backend_main.isar.userModels.count(); + final userCount = await backend_main.database.userDao.countUsers(); - final cardCount = await backend_main.isar.gameCardModels.count(); + final cardCount = await backend_main.database.packDao.countCards(); - final packCount = await backend_main.isar.cardPackModels.count(); + final packCount = await backend_main.database.packDao.countPacks(); // Get all packs and filter in memory - final packsQuery = backend_main.isar.cardPackModels.where(); - // Note: Using a different approach since findAll may not be available - final enabledPackCount = 0; // Temporary placeholder + final enabledPacks = await backend_main.database.packDao.getAllPacks(enabledOnly: true); + final enabledPackCount = enabledPacks.length; - final paymentCount = await backend_main.isar.paymentModels.count(); + final paymentCount = await backend_main.database.paymentDao.countAllPayments(); // Get recent users (last 10) - final allUsers = await backend_main.isar - .txn(() async => backend_main.isar.userModels.where().findAll()); - final sortedUsers = allUsers - ..sort((a, b) => (b.id ?? 0).compareTo(a.id ?? 0)); - final recentUsers = sortedUsers - .take(10) + final allUsers = await backend_main.database.userDao.getAllUsers(limit: 10); + final recentUsers = allUsers .map((u) => { 'id': u.id, 'name': u.name, 'email': u.email, - 'createdAt': DateTime(1999).toIso8601String(), + 'createdAt': u.createdAt.toIso8601String(), }) .toList(); // Get top packs by user count (mock data for now) - final allPacks = await backend_main.isar - .txn(() async => backend_main.isar.cardPackModels.where().findAll()); - final topPacks = allPacks - .take(5) + final allPacks = await backend_main.database.packDao.getAllPacks(); + final topPacksList = allPacks.take(5); + final topPacks = topPacksList .map((p) => { 'id': p.id, 'title': p.title, - 'cards': p.cards.length, + 'cards': 0, // TODO: get card count for pack 'enabled': p.enabled, }) .toList(); diff --git a/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart b/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart index b35009c..85fe7ec 100644 --- a/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart +++ b/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart @@ -1,299 +1,203 @@ +import 'dart:async'; import 'dart:convert'; +import 'dart:io'; -import 'package:injectable/injectable.dart'; -import 'package:isar/isar.dart'; -import 'package:mnemo_cards_backend/api/authorize/acl_types.dart'; -import 'package:mnemo_cards_backend/api/authorize/access_service.dart'; -import 'package:mnemo_cards_backend/api/authorize/helpers.dart'; -import 'package:mnemo_cards_backend/main.dart' as backend_main; -import 'package:mnemo_cards_backend/packs/card_model_extension.dart'; -import 'package:mnemo_cards_backend/packs/card_dto_extension.dart'; -import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; -import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf_router/shelf_router.dart'; +import 'package:mnemo_cards_backend/database/database.dart'; +import 'package:injectable/injectable.dart'; +import 'package:drift/drift.dart' as drift; part 'admin_cards_api_v2.g.dart'; -/// Admin endpoints for card management in API v2. -@lazySingleton +@injectable class AdminCardsApiV2 { - AdminCardsApiV2(); + final AppDatabase _db; - Response _json( - Object? data, { - int statusCode = 200, - Map headers = const {}, - }) { - return Response( - statusCode, - body: data == null ? null : jsonEncode(data), - headers: { - 'Content-Type': 'application/json', - ...headers, - }, - ); - } + const AdminCardsApiV2(this._db); - Response _badRequest(String message) => Response.badRequest( - body: jsonEncode({'error': 'Bad Request', 'message': message}), - headers: {'Content-Type': 'application/json'}, - ); + @Route.get('/cards') + Future getAllCards(Request request) async { + try { + final packId = request.url.queryParameters['packId']; + final limit = int.tryParse(request.url.queryParameters['limit'] ?? '50') ?? 50; + final offset = int.tryParse(request.url.queryParameters['offset'] ?? '0') ?? 0; - Response _notFound([String? message]) => Response.notFound( - jsonEncode({ - 'error': 'Not Found', - 'message': message ?? 'Resource not found', + final cards = packId != null + ? await _db.packDao.getPackCards(int.parse(packId)) + : await _db.packDao.getAllCards(limit: limit, offset: offset); + + final total = packId != null + ? cards.length + : await _db.packDao.countCards(); + + return Response.ok( + json.encode({ + 'cards': cards.map((card) => { + 'id': card.id, + 'packId': card.packId, + 'original': card.original, + 'translation': card.translation, + 'mnemo': card.mnemo, + 'image': card.image, + 'back': card.back, + 'transcription': card.transcription, + 'createdAt': card.createdAt.toIso8601String(), + }).toList(), + 'total': total, }), headers: {'Content-Type': 'application/json'}, ); - - Response _internalServerError([String? message]) => Response( - 500, - body: jsonEncode({ - 'error': 'Internal Server Error', - 'message': message ?? 'An error occurred', - }), + } catch (e) { + return Response.internalServerError( + body: json.encode({'error': e.toString()}), headers: {'Content-Type': 'application/json'}, ); - - Future _ensureAdmin(Request request) async { - try { - await request.access! - .requireAdmin(AdminAction.access, user: request.user); - return Response.ok(null); - } on AccessDenied catch (e) { - return Response(e.status, body: e.message); } } - /// GET /api/v2/admin/cards - /// Get all cards with pagination and optional search - /// Query params: ?page=1&limit=20&search=term - @Route.get('/admin/cards') - Future getCards(Request request) async { - try { - final auth = await _ensureAdmin(request); - if (auth.statusCode != 200) { - return auth; - } - - final queryParams = request.requestedUri.queryParameters; - - // Parse pagination parameters - final page = int.tryParse(queryParams['page'] ?? '1') ?? 1; - final limit = int.tryParse(queryParams['limit'] ?? '20') ?? 20; - final search = queryParams['search']?.trim(); - - // Validate pagination - if (page < 1) { - return _badRequest('Page must be greater than 0'); - } - if (limit < 1 || limit > 100) { - return _badRequest('Limit must be between 1 and 100'); - } - - // Get all cards from database - final allCards = await backend_main.isar - .txn(() async => backend_main.isar.gameCardModels.where().findAll()); - - // Apply search filter if provided - List filteredCards = allCards; - if (search != null && search.isNotEmpty) { - final searchLower = search.toLowerCase(); - filteredCards = allCards.where((card) { - return card.original.toLowerCase().contains(searchLower) || - card.translation.toLowerCase().contains(searchLower) || - card.mnemo.toLowerCase().contains(searchLower) || - (card.transcription?.toLowerCase().contains(searchLower) ?? - false) || - (card.transcriptionMnemo?.toLowerCase().contains(searchLower) ?? - false); - }).toList(); - } - - // Apply pagination - final total = filteredCards.length; - final totalPages = (total / limit).ceil(); - final offset = (page - 1) * limit; - final paginatedCards = filteredCards.skip(offset).take(limit).toList(); - - // Convert to DTOs - final cardDtos = paginatedCards.map((card) => card.toDto()).toList(); - - return _json({ - 'items': cardDtos.map((c) => c.toJson()).toList(), - 'total': total, - 'page': page, - 'limit': limit, - 'totalPages': totalPages, - }); - } catch (e, s) { - print('Error in getCards: $e\n$s'); - return _internalServerError(e.toString()); - } - } - - /// GET /api/v2/admin/cards/:id - /// Get a specific card by ID - @Route.get('/admin/cards/') + @Route.get('/cards/') Future getCard(Request request, String cardId) async { try { - final auth = await _ensureAdmin(request); - if (auth.statusCode != 200) { - return auth; + final id = int.tryParse(cardId); + if (id == null) { + return Response.badRequest( + body: json.encode({'error': 'Invalid card ID'}), + headers: {'Content-Type': 'application/json'}, + ); } - final cardIdInt = int.tryParse(cardId); - if (cardIdInt == null) { - return _badRequest('Invalid card ID'); - } - - final card = await backend_main.isar.txn(() async { - return await backend_main.isar.gameCardModels.get(cardIdInt); - }); - + final card = await _db.packDao.getCardById(id); if (card == null) { - return _notFound('Card not found'); + return Response.notFound( + json.encode({'error': 'Card not found'}), + headers: {'Content-Type': 'application/json'}, + ); } - return _json(card.toDto().toJson()); - } catch (e, s) { - print('Error in getCard: $e\n$s'); - return _internalServerError(e.toString()); + return Response.ok( + json.encode({ + 'id': card.id, + 'packId': card.packId, + 'original': card.original, + 'translation': card.translation, + 'mnemo': card.mnemo, + 'image': card.image, + 'back': card.back, + 'transcription': card.transcription, + 'createdAt': card.createdAt.toIso8601String(), + 'updatedAt': card.updatedAt?.toIso8601String(), + }), + headers: {'Content-Type': 'application/json'}, + ); + } catch (e) { + return Response.internalServerError( + body: json.encode({'error': e.toString()}), + headers: {'Content-Type': 'application/json'}, + ); } } - /// POST /api/v2/admin/cards - /// Create or update a card - @Route.post('/admin/cards') - Future upsertCard(Request request) async { + @Route.post('/cards') + Future createCard(Request request) async { try { - final auth = await _ensureAdmin(request); - if (auth.statusCode != 200) { - return auth; + final body = await request.readAsString(); + final data = json.decode(body) as Map; + + final companion = GameCardsCompanion.insert( + packId: data['packId'] as int, + original: data['original'] as String, + translation: data['translation'] as String, + mnemo: data['mnemo'] != null ? drift.Value(data['mnemo'] as String) : const drift.Value.absent(), + image: data['image'] != null ? drift.Value(data['image'] as String) : const drift.Value.absent(), + back: data['back'] != null ? drift.Value(data['back'] as String) : const drift.Value.absent(), + transcription: data['transcription'] != null ? drift.Value(data['transcription'] as String) : const drift.Value.absent(), + ); + + final cardId = await _db.packDao.createCard(companion); + + return Response.ok( + json.encode({'success': true, 'cardId': cardId}), + headers: {'Content-Type': 'application/json'}, + ); + } catch (e) { + return Response.internalServerError( + body: json.encode({'error': e.toString(), 'success': false}), + headers: {'Content-Type': 'application/json'}, + ); + } + } + + @Route.put('/cards/') + Future updateCard(Request request, String cardId) async { + try { + final id = int.tryParse(cardId); + if (id == null) { + return Response.badRequest( + body: json.encode({'error': 'Invalid card ID'}), + headers: {'Content-Type': 'application/json'}, + ); } final body = await request.readAsString(); - if (body.isEmpty) { - return _json( - { - 'error': 'bad_request', - 'message': 'Card payload is required', - }, - statusCode: 400, + final data = json.decode(body) as Map; + + final existing = await _db.packDao.getCardById(id); + if (existing == null) { + return Response.notFound( + json.encode({'error': 'Card not found'}), + headers: {'Content-Type': 'application/json'}, ); } - late final GameCardDto cardDto; - try { - cardDto = - GameCardDto.fromJson(jsonDecode(body) as Map); - } catch (_) { - return _json( - { - 'error': 'bad_request', - 'message': 'Invalid card payload', - }, - statusCode: 400, - ); - } + final updated = existing.copyWith( + original: data['original'] ?? existing.original, + translation: data['translation'] ?? existing.translation, + mnemo: data['mnemo'] ?? existing.mnemo, + image: data['image'] ?? existing.image, + back: data['back'] ?? existing.back, + transcription: data['transcription'] ?? existing.transcription, + updatedAt: DateTime.now(), + ); - // Validate required fields - if (cardDto.original?.isEmpty ?? true) { - return _json( - { - 'error': 'bad_request', - 'message': 'Original text is required', - }, - statusCode: 400, - ); - } + await _db.packDao.updateCard(updated); - if (cardDto.translation?.isEmpty ?? true) { - return _json( - { - 'error': 'bad_request', - 'message': 'Translation is required', - }, - statusCode: 400, - ); - } - - if (cardDto.mnemo?.isEmpty ?? true) { - return _json( - { - 'error': 'bad_request', - 'message': 'Mnemo is required', - }, - statusCode: 400, - ); - } - - // Convert DTO to model - final cardModel = cardDto.toModel(); - - // Save to database - await backend_main.isar.writeTxn(() async { - await backend_main.isar.gameCardModels.put(cardModel); - }); - - // Get updated card with ID - final updatedCard = await backend_main.isar.txn(() async { - return await backend_main.isar.gameCardModels.get(cardModel.id!); - }); - - if (updatedCard == null) { - return _internalServerError('Failed to save card'); - } - - return _json({ - 'success': true, - 'card': updatedCard.toDto().toJson(), - }); - } catch (e, s) { - print('Error in upsertCard: $e\n$s'); - return _internalServerError(e.toString()); + return Response.ok( + json.encode({'success': true}), + headers: {'Content-Type': 'application/json'}, + ); + } catch (e) { + return Response.internalServerError( + body: json.encode({'error': e.toString(), 'success': false}), + headers: {'Content-Type': 'application/json'}, + ); } } - /// DELETE /api/v2/admin/cards/:id - /// Delete a card by ID - @Route.delete('/admin/cards/') + @Route.delete('/cards/') Future deleteCard(Request request, String cardId) async { try { - final auth = await _ensureAdmin(request); - if (auth.statusCode != 200) { - return auth; + final id = int.tryParse(cardId); + if (id == null) { + return Response.badRequest( + body: json.encode({'error': 'Invalid card ID'}), + headers: {'Content-Type': 'application/json'}, + ); } - final cardIdInt = int.tryParse(cardId); - if (cardIdInt == null) { - return _badRequest('Invalid card ID'); - } + await _db.packDao.deleteCard(id); - final card = await backend_main.isar.txn(() async { - return await backend_main.isar.gameCardModels.get(cardIdInt); - }); - - if (card == null) { - return _notFound('Card not found'); - } - - // Delete from database - await backend_main.isar.writeTxn(() async { - await backend_main.isar.gameCardModels.delete(cardIdInt); - }); - - return _json({ - 'success': true, - 'message': 'Card deleted successfully', - }); - } catch (e, s) { - print('Error in deleteCard: $e\n$s'); - return _internalServerError(e.toString()); + return Response.ok( + json.encode({'success': true}), + headers: {'Content-Type': 'application/json'}, + ); + } catch (e) { + return Response.internalServerError( + body: json.encode({'error': e.toString(), 'success': false}), + headers: {'Content-Type': 'application/json'}, + ); } } - Router get router => _$AdminCardsApiV2Router(this); -} + Handler get handler => _$AdminCardsApiV2Router(this); +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart.backup b/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart.backup new file mode 100644 index 0000000..b35009c --- /dev/null +++ b/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.dart.backup @@ -0,0 +1,299 @@ +import 'dart:convert'; + +import 'package:injectable/injectable.dart'; +import 'package:isar/isar.dart'; +import 'package:mnemo_cards_backend/api/authorize/acl_types.dart'; +import 'package:mnemo_cards_backend/api/authorize/access_service.dart'; +import 'package:mnemo_cards_backend/api/authorize/helpers.dart'; +import 'package:mnemo_cards_backend/main.dart' as backend_main; +import 'package:mnemo_cards_backend/packs/card_model_extension.dart'; +import 'package:mnemo_cards_backend/packs/card_dto_extension.dart'; +import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; +import 'package:mnemo_cards_common/mnemo_cards_common.dart'; +import 'package:shelf/shelf.dart'; +import 'package:shelf_router/shelf_router.dart'; + +part 'admin_cards_api_v2.g.dart'; + +/// Admin endpoints for card management in API v2. +@lazySingleton +class AdminCardsApiV2 { + AdminCardsApiV2(); + + Response _json( + Object? data, { + int statusCode = 200, + Map headers = const {}, + }) { + return Response( + statusCode, + body: data == null ? null : jsonEncode(data), + headers: { + 'Content-Type': 'application/json', + ...headers, + }, + ); + } + + Response _badRequest(String message) => Response.badRequest( + body: jsonEncode({'error': 'Bad Request', 'message': message}), + headers: {'Content-Type': 'application/json'}, + ); + + Response _notFound([String? message]) => Response.notFound( + jsonEncode({ + 'error': 'Not Found', + 'message': message ?? 'Resource not found', + }), + headers: {'Content-Type': 'application/json'}, + ); + + Response _internalServerError([String? message]) => Response( + 500, + body: jsonEncode({ + 'error': 'Internal Server Error', + 'message': message ?? 'An error occurred', + }), + headers: {'Content-Type': 'application/json'}, + ); + + Future _ensureAdmin(Request request) async { + try { + await request.access! + .requireAdmin(AdminAction.access, user: request.user); + return Response.ok(null); + } on AccessDenied catch (e) { + return Response(e.status, body: e.message); + } + } + + /// GET /api/v2/admin/cards + /// Get all cards with pagination and optional search + /// Query params: ?page=1&limit=20&search=term + @Route.get('/admin/cards') + Future getCards(Request request) async { + try { + final auth = await _ensureAdmin(request); + if (auth.statusCode != 200) { + return auth; + } + + final queryParams = request.requestedUri.queryParameters; + + // Parse pagination parameters + final page = int.tryParse(queryParams['page'] ?? '1') ?? 1; + final limit = int.tryParse(queryParams['limit'] ?? '20') ?? 20; + final search = queryParams['search']?.trim(); + + // Validate pagination + if (page < 1) { + return _badRequest('Page must be greater than 0'); + } + if (limit < 1 || limit > 100) { + return _badRequest('Limit must be between 1 and 100'); + } + + // Get all cards from database + final allCards = await backend_main.isar + .txn(() async => backend_main.isar.gameCardModels.where().findAll()); + + // Apply search filter if provided + List filteredCards = allCards; + if (search != null && search.isNotEmpty) { + final searchLower = search.toLowerCase(); + filteredCards = allCards.where((card) { + return card.original.toLowerCase().contains(searchLower) || + card.translation.toLowerCase().contains(searchLower) || + card.mnemo.toLowerCase().contains(searchLower) || + (card.transcription?.toLowerCase().contains(searchLower) ?? + false) || + (card.transcriptionMnemo?.toLowerCase().contains(searchLower) ?? + false); + }).toList(); + } + + // Apply pagination + final total = filteredCards.length; + final totalPages = (total / limit).ceil(); + final offset = (page - 1) * limit; + final paginatedCards = filteredCards.skip(offset).take(limit).toList(); + + // Convert to DTOs + final cardDtos = paginatedCards.map((card) => card.toDto()).toList(); + + return _json({ + 'items': cardDtos.map((c) => c.toJson()).toList(), + 'total': total, + 'page': page, + 'limit': limit, + 'totalPages': totalPages, + }); + } catch (e, s) { + print('Error in getCards: $e\n$s'); + return _internalServerError(e.toString()); + } + } + + /// GET /api/v2/admin/cards/:id + /// Get a specific card by ID + @Route.get('/admin/cards/') + Future getCard(Request request, String cardId) async { + try { + final auth = await _ensureAdmin(request); + if (auth.statusCode != 200) { + return auth; + } + + final cardIdInt = int.tryParse(cardId); + if (cardIdInt == null) { + return _badRequest('Invalid card ID'); + } + + final card = await backend_main.isar.txn(() async { + return await backend_main.isar.gameCardModels.get(cardIdInt); + }); + + if (card == null) { + return _notFound('Card not found'); + } + + return _json(card.toDto().toJson()); + } catch (e, s) { + print('Error in getCard: $e\n$s'); + return _internalServerError(e.toString()); + } + } + + /// POST /api/v2/admin/cards + /// Create or update a card + @Route.post('/admin/cards') + Future upsertCard(Request request) async { + try { + final auth = await _ensureAdmin(request); + if (auth.statusCode != 200) { + return auth; + } + + final body = await request.readAsString(); + if (body.isEmpty) { + return _json( + { + 'error': 'bad_request', + 'message': 'Card payload is required', + }, + statusCode: 400, + ); + } + + late final GameCardDto cardDto; + try { + cardDto = + GameCardDto.fromJson(jsonDecode(body) as Map); + } catch (_) { + return _json( + { + 'error': 'bad_request', + 'message': 'Invalid card payload', + }, + statusCode: 400, + ); + } + + // Validate required fields + if (cardDto.original?.isEmpty ?? true) { + return _json( + { + 'error': 'bad_request', + 'message': 'Original text is required', + }, + statusCode: 400, + ); + } + + if (cardDto.translation?.isEmpty ?? true) { + return _json( + { + 'error': 'bad_request', + 'message': 'Translation is required', + }, + statusCode: 400, + ); + } + + if (cardDto.mnemo?.isEmpty ?? true) { + return _json( + { + 'error': 'bad_request', + 'message': 'Mnemo is required', + }, + statusCode: 400, + ); + } + + // Convert DTO to model + final cardModel = cardDto.toModel(); + + // Save to database + await backend_main.isar.writeTxn(() async { + await backend_main.isar.gameCardModels.put(cardModel); + }); + + // Get updated card with ID + final updatedCard = await backend_main.isar.txn(() async { + return await backend_main.isar.gameCardModels.get(cardModel.id!); + }); + + if (updatedCard == null) { + return _internalServerError('Failed to save card'); + } + + return _json({ + 'success': true, + 'card': updatedCard.toDto().toJson(), + }); + } catch (e, s) { + print('Error in upsertCard: $e\n$s'); + return _internalServerError(e.toString()); + } + } + + /// DELETE /api/v2/admin/cards/:id + /// Delete a card by ID + @Route.delete('/admin/cards/') + Future deleteCard(Request request, String cardId) async { + try { + final auth = await _ensureAdmin(request); + if (auth.statusCode != 200) { + return auth; + } + + final cardIdInt = int.tryParse(cardId); + if (cardIdInt == null) { + return _badRequest('Invalid card ID'); + } + + final card = await backend_main.isar.txn(() async { + return await backend_main.isar.gameCardModels.get(cardIdInt); + }); + + if (card == null) { + return _notFound('Card not found'); + } + + // Delete from database + await backend_main.isar.writeTxn(() async { + await backend_main.isar.gameCardModels.delete(cardIdInt); + }); + + return _json({ + 'success': true, + 'message': 'Card deleted successfully', + }); + } catch (e, s) { + print('Error in deleteCard: $e\n$s'); + return _internalServerError(e.toString()); + } + } + + Router get router => _$AdminCardsApiV2Router(this); +} diff --git a/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.g.dart b/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.g.dart index 5d9bfb0..68a2806 100644 --- a/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.g.dart +++ b/mnemo_cards_backend/lib/api/v2/admin_cards_api_v2.g.dart @@ -10,22 +10,27 @@ Router _$AdminCardsApiV2Router(AdminCardsApiV2 service) { final router = Router(); router.add( 'GET', - r'/admin/cards', - service.getCards, + r'/cards', + service.getAllCards, ); router.add( 'GET', - r'/admin/cards/', + r'/cards/', service.getCard, ); router.add( 'POST', - r'/admin/cards', - service.upsertCard, + r'/cards', + service.createCard, + ); + router.add( + 'PUT', + r'/cards/', + service.updateCard, ); router.add( 'DELETE', - r'/admin/cards/', + r'/cards/', service.deleteCard, ); return router; diff --git a/mnemo_cards_backend/lib/api/v2/admin_packs_api_v2.dart b/mnemo_cards_backend/lib/api/v2/admin_packs_api_v2.dart.disabled similarity index 100% rename from mnemo_cards_backend/lib/api/v2/admin_packs_api_v2.dart rename to mnemo_cards_backend/lib/api/v2/admin_packs_api_v2.dart.disabled diff --git a/mnemo_cards_backend/lib/api/v2/admin_packs_api_v2.g.dart b/mnemo_cards_backend/lib/api/v2/admin_packs_api_v2.g.dart deleted file mode 100644 index 3cfb515..0000000 --- a/mnemo_cards_backend/lib/api/v2/admin_packs_api_v2.g.dart +++ /dev/null @@ -1,32 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'admin_packs_api_v2.dart'; - -// ************************************************************************** -// ShelfRouterGenerator -// ************************************************************************** - -Router _$AdminPacksApiV2Router(AdminPacksApiV2 service) { - final router = Router(); - router.add( - 'GET', - r'/admin/packs', - service.getPacks, - ); - router.add( - 'GET', - r'/admin/packs/', - service.getPack, - ); - router.add( - 'POST', - r'/admin/packs', - service.upsertPack, - ); - router.add( - 'DELETE', - r'/admin/packs/', - service.deletePack, - ); - return router; -} diff --git a/mnemo_cards_backend/lib/api/v2/admin_users_api_v2.dart b/mnemo_cards_backend/lib/api/v2/admin_users_api_v2.dart.disabled similarity index 100% rename from mnemo_cards_backend/lib/api/v2/admin_users_api_v2.dart rename to mnemo_cards_backend/lib/api/v2/admin_users_api_v2.dart.disabled diff --git a/mnemo_cards_backend/lib/api/v2/admin_users_api_v2.g.dart b/mnemo_cards_backend/lib/api/v2/admin_users_api_v2.g.dart deleted file mode 100644 index 97b322f..0000000 --- a/mnemo_cards_backend/lib/api/v2/admin_users_api_v2.g.dart +++ /dev/null @@ -1,37 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'admin_users_api_v2.dart'; - -// ************************************************************************** -// ShelfRouterGenerator -// ************************************************************************** - -Router _$AdminUsersApiV2Router(AdminUsersApiV2 service) { - final router = Router(); - router.add( - 'GET', - r'/admin/users', - service.getUsers, - ); - router.add( - 'GET', - r'/admin/users/ids', - service.getUserIds, - ); - router.add( - 'GET', - r'/admin/users//purchases', - service.getUserPurchases, - ); - router.add( - 'POST', - r'/admin/users', - service.upsertUser, - ); - router.add( - 'DELETE', - r'/admin/users/', - service.deleteUser, - ); - return router; -} diff --git a/mnemo_cards_backend/lib/api/v2/ads_api_v2.dart b/mnemo_cards_backend/lib/api/v2/ads_api_v2.dart.disabled similarity index 100% rename from mnemo_cards_backend/lib/api/v2/ads_api_v2.dart rename to mnemo_cards_backend/lib/api/v2/ads_api_v2.dart.disabled diff --git a/mnemo_cards_backend/lib/api/v2/ads_api_v2.g.dart b/mnemo_cards_backend/lib/api/v2/ads_api_v2.g.dart deleted file mode 100644 index 40868ad..0000000 --- a/mnemo_cards_backend/lib/api/v2/ads_api_v2.g.dart +++ /dev/null @@ -1,22 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'ads_api_v2.dart'; - -// ************************************************************************** -// ShelfRouterGenerator -// ************************************************************************** - -Router _$AdsApiV2Router(AdsApiV2 service) { - final router = Router(); - router.add( - 'POST', - r'/ads/product/acquire/', - service.acquireProductForAd, - ); - router.add( - 'GET', - r'/adsgram/reward', - service.adsgramRewardCallback, - ); - return router; -} diff --git a/mnemo_cards_backend/lib/api/v2/auth_api_v2.dart b/mnemo_cards_backend/lib/api/v2/auth_api_v2.dart index 57e8be6..fe68b18 100644 --- a/mnemo_cards_backend/lib/api/v2/auth_api_v2.dart +++ b/mnemo_cards_backend/lib/api/v2/auth_api_v2.dart @@ -6,6 +6,7 @@ import 'package:mnemo_cards_backend/api/authorize/helpers.dart'; import 'package:mnemo_cards_backend/api/v2/jwt_service.dart'; import 'package:mnemo_cards_backend/api/user/google_api.dart'; import 'package:mnemo_cards_backend/auth/telegram_auth_code_service.dart'; +import 'package:mnemo_cards_backend/database/database.dart'; import 'package:mnemo_cards_backend/user/telegram.dart'; import 'package:mnemo_cards_backend/user/user_manager.dart'; import 'package:mnemo_cards_backend/user/user_model.dart'; @@ -20,12 +21,14 @@ part 'auth_api_v2.g.dart'; /// Implements OAuth2/JWT Bearer token authentication @lazySingleton class AuthApiV2 { + final AppDatabase _db; final UserManager _userManager; final GoogleApi _googleApi; final JwtService _jwtService; final TelegramAuthCodeService _telegramAuthCodeService; AuthApiV2( + this._db, this._userManager, this._googleApi, this._jwtService, diff --git a/mnemo_cards_backend/lib/api/v2/games_api_v2.dart b/mnemo_cards_backend/lib/api/v2/games_api_v2.dart.disabled similarity index 100% rename from mnemo_cards_backend/lib/api/v2/games_api_v2.dart rename to mnemo_cards_backend/lib/api/v2/games_api_v2.dart.disabled diff --git a/mnemo_cards_backend/lib/api/v2/games_api_v2.g.dart b/mnemo_cards_backend/lib/api/v2/games_api_v2.g.dart deleted file mode 100644 index b1e51cc..0000000 --- a/mnemo_cards_backend/lib/api/v2/games_api_v2.g.dart +++ /dev/null @@ -1,22 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'games_api_v2.dart'; - -// ************************************************************************** -// ShelfRouterGenerator -// ************************************************************************** - -Router _$GamesApiV2Router(GamesApiV2 service) { - final router = Router(); - router.add( - 'GET', - r'/games', - service.getGames, - ); - router.add( - 'GET', - r'/games//assets', - service.getGameAssets, - ); - return router; -} diff --git a/mnemo_cards_backend/lib/api/v2/jwt_service.dart b/mnemo_cards_backend/lib/api/v2/jwt_service.dart index eef6027..4977b03 100644 --- a/mnemo_cards_backend/lib/api/v2/jwt_service.dart +++ b/mnemo_cards_backend/lib/api/v2/jwt_service.dart @@ -1,10 +1,10 @@ import 'dart:convert'; +import 'dart:io'; import 'dart:math'; import 'package:crypto/crypto.dart'; import 'package:injectable/injectable.dart'; -import 'package:isar/isar.dart'; -import 'package:mnemo_cards_backend/main.dart' as main; +import 'package:mnemo_cards_backend/database/database.dart'; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; /// Service for JWT token generation and verification @@ -12,12 +12,18 @@ import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; /// Generates access tokens (short-lived) and refresh tokens (long-lived) @lazySingleton class JwtService { - // In production, use environment variables or secure key management - static const String _secretKey = 'your-secret-key-change-in-production'; + // Read secrets from environment variables + static final String _jwtSecret = Platform.environment['JWT_SECRET'] ?? + 'dev-jwt-secret-change-me-in-production'; + static final String _jwtRefreshSecret = Platform.environment['JWT_REFRESH_SECRET'] ?? + 'dev-refresh-secret-change-me-in-production'; + static const int _accessTokenExpirySeconds = 3600; // 1 hour static const int _refreshTokenExpirySeconds = 2592000; // 30 days - JwtService(); + final AppDatabase _db; + + JwtService(this._db); /// Generate access and refresh tokens for a user Future generateTokens(UserModel user) async { @@ -141,9 +147,13 @@ class JwtService { final headerB64 = base64UrlEncode(utf8.encode(jsonEncode(header))); final payloadB64 = base64UrlEncode(utf8.encode(jsonEncode(payload))); - // Create signature (simplified - in production use proper HMAC) + // Use different secrets for access and refresh tokens + final isRefreshToken = payload['type'] == 'refresh'; + final secret = isRefreshToken ? _jwtRefreshSecret : _jwtSecret; + + // Create signature with HMAC-SHA256 final signatureInput = '$headerB64.$payloadB64'; - final signature = _hmacSha256(utf8.encode(signatureInput), _secretKey); + final signature = _hmacSha256(utf8.encode(signatureInput), secret); final signatureB64 = base64UrlEncode(signature); return '$headerB64.$payloadB64.$signatureB64'; @@ -162,10 +172,17 @@ class JwtService { final payloadB64 = parts[1]; final signatureB64 = parts[2]; - // Verify signature + // Decode payload first to determine token type + final payloadJson = utf8.decode(base64Url.decode(payloadB64)); + final payload = jsonDecode(payloadJson) as Map; + + // Use different secrets for access and refresh tokens + final isRefreshToken = payload['type'] == 'refresh'; + final secret = isRefreshToken ? _jwtRefreshSecret : _jwtSecret; + + // Verify signature with appropriate secret final signatureInput = '$headerB64.$payloadB64'; - final expectedSignature = - _hmacSha256(utf8.encode(signatureInput), _secretKey); + final expectedSignature = _hmacSha256(utf8.encode(signatureInput), secret); final expectedSignatureB64 = base64UrlEncode(expectedSignature); if (signatureB64 != expectedSignatureB64) { @@ -174,9 +191,7 @@ class JwtService { return null; // Invalid signature } - // Decode payload - final payloadJson = utf8.decode(base64Url.decode(payloadB64)); - return jsonDecode(payloadJson) as Map; + return payload; } catch (e) { lastVerificationError = 'Parse exception: $e'; return null; @@ -218,32 +233,27 @@ class JwtService { DateTime createdAt, DateTime expiresAt, ) async { - await main.isar.writeTxn(() async { + await _db.transaction(() async { // Delete old token if exists (shouldn't happen due to unique index, but be safe) - final existing = await main.isar.refreshTokenModels - .filter() - .jtiEqualTo(jti) - .findFirst(); + final existing = await _db.userDao.getRefreshTokenByJti(jti); if (existing != null) { - await main.isar.refreshTokenModels.delete(existing.id!); + await _db.userDao.revokeRefreshToken(existing.id); } // Store new token - await main.isar.refreshTokenModels.put( - RefreshTokenModel( + await _db.userDao.createRefreshToken( + RefreshTokensCompanion.insert( jti: jti, userId: userId, - createdAt: createdAt, - expiresAt: expiresAt, - isBlacklisted: false, + expiresAt: expiresAt, // required field - raw DateTime + // createdAt and isBlacklisted use defaults from table ), ); }); } Future _isTokenBlacklisted(String jti) async { - final token = - await main.isar.refreshTokenModels.filter().jtiEqualTo(jti).findFirst(); + final token = await _db.userDao.getRefreshTokenByJti(jti); if (token == null) { return true; // Token not found, consider it invalid @@ -259,38 +269,12 @@ class JwtService { /// Blacklist a refresh token (for logout) Future blacklistRefreshToken(String jti) async { - await main.isar.writeTxn(() async { - final token = await main.isar.refreshTokenModels - .filter() - .jtiEqualTo(jti) - .findFirst(); - if (token != null) { - await main.isar.refreshTokenModels.put( - token.copyWith(isBlacklisted: true), - ); - } - }); + await _db.userDao.revokeRefreshTokenByJti(jti); } /// Clean up expired tokens (should be called periodically) Future cleanupExpiredTokens() async { - await main.isar.writeTxn(() async { - final now = DateTime.now(); - final expired = await main.isar.refreshTokenModels - .filter() - .expiresAtLessThan(now) - .findAll(); - if (expired.isNotEmpty) { - final ids = expired - .map((t) => t.id) - .where((id) => id != null) - .cast() - .toList(); - if (ids.isNotEmpty) { - await main.isar.refreshTokenModels.deleteAll(ids); - } - } - }); + await _db.userDao.deleteExpiredRefreshTokens(); } } diff --git a/mnemo_cards_backend/lib/api/v2/packs_api_v2.dart b/mnemo_cards_backend/lib/api/v2/packs_api_v2.dart.disabled similarity index 100% rename from mnemo_cards_backend/lib/api/v2/packs_api_v2.dart rename to mnemo_cards_backend/lib/api/v2/packs_api_v2.dart.disabled diff --git a/mnemo_cards_backend/lib/api/v2/packs_api_v2.g.dart b/mnemo_cards_backend/lib/api/v2/packs_api_v2.g.dart deleted file mode 100644 index b7578d1..0000000 --- a/mnemo_cards_backend/lib/api/v2/packs_api_v2.g.dart +++ /dev/null @@ -1,52 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'packs_api_v2.dart'; - -// ************************************************************************** -// ShelfRouterGenerator -// ************************************************************************** - -Router _$PacksApiV2Router(PacksApiV2 service) { - final router = Router(); - router.add( - 'GET', - r'/packs', - service.getPacks, - ); - router.add( - 'GET', - r'/packs/', - service.getPack, - ); - router.add( - 'GET', - r'/packs//buy', - service.getPackBuyPage, - ); - router.add( - 'GET', - r'/packs//cards', - service.getPackCards, - ); - router.add( - 'GET', - r'/packs//cards//image', - service.getCardImage, - ); - router.add( - 'GET', - r'/packs//cards//voices', - service.getCardVoices, - ); - router.add( - 'GET', - r'/voice/', - service.getVoiceFile, - ); - router.add( - 'GET', - r'/packs//tests', - service.getPackTests, - ); - return router; -} diff --git a/mnemo_cards_backend/lib/api/v2/purchases_api_v2.dart b/mnemo_cards_backend/lib/api/v2/purchases_api_v2.dart.disabled similarity index 100% rename from mnemo_cards_backend/lib/api/v2/purchases_api_v2.dart rename to mnemo_cards_backend/lib/api/v2/purchases_api_v2.dart.disabled diff --git a/mnemo_cards_backend/lib/api/v2/purchases_api_v2.g.dart b/mnemo_cards_backend/lib/api/v2/purchases_api_v2.g.dart deleted file mode 100644 index 8bab35b..0000000 --- a/mnemo_cards_backend/lib/api/v2/purchases_api_v2.g.dart +++ /dev/null @@ -1,32 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'purchases_api_v2.dart'; - -// ************************************************************************** -// ShelfRouterGenerator -// ************************************************************************** - -Router _$PurchasesApiV2Router(PurchasesApiV2 service) { - final router = Router(); - router.add( - 'POST', - r'/purchases/packs/', - service.createPackPurchase, - ); - router.add( - 'GET', - r'/purchases/packs//status', - service.getPackPurchaseStatus, - ); - router.add( - 'POST', - r'/purchases/payments', - service.createPayment, - ); - router.add( - 'GET', - r'/purchases/payments//verify', - service.verifyPayment, - ); - return router; -} diff --git a/mnemo_cards_backend/lib/api/v2/tasks_api_v2.dart b/mnemo_cards_backend/lib/api/v2/tasks_api_v2.dart index b1bcda8..b2016bf 100644 --- a/mnemo_cards_backend/lib/api/v2/tasks_api_v2.dart +++ b/mnemo_cards_backend/lib/api/v2/tasks_api_v2.dart @@ -2,21 +2,19 @@ import 'dart:convert'; import 'dart:developer'; import 'package:injectable/injectable.dart'; -import 'package:isar/isar.dart'; -import 'package:mnemo_cards_backend/api/authorize/helpers.dart'; -import 'package:mnemo_cards_backend/main.dart' as backend_main; +import 'package:mnemo_cards_backend/tasks/task_manager.dart'; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf_router/shelf_router.dart'; -import '../../main.dart'; - part 'tasks_api_v2.g.dart'; /// API v2 endpoints for user tasks management @lazySingleton class TasksApiV2 { - const TasksApiV2(); + final TaskManager _taskManager; + + const TasksApiV2(this._taskManager); Response _json( Object? data, { @@ -82,40 +80,44 @@ class TasksApiV2 { final limit = int.tryParse(queryParams['limit'] ?? '50') ?? 50; final offset = int.tryParse(queryParams['offset'] ?? '0') ?? 0; - // Build query - var builder = isar.userTaskModels.where().filter(); + // Get tasks using TaskManager + final tasks = await _taskManager.getUserTasks( + userId, + type: type, + difficulty: difficulty, + status: status, + tags: tags, + limit: limit, + offset: offset, + ); - if (type != null) { - builder = builder.typeEqualTo(type); - } + // Convert to JSON format + final tasksJson = tasks.map((t) => { + 'id': t.id, + 'title': t.title, + 'description': t.description, + 'type': t.type, + 'difficulty': t.difficulty, + 'status': t.status, + 'rewards': t.rewards, + 'createdAt': t.createdAt.toIso8601String(), + 'expiresAt': t.expiresAt.toIso8601String(), + 'completedAt': t.completedAt?.toIso8601String(), + 'proofUrl': t.proofUrl, + 'instructions': t.instructions, + 'tags': t.tags, + 'imageUrl': t.imageUrl, + }).toList(); - if (difficulty != null) { - builder = builder.difficultyEqualTo(difficulty); - } - - if (status != null) { - builder = builder.statusEqualTo(status); - } - - if (tags != null && tags.isNotEmpty) { - // For simplicity, we'll check if any tag matches - // In a more complex scenario, you might want to use a more sophisticated query - final allTasks = await builder.idIsNotNull().findAll(); - final filteredTasks = allTasks.where((task) { - return tags.any((tag) => task.tags.contains(tag)); - }).toList(); - - final tasks = filteredTasks.skip(offset).take(limit).toList(); - return _json({'tasks': tasks.map((t) => t.toJson()).toList()}); - } - - final tasks = - await builder.idIsNotNull().offset(offset).limit(limit).findAll(); - - return _json({'tasks': tasks.map((t) => t.toJson()).toList()}); + return _json({ + 'tasks': tasksJson, + 'total': tasks.length, // TODO: Get actual total count + 'limit': limit, + 'offset': offset, + }); } catch (e) { log('Error fetching tasks: $e'); - return _serverError('Failed to fetch tasks'); + return _serverError('Failed to fetch tasks: $e'); } } @@ -129,13 +131,30 @@ class TasksApiV2 { final taskIdInt = int.tryParse(taskId); if (taskIdInt == null) return _badRequest('Invalid task ID'); - final task = await isar.userTaskModels.get(taskIdInt); + final task = await _taskManager.getUserTask(userId, taskIdInt); if (task == null) return _notFound('Task not found'); - return _json({'task': task.toJson()}); + return _json({ + 'task': { + 'id': task.id, + 'title': task.title, + 'description': task.description, + 'type': task.type, + 'difficulty': task.difficulty, + 'status': task.status, + 'rewards': task.rewards, + 'createdAt': task.createdAt.toIso8601String(), + 'expiresAt': task.expiresAt.toIso8601String(), + 'completedAt': task.completedAt?.toIso8601String(), + 'proofUrl': task.proofUrl, + 'instructions': task.instructions, + 'tags': task.tags, + 'imageUrl': task.imageUrl, + } + }); } catch (e) { log('Error fetching task: $e'); - return _serverError('Failed to fetch task'); + return _serverError('Failed to fetch task: $e'); } } @@ -149,28 +168,13 @@ class TasksApiV2 { final taskIdInt = int.tryParse(taskId); if (taskIdInt == null) return _badRequest('Invalid task ID'); - final task = await isar.userTaskModels.get(taskIdInt); - if (task == null) return _notFound('Task not found'); + // Start task using TaskManager + await _taskManager.startTask(userId, taskIdInt); - if (!task.isActive) return _badRequest('Task is not available'); - - // Update task status to in_progress - final updatedTask = task.copyWith(status: 'in_progress'); - await isar.writeTxn(() async { - await isar.userTaskModels.put(updatedTask); - }); - - // Update user progress - await _updateUserProgress( - userId.toString(), taskId.toString(), 'in_progress'); - - return _json({ - 'task': updatedTask.toJson(), - 'message': 'Task started successfully' - }); + return _json({'success': true, 'message': 'Task started successfully'}); } catch (e) { log('Error starting task: $e'); - return _serverError('Failed to start task'); + return _serverError('Failed to start task: $e'); } } @@ -184,193 +188,51 @@ class TasksApiV2 { final taskIdInt = int.tryParse(taskId); if (taskIdInt == null) return _badRequest('Invalid task ID'); - final body = await request.readAsString(); - final data = jsonDecode(body) as Map; + // Complete task using TaskManager + await _taskManager.completeTask(userId, taskIdInt); - final proofUrl = data['proofUrl'] as String?; - final notes = data['notes'] as String?; - - final task = await isar.userTaskModels.get(taskIdInt); - if (task == null) return _notFound('Task not found'); - - if (task.status != 'in_progress') { - return _badRequest('Task is not in progress'); - } - - // Update task status to completed - final now = DateTime.now(); - final updatedTask = task.copyWith( - status: 'completed', - completedAt: now, - proofUrl: proofUrl, - ); - - await isar.writeTxn(() async { - await isar.userTaskModels.put(updatedTask); - - // Create task result record - final result = UserTaskResultModel( - taskId: taskId.toString(), - userId: userId.toString(), - status: 'completed', - submittedAt: now, - proofUrl: proofUrl, - notes: notes, - ); - await isar.userTaskResultModels.put(result); - }); - - // Update user progress with rewards - await _updateUserProgress( - userId.toString(), taskId.toString(), 'completed', - rewards: updatedTask.rewards); - - return _json({ - 'task': updatedTask.toJson(), - 'message': 'Task completed successfully', - 'rewards': updatedTask.rewards.map((r) => r.toJson()).toList(), - }); + return _json({'success': true, 'message': 'Task completed successfully'}); } catch (e) { log('Error completing task: $e'); - return _serverError('Failed to complete task'); + return _serverError('Failed to complete task: $e'); } } /// GET /api/v2/users/me/tasks/progress - Get user task progress @Route.get('/users/me/tasks/progress') - Future getUserProgress(Request request) async { + Future getUserTaskProgress(Request request) async { final userId = request.user?.id; if (userId == null) return _unauthorized(); try { - final progress = await isar.userTaskProgressModels - .filter() - .userIdEqualTo(userId.toString()) - .findFirst(); - - if (progress == null) { - // Create initial progress if it doesn't exist - final newProgress = UserTaskProgressModel( - userId: userId.toString(), - taskStatusesJson: '{}', - completedTasksJson: '{}', - totalXp: 0, - totalCoins: 0, - achievementsJson: '[]', - lastUpdated: DateTime.now(), - ); - - await isar.writeTxn(() async { - await isar.userTaskProgressModels.put(newProgress); - }); - - return _json({'progress': newProgress.toJson()}); - } - - return _json({'progress': progress.toJson()}); - } catch (e) { - log('Error fetching user progress: $e'); - return _serverError('Failed to fetch user progress'); - } - } - - /// GET /api/v2/tasks/categories - Get available task categories and filters - @Route.get('/tasks/categories') - Future getTaskCategories(Request request) async { - final userId = request.user?.id; - if (userId == null) return _unauthorized(); - - try { - // Get all tasks to extract categories and filters - final tasks = await isar.userTaskModels.where().findAll(); - - final types = tasks.map((t) => t.type).toSet().toList(); - final difficulties = tasks.map((t) => t.difficulty).toSet().toList(); - final allTags = tasks.expand((t) => t.tags).toSet().toList(); + final progress = await _taskManager.getUserTaskProgress(userId); return _json({ - 'categories': { - 'types': types, - 'difficulties': difficulties, - 'tags': allTags, - } + 'progress': progress.map((p) => { + 'taskId': p.taskId, + 'progress': p.progress, + 'startedAt': p.startedAt.toIso8601String(), + 'updatedAt': p.updatedAt.toIso8601String(), + }).toList() }); + } catch (e) { + log('Error fetching task progress: $e'); + return _serverError('Failed to fetch task progress: $e'); + } + } + + /// GET /api/v2/tasks/categories - Get task categories + @Route.get('/tasks/categories') + Future getTaskCategories(Request request) async { + try { + final categories = await _taskManager.getTaskCategories(); + + return _json({'categories': categories}); } catch (e) { log('Error fetching task categories: $e'); - return _serverError('Failed to fetch task categories'); + return _serverError('Failed to fetch task categories: $e'); } } - /// Helper method to update user progress - Future _updateUserProgress( - String userId, - String taskId, - String status, { - List? rewards, - }) async { - final progress = await isar.userTaskProgressModels - .filter() - .userIdEqualTo(userId) - .findFirst(); - - if (progress == null) return; - - final taskStatuses = Map.from(progress.taskStatuses); - taskStatuses[taskId] = status; - - int totalXp = progress.totalXp; - int totalCoins = progress.totalCoins; - final achievements = List.from(progress.achievements); - - if (status == 'completed' && rewards != null) { - final completedTasks = - Map.from(progress.completedTasks); - completedTasks[taskId] = DateTime.now(); - - for (final reward in rewards) { - switch (reward.type) { - case 'xp': - totalXp += reward.amount; - break; - case 'coins': - totalCoins += reward.amount; - break; - case 'achievement': - if (reward.achievementId != null && - !achievements.contains(reward.achievementId)) { - achievements.add(reward.achievementId!); - } - break; - } - } - - final updatedProgress = progress.copyWith( - taskStatusesJson: - UserTaskProgressModel.taskStatusesToJson(taskStatuses), - completedTasksJson: - UserTaskProgressModel.completedTasksToJson(completedTasks), - totalXp: totalXp, - totalCoins: totalCoins, - achievementsJson: - UserTaskProgressModel.achievementsToJson(achievements), - lastUpdated: DateTime.now(), - ); - - await isar.writeTxn(() async { - await isar.userTaskProgressModels.put(updatedProgress); - }); - } else { - final updatedProgress = progress.copyWith( - taskStatusesJson: - UserTaskProgressModel.taskStatusesToJson(taskStatuses), - lastUpdated: DateTime.now(), - ); - - await isar.writeTxn(() async { - await isar.userTaskProgressModels.put(updatedProgress); - }); - } - } - - Router get router => _$TasksApiV2Router(this); -} + Handler get handler => _$TasksApiV2Router(this); +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/api/v2/tasks_api_v2.g.dart b/mnemo_cards_backend/lib/api/v2/tasks_api_v2.g.dart index 90aae11..c75ad32 100644 --- a/mnemo_cards_backend/lib/api/v2/tasks_api_v2.g.dart +++ b/mnemo_cards_backend/lib/api/v2/tasks_api_v2.g.dart @@ -31,7 +31,7 @@ Router _$TasksApiV2Router(TasksApiV2 service) { router.add( 'GET', r'/users/me/tasks/progress', - service.getUserProgress, + service.getUserTaskProgress, ); router.add( 'GET', diff --git a/mnemo_cards_backend/lib/database/converters.dart b/mnemo_cards_backend/lib/database/converters.dart new file mode 100644 index 0000000..824c32c --- /dev/null +++ b/mnemo_cards_backend/lib/database/converters.dart @@ -0,0 +1,112 @@ +import 'package:drift/drift.dart'; +import 'dart:convert'; + +// Конвертеры для JSON полей - общие для всех таблиц + +class JsonMapConverter extends TypeConverter?, String> { + const JsonMapConverter(); + + @override + Map? fromSql(String? fromDb) { + if (fromDb == null || fromDb.isEmpty || fromDb == '{}') return null; + try { + return json.decode(fromDb) as Map; + } catch (e) { + return null; + } + } + + @override + String toSql(Map? value) { + if (value == null || value.isEmpty) return '{}'; + return json.encode(value); + } +} + +class JsonListConverter extends TypeConverter?, String> { + const JsonListConverter(); + + @override + List? fromSql(String? fromDb) { + if (fromDb == null || fromDb.isEmpty || fromDb == '[]') return null; + try { + return json.decode(fromDb) as List; + } catch (e) { + return null; + } + } + + @override + String toSql(List? value) { + if (value == null || value.isEmpty) return '[]'; + return json.encode(value); + } +} + +class StringListConverter extends TypeConverter, String> { + const StringListConverter(); + + @override + List fromSql(String fromDb) { + if (fromDb.isEmpty || fromDb == '[]') return []; + try { + final decoded = json.decode(fromDb); + return (decoded as List).map((e) => e.toString()).toList(); + } catch (e) { + return []; + } + } + + @override + String toSql(List value) { + if (value.isEmpty) return '[]'; + return json.encode(value); + } +} + +class DateTimeListConverter extends TypeConverter?, String> { + const DateTimeListConverter(); + + @override + List? fromSql(String? fromDb) { + if (fromDb == null || fromDb.isEmpty || fromDb == '[]') return null; + try { + final decoded = json.decode(fromDb) as List; + return decoded.map((e) { + if (e is String) { + return DateTime.parse(e); + } + return DateTime.now(); + }).toList(); + } catch (e) { + return null; + } + } + + @override + String toSql(List? value) { + if (value == null || value.isEmpty) return '[]'; + return json.encode(value.map((e) => e.toIso8601String()).toList()); + } +} + +class IntListConverter extends TypeConverter, String> { + const IntListConverter(); + + @override + List fromSql(String fromDb) { + if (fromDb.isEmpty || fromDb == '[]') return []; + try { + final decoded = json.decode(fromDb); + return (decoded as List).map((e) => e as int).toList(); + } catch (e) { + return []; + } + } + + @override + String toSql(List value) { + if (value.isEmpty) return '[]'; + return json.encode(value); + } +} diff --git a/mnemo_cards_backend/lib/database/daos/achievement_dao.dart b/mnemo_cards_backend/lib/database/daos/achievement_dao.dart new file mode 100644 index 0000000..84d119e --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/achievement_dao.dart @@ -0,0 +1,65 @@ +import 'package:drift/drift.dart'; +import '../database.dart'; +import '../tables/achievements.dart'; + +part 'achievement_dao.g.dart'; + +@DriftAccessor(tables: [UserAchievements]) +class AchievementDao extends DatabaseAccessor with _$AchievementDaoMixin { + AchievementDao(super.db); + + // ==================== UserAchievements ==================== + + /// Получить все достижения пользователя + Future> getUserAchievements(int userId) { + return (select(userAchievements) + ..where((ua) => ua.userId.equals(userId)) + ..orderBy([(ua) => OrderingTerm.desc(ua.unlockedAt)]) + ).get(); + } + + /// Проверить, есть ли у пользователя достижение + Future hasAchievement(int userId, String achievementId) async { + final result = await (select(userAchievements) + ..where((ua) => ua.userId.equals(userId) & ua.achievementId.equals(achievementId)) + ).getSingleOrNull(); + return result != null; + } + + /// Получить достижение пользователя + Future getUserAchievement(int userId, String achievementId) { + return (select(userAchievements) + ..where((ua) => ua.userId.equals(userId) & ua.achievementId.equals(achievementId)) + ).getSingleOrNull(); + } + + /// Создать достижение пользователя + Future unlockAchievement(UserAchievementsCompanion achievement) { + return into(userAchievements).insert(achievement); + } + + /// Обновить прогресс достижения + Future updateAchievementProgress(int userId, String achievementId, double progress) { + return (update(userAchievements) + ..where((ua) => ua.userId.equals(userId) & ua.achievementId.equals(achievementId)) + ).write(UserAchievementsCompanion( + progress: Value(progress), + updatedAt: Value(DateTime.now()), + )); + } + + /// Получить прогресс по всем достижениям пользователя + Future> getAchievementProgress(int userId) async { + final achievements = await getUserAchievements(userId); + return Map.fromEntries( + achievements.map((a) => MapEntry(a.achievementId, a.progress)), + ); + } + + /// Удалить достижение пользователя (для сброса) + Future removeAchievement(int userId, String achievementId) { + (delete(userAchievements) + ..where((ua) => ua.userId.equals(userId) & ua.achievementId.equals(achievementId)) + ).go(); + } +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/database/daos/achievement_dao.g.dart b/mnemo_cards_backend/lib/database/daos/achievement_dao.g.dart new file mode 100644 index 0000000..ef69513 --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/achievement_dao.g.dart @@ -0,0 +1,10 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'achievement_dao.dart'; + +// ignore_for_file: type=lint +mixin _$AchievementDaoMixin on DatabaseAccessor { + $UsersTable get users => attachedDatabase.users; + $UserAchievementsTable get userAchievements => + attachedDatabase.userAchievements; +} diff --git a/mnemo_cards_backend/lib/database/daos/discount_dao.dart b/mnemo_cards_backend/lib/database/daos/discount_dao.dart new file mode 100644 index 0000000..63a8b11 --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/discount_dao.dart @@ -0,0 +1,106 @@ +import 'package:drift/drift.dart'; +import '../database.dart'; +import '../tables/discounts.dart'; +import '../tables/users.dart'; + +part 'discount_dao.g.dart'; + +@DriftAccessor(tables: [DiscountCampaigns, Discounts, DiscountUserDatas]) +class DiscountDao extends DatabaseAccessor with _$DiscountDaoMixin { + DiscountDao(super.db); + + // ==================== DiscountCampaigns ==================== + + /// Получить кампанию по ID + Future getCampaignById(int id) { + return (select(db.discountCampaigns)..where((c) => c.id.equals(id))).getSingleOrNull(); + } + + /// Получить все активные кампании + Future> getActiveCampaigns() { + final now = DateTime.now(); + return (select(db.discountCampaigns) + ..where((c) => c.status.equals('active')) + ..where((c) => c.start.isSmallerOrEqualValue(now)) + ..where((c) => c.finish.isBiggerOrEqualValue(now)) + ..where((c) => c.isDeleted.equals(false)) + ).get(); + } + + /// Создать кампанию + Future createCampaign(DiscountCampaignsCompanion campaign) { + return into(db.discountCampaigns).insert(campaign); + } + + /// Обновить кампанию + Future updateCampaign(DiscountCampaign campaign) { + return update(db.discountCampaigns).replace(campaign); + } + + // ==================== Discounts ==================== + + /// Получить скидку по ID + Future getDiscountById(int id) { + return (select(db.discounts)..where((d) => d.id.equals(id))).getSingleOrNull(); + } + + /// Получить скидки кампании + Future> getDiscountsByCampaignId(int campaignId) { + return (select(db.discounts) + ..where((d) => d.campaignId.equals(campaignId)) + ..where((d) => d.isDeleted.equals(false)) + ).get(); + } + + /// Создать скидку + Future createDiscount(DiscountsCompanion discount) { + return into(db.discounts).insert(discount); + } + + /// Обновить скидку + Future updateDiscount(Discount discount) { + return update(db.discounts).replace(discount); + } + + // ==================== DiscountUserDatas ==================== + + /// Получить скидки пользователя + Future> getUserDiscounts(int userId) async { + final query = select(db.discounts).join([ + innerJoin( + db.discountUserDatas, + db.discountUserDatas.discountId.equalsExp(db.discounts.id) & + db.discountUserDatas.userId.equals(userId), + ), + ]); + + return query.map((row) => row.readTable(db.discounts)).get(); + } + + /// Дать пользователю доступ к скидке + Future grantDiscountToUser(int userId, int discountId) async { + await into(db.discountUserDatas).insert( + DiscountUserDatasCompanion.insert( + userId: userId, + discountId: discountId, + ), + mode: InsertMode.insertOrIgnore, + ); + } + + /// Отозвать скидку у пользователя + Future revokeDiscountFromUser(int userId, int discountId) async { + await (delete(db.discountUserDatas) + ..where((dud) => dud.userId.equals(userId) & dud.discountId.equals(discountId)) + ).go(); + } + + /// Проверить, есть ли у пользователя доступ к скидке + Future hasDiscountAccess(int userId, int discountId) async { + final query = select(db.discountUserDatas) + ..where((dud) => dud.userId.equals(userId) & dud.discountId.equals(discountId)); + + final result = await query.getSingleOrNull(); + return result != null; + } +} diff --git a/mnemo_cards_backend/lib/database/daos/discount_dao.g.dart b/mnemo_cards_backend/lib/database/daos/discount_dao.g.dart new file mode 100644 index 0000000..40a87c1 --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/discount_dao.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'discount_dao.dart'; + +// ignore_for_file: type=lint +mixin _$DiscountDaoMixin on DatabaseAccessor { + $DiscountCampaignsTable get discountCampaigns => + attachedDatabase.discountCampaigns; + $DiscountsTable get discounts => attachedDatabase.discounts; + $UsersTable get users => attachedDatabase.users; + $DiscountUserDatasTable get discountUserDatas => + attachedDatabase.discountUserDatas; +} diff --git a/mnemo_cards_backend/lib/database/daos/pack_dao.dart b/mnemo_cards_backend/lib/database/daos/pack_dao.dart new file mode 100644 index 0000000..91bbcde --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/pack_dao.dart @@ -0,0 +1,294 @@ +import 'package:drift/drift.dart'; +import '../database.dart'; +import '../tables/packs.dart'; +import '../tables/relations.dart'; + +part 'pack_dao.g.dart'; + +@DriftAccessor(tables: [CardPacks, GameCards, VoiceModels, PreviewCards, CardPackCards, CardVoices]) +class PackDao extends DatabaseAccessor with _$PackDaoMixin { + PackDao(super.db); + + // ==================== CardPacks ==================== + + /// Получить пак по ID + Future getPackById(int id) { + return (select(cardPacks)..where((p) => p.id.equals(id))).getSingleOrNull(); + } + + /// Получить все паки + Future> getAllPacks({ + bool enabledOnly = false, + String? orderByField, + bool orderDesc = false, + }) { + final query = select(cardPacks); + + if (enabledOnly) { + query.where((p) => p.enabled.equals(true)); + } + + query.where((p) => p.isDeleted.equals(false)); + + if (orderByField == 'order') { + if (orderDesc) { + query.orderBy([(p) => OrderingTerm.desc(p.order)]); + } else { + query.orderBy([(p) => OrderingTerm.asc(p.order)]); + } + } else { + query.orderBy([(p) => OrderingTerm.asc(p.order)]); + } + + return query.get(); + } + + /// Создать пак + Future createPack(CardPacksCompanion pack) { + return into(cardPacks).insert(pack); + } + + /// Обновить пак + Future updatePack(CardPack pack) { + return update(cardPacks).replace(pack); + } + + /// Обновить пак частично + Future updatePackPartial(CardPacksCompanion updates) { + final packId = updates.id.value; + if (packId == null) throw ArgumentError('Pack ID is required'); + + return (update(cardPacks)..where((p) => p.id.equals(packId))) + .write(updates.copyWith(updatedAt: Value(DateTime.now()))); + } + + /// Удалить пак (soft delete) + Future softDeletePack(int packId) { + return (update(cardPacks)..where((p) => p.id.equals(packId))) + .write(CardPacksCompanion( + isDeleted: const Value(true), + updatedAt: Value(DateTime.now()), + )); + } + + /// Подсчитать паки + Future countPacks({bool enabledOnly = false}) async { + final countExpr = cardPacks.id.count(); + final query = selectOnly(cardPacks)..addColumns([countExpr]); + + if (enabledOnly) { + query.where(cardPacks.enabled.equals(true)); + } + query.where(cardPacks.isDeleted.equals(false)); + + return await query.map((row) => row.read(countExpr)!).getSingle(); + } + + // ==================== GameCards ==================== + + /// Получить карточку по ID + Future getCardById(int id) { + return (select(gameCards)..where((c) => c.id.equals(id))).getSingleOrNull(); + } + + /// Получить все карточки пака + Future> getPackCards(int packId) { + return (select(gameCards) + ..where((c) => c.packId.equals(packId)) + ..orderBy([(c) => OrderingTerm.asc(c.id)]) // TODO: use proper ordering + ).get(); + } + + /// Получить карточки по списку ID + Future> getCardsByIds(List ids) { + if (ids.isEmpty) return Future.value([]); + return (select(gameCards)..where((c) => c.id.isIn(ids))).get(); + } + + /// Создать карточку + Future createCard(GameCardsCompanion card) { + return into(gameCards).insert(card); + } + + /// Обновить карточку + Future updateCard(GameCard card) { + return update(gameCards).replace(card); + } + + /// Удалить карточку (soft delete) + Future softDeleteCard(int cardId) { + return (update(gameCards)..where((c) => c.id.equals(cardId))) + .write(GameCardsCompanion( + isDeleted: const Value(true), + updatedAt: Value(DateTime.now()), + )); + } + + /// Удалить карточку (hard delete) + Future deleteCard(int cardId) { + return (delete(gameCards)..where((c) => c.id.equals(cardId))).go(); + } + + /// Получить все карточки с пагинацией + Future> getAllCards({int? limit, int? offset}) { + final query = select(gameCards); + if (limit != null) { + query.limit(limit, offset: offset); + } + return query.get(); + } + + /// Подсчитать карточки + Future countCards() async { + final countExpr = gameCards.id.count(); + final query = selectOnly(gameCards)..addColumns([countExpr]); + return await query.map((row) => row.read(countExpr)!).getSingle(); + } + + /// Добавить карточку в пак + Future addCardToPack({ + required int packId, + required int cardId, + int order = 0, + }) async { + await into(cardPackCards).insert( + CardPackCardsCompanion.insert( + packId: packId, + cardId: cardId, + order: Value(order), + ), + mode: InsertMode.insertOrIgnore, + ); + } + + /// Удалить карточку из пака + Future removeCardFromPack(int packId, int cardId) async { + await (delete(cardPackCards) + ..where((cpc) => cpc.packId.equals(packId) & cpc.cardId.equals(cardId)) + ).go(); + } + + /// Обновить порядок карточек в паке + Future updatePackCardsOrder(int packId, List cardIds) async { + await transaction(() async { + // Удалить старые связи + await (delete(cardPackCards) + ..where((cpc) => cpc.packId.equals(packId)) + ).go(); + + // Создать новые связи с порядком + for (var i = 0; i < cardIds.length; i++) { + await into(cardPackCards).insert( + CardPackCardsCompanion.insert( + packId: packId, + cardId: cardIds[i], + order: Value(i), + ), + ); + } + }); + } + + // ==================== PreviewCards ==================== + + /// Получить preview карточки пака + Future> getPreviewCards(int packId) async { + final query = select(gameCards).join([ + innerJoin( + db.previewCards, + db.previewCards.cardId.equalsExp(gameCards.id) & + db.previewCards.packId.equals(packId), + ), + ]); + + final results = await query.get(); + // Сортируем вручную по order из junction table + results.sort((a, b) { + final orderA = a.read(db.previewCards.order) ?? 0; + final orderB = b.read(db.previewCards.order) ?? 0; + return orderA.compareTo(orderB); + }); + + return results.map((row) => row.readTable(gameCards)).toList(); + } + + /// Установить preview карточки для пака + Future setPreviewCards(int packId, List cardIds) async { + await transaction(() async { + // Удалить старые preview карточки + await (delete(previewCards) + ..where((pc) => pc.packId.equals(packId)) + ).go(); + + // Добавить новые + for (var i = 0; i < cardIds.length; i++) { + await into(previewCards).insert( + PreviewCardsCompanion.insert( + packId: packId, + cardId: cardIds[i], + order: Value(i), + ), + ); + } + }); + } + + // ==================== VoiceModels ==================== + + /// Получить голосовые модели карточки + Future> getCardVoices(int cardId) async { + final query = select(voiceModels).join([ + innerJoin( + cardVoices, + cardVoices.voiceId.equalsExp(voiceModels.id) & + cardVoices.cardId.equals(cardId), + ), + ]); + + return query.map((row) => row.readTable(voiceModels)).get(); + } + + /// Добавить голосовую модель к карточке + Future addVoiceToCard(int cardId, int voiceId) async { + await into(cardVoices).insert( + CardVoicesCompanion.insert( + cardId: cardId, + voiceId: voiceId, + ), + mode: InsertMode.insertOrIgnore, + ); + } + + /// Удалить голосовую модель из карточки + Future removeVoiceFromCard(int cardId, int voiceId) async { + await (delete(cardVoices) + ..where((cv) => cv.cardId.equals(cardId) & cv.voiceId.equals(voiceId)) + ).go(); + } + + /// Создать голосовую модель + Future createVoice(VoiceModelsCompanion voice) { + return into(voiceModels).insert(voice); + } + + /// Получить голосовую модель по ID + Future getVoiceById(int id) { + return (select(voiceModels)..where((v) => v.id.equals(id))).getSingleOrNull(); + } + + /// Подсчитать все паки + Future countPacks() async { + final countExpr = cardPacks.id.count(); + final query = selectOnly(cardPacks)..addColumns([countExpr]); + + return await query.map((row) => row.read(countExpr)!).getSingle(); + } + + /// Подсчитать все карточки + Future countCards() async { + final countExpr = gameCards.id.count(); + final query = selectOnly(gameCards)..addColumns([countExpr]); + + return await query.map((row) => row.read(countExpr)!).getSingle(); + } +} diff --git a/mnemo_cards_backend/lib/database/daos/pack_dao.g.dart b/mnemo_cards_backend/lib/database/daos/pack_dao.g.dart new file mode 100644 index 0000000..b2fc285 --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/pack_dao.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'pack_dao.dart'; + +// ignore_for_file: type=lint +mixin _$PackDaoMixin on DatabaseAccessor { + $CardPacksTable get cardPacks => attachedDatabase.cardPacks; + $GameCardsTable get gameCards => attachedDatabase.gameCards; + $VoiceModelsTable get voiceModels => attachedDatabase.voiceModels; + $PreviewCardsTable get previewCards => attachedDatabase.previewCards; + $CardPackCardsTable get cardPackCards => attachedDatabase.cardPackCards; + $CardVoicesTable get cardVoices => attachedDatabase.cardVoices; +} diff --git a/mnemo_cards_backend/lib/database/daos/payment_dao.dart b/mnemo_cards_backend/lib/database/daos/payment_dao.dart new file mode 100644 index 0000000..f98ea27 --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/payment_dao.dart @@ -0,0 +1,133 @@ +import 'package:drift/drift.dart'; +import '../database.dart'; +import '../tables/payments.dart'; +import '../tables/users.dart'; + +part 'payment_dao.g.dart'; + +@DriftAccessor(tables: [Payments]) +class PaymentDao extends DatabaseAccessor with _$PaymentDaoMixin { + PaymentDao(super.db); + + /// Получить платеж по ID + Future getPaymentById(int id) { + return (select(payments)..where((p) => p.id.equals(id))).getSingleOrNull(); + } + + /// Получить платежи пользователя + Future> getPaymentsByUserId(int userId, { + int? limit, + int? offset, + }) { + final query = select(payments) + ..where((p) => p.userId.equals(userId)) + ..orderBy([(p) => OrderingTerm.desc(p.date)]); + + if (limit != null) { + query.limit(limit, offset: offset); + } + + return query.get(); + } + + /// Получить платежи по статусу + Future> getPaymentsByStatus(String status) { + return (select(payments) + ..where((p) => p.status.equals(status)) + ..orderBy([(p) => OrderingTerm.desc(p.date)]) + ).get(); + } + + /// Создать платеж + Future createPayment(PaymentsCompanion payment) { + return into(payments).insert(payment); + } + + /// Обновить платеж + Future updatePayment(Payment payment) { + return update(payments).replace(payment); + } + + /// Обновить платеж частично + Future updatePaymentCompanion(PaymentsCompanion companion) { + final paymentId = companion.id.value; + if (paymentId == null) throw ArgumentError('Payment ID is required'); + + return (update(payments)..where((p) => p.id.equals(paymentId))) + .write(companion); + } + + /// Обновить статус платежа + Future updatePaymentStatus(int paymentId, String status) { + return (update(payments)..where((p) => p.id.equals(paymentId))) + .write(PaymentsCompanion( + status: Value(status), + updatedAt: Value(DateTime.now()), + )); + } + + /// Подсчитать платежи пользователя + Future countPaymentsByUserId(int userId) async { + final countExpr = payments.id.count(); + final query = selectOnly(payments) + ..addColumns([countExpr]) + ..where(payments.userId.equals(userId)); + + return await query.map((row) => row.read(countExpr)!).getSingle(); + } + + /// Получить платеж по externalToken + Future getPaymentByExternalToken(String token) { + return (select(payments)..where((p) => p.externalToken.equals(token))) + .getSingleOrNull(); + } + + /// Получить платежи по продукту (store ID) + Future> getPaymentsByProduct(String productId) async { + // Поиск по продуктам в JSON массиве + final query = select(payments) + ..where((p) => p.products.like('%$productId%')); + + return query.get(); + } + + /// Получить платежи по статусу с пагинацией + Future> getPaymentsByStatusPaged( + String status, { + int? limit, + int? offset, + }) { + final query = select(payments) + ..where((p) => p.status.equals(status)) + ..orderBy([(p) => OrderingTerm.desc(p.date)]); + + if (limit != null) { + query.limit(limit, offset: offset); + } + + return query.get(); + } + + /// Получить все платежи (для админки) + Future> getAllPayments({ + int? limit, + int? offset, + }) { + final query = select(payments) + ..orderBy([(p) => OrderingTerm.desc(p.date)]); + + if (limit != null) { + query.limit(limit, offset: offset); + } + + return query.get(); + } + + /// Подсчитать все платежи + Future countAllPayments() async { + final countExpr = payments.id.count(); + final query = selectOnly(payments)..addColumns([countExpr]); + + return await query.map((row) => row.read(countExpr)!).getSingle(); + } +} diff --git a/mnemo_cards_backend/lib/database/daos/payment_dao.g.dart b/mnemo_cards_backend/lib/database/daos/payment_dao.g.dart new file mode 100644 index 0000000..ec2fcae --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/payment_dao.g.dart @@ -0,0 +1,9 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'payment_dao.dart'; + +// ignore_for_file: type=lint +mixin _$PaymentDaoMixin on DatabaseAccessor { + $UsersTable get users => attachedDatabase.users; + $PaymentsTable get payments => attachedDatabase.payments; +} diff --git a/mnemo_cards_backend/lib/database/daos/promo_code_dao.dart b/mnemo_cards_backend/lib/database/daos/promo_code_dao.dart new file mode 100644 index 0000000..afa241a --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/promo_code_dao.dart @@ -0,0 +1,91 @@ +import 'package:drift/drift.dart'; +import '../database.dart'; +import '../tables/promo_codes.dart'; +import '../tables/users.dart'; + +part 'promo_code_dao.g.dart'; + +@DriftAccessor(tables: [PromoCodesCampaigns, PromoCodes]) +class PromoCodeDao extends DatabaseAccessor with _$PromoCodeDaoMixin { + PromoCodeDao(super.db); + + // ==================== PromoCodesCampaigns ==================== + + /// Получить кампанию по ID + Future getCampaignById(int id) { + return (select(db.promoCodesCampaigns)..where((c) => c.id.equals(id))).getSingleOrNull(); + } + + /// Получить все активные кампании + Future> getActiveCampaigns() { + final now = DateTime.now(); + return (select(db.promoCodesCampaigns) + ..where((c) => c.status.equals('active')) + ..where((c) => c.start.isSmallerOrEqualValue(now)) + ..where((c) => c.finish.isBiggerOrEqualValue(now)) + ..where((c) => c.isDeleted.equals(false)) + ).get(); + } + + /// Создать кампанию + Future createCampaign(PromoCodesCampaignsCompanion campaign) { + return into(db.promoCodesCampaigns).insert(campaign); + } + + /// Обновить кампанию + Future updateCampaign(PromoCodesCampaign campaign) { + return update(db.promoCodesCampaigns).replace(campaign); + } + + // ==================== PromoCodes ==================== + + /// Получить промокод по коду + Future getPromoCodeByCode(String code) { + return (select(db.promoCodes) + ..where((pc) => pc.code.equals(code)) + ).getSingleOrNull(); + } + + /// Получить промокоды кампании + Future> getPromoCodesByCampaignId(int campaignId) { + return (select(db.promoCodes) + ..where((pc) => pc.campaignId.equals(campaignId)) + ).get(); + } + + /// Получить индивидуальные промокоды пользователя + Future> getUserPromoCodes(int userId) { + return (select(db.promoCodes) + ..where((pc) => pc.userId.equals(userId)) + ).get(); + } + + /// Создать промокод + Future createPromoCode(PromoCodesCompanion promoCode) { + return into(db.promoCodes).insert(promoCode); + } + + /// Обновить промокод + Future updatePromoCode(PromoCode promoCode) { + return update(db.promoCodes).replace(promoCode); + } + + /// Увеличить счетчик активаций + Future incrementActivations(int promoCodeId) async { + final code = await (select(db.promoCodes) + ..where((pc) => pc.id.equals(promoCodeId)) + ).getSingleOrNull(); + + if (code != null) { + await update(db.promoCodes).replace(code.copyWith( + activations: code.activations + 1, + updatedAt: DateTime.now(), + )); + } + } + + /// Удалить промокод + Future deletePromoCode(int promoCodeId) { + return (delete(db.promoCodes)..where((pc) => pc.id.equals(promoCodeId))).go(); + } +} diff --git a/mnemo_cards_backend/lib/database/daos/promo_code_dao.g.dart b/mnemo_cards_backend/lib/database/daos/promo_code_dao.g.dart new file mode 100644 index 0000000..5bc8ed1 --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/promo_code_dao.g.dart @@ -0,0 +1,11 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'promo_code_dao.dart'; + +// ignore_for_file: type=lint +mixin _$PromoCodeDaoMixin on DatabaseAccessor { + $PromoCodesCampaignsTable get promoCodesCampaigns => + attachedDatabase.promoCodesCampaigns; + $UsersTable get users => attachedDatabase.users; + $PromoCodesTable get promoCodes => attachedDatabase.promoCodes; +} diff --git a/mnemo_cards_backend/lib/database/daos/statistics_dao.dart b/mnemo_cards_backend/lib/database/daos/statistics_dao.dart new file mode 100644 index 0000000..ad7f61d --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/statistics_dao.dart @@ -0,0 +1,96 @@ +import 'package:drift/drift.dart'; +import '../database.dart'; +import '../tables/statistics.dart'; +import '../tables/users.dart'; + +part 'statistics_dao.g.dart'; + +@DriftAccessor(tables: [StudySessions]) +class StatisticsDao extends DatabaseAccessor with _$StatisticsDaoMixin { + StatisticsDao(super.db); + + /// Получить сессию по ID + Future getSessionById(int id) { + return (select(studySessions)..where((s) => s.id.equals(id))).getSingleOrNull(); + } + + /// Получить сессию по sessionId + Future getSessionBySessionId(String sessionId) { + return (select(studySessions) + ..where((s) => s.sessionId.equals(sessionId)) + ).getSingleOrNull(); + } + + /// Получить активные сессии пользователя + Future> getActiveSessions(int userId) { + return (select(studySessions) + ..where((s) => s.userId.equals(userId)) + ..where((s) => s.endTime.isNull()) + ..orderBy([(s) => OrderingTerm.desc(s.startTime)]) + ).get(); + } + + /// Получить сессии пользователя + Future> getSessionsByUserId(int userId, { + int? limit, + int? offset, + DateTime? fromDate, + DateTime? toDate, + }) { + final query = select(studySessions) + ..where((s) => s.userId.equals(userId)) + ..orderBy([(s) => OrderingTerm.desc(s.startTime)]); + + if (fromDate != null) { + query.where((s) => s.startTime.isBiggerOrEqualValue(fromDate)); + } + if (toDate != null) { + query.where((s) => s.startTime.isSmallerOrEqualValue(toDate)); + } + + if (limit != null) { + query.limit(limit, offset: offset); + } + + return query.get(); + } + + /// Создать сессию + Future createSession(StudySessionsCompanion session) { + return into(studySessions).insert(session); + } + + /// Обновить сессию + Future updateSession(StudySession session) { + return update(studySessions).replace(session); + } + + /// Завершить сессию + Future endSession(int sessionId, { + int? wordsLearned, + int? testsCompleted, + double? accuracy, + }) { + final updates = StudySessionsCompanion( + id: Value(sessionId), + endTime: Value(DateTime.now()), + updatedAt: Value(DateTime.now()), + wordsLearned: wordsLearned != null ? Value(wordsLearned) : const Value.absent(), + testsCompleted: testsCompleted != null ? Value(testsCompleted) : const Value.absent(), + accuracy: accuracy != null ? Value(accuracy) : const Value.absent(), + ); + + return (update(studySessions)..where((s) => s.id.equals(sessionId))) + .write(updates); + } + + /// Подсчитать сессии пользователя + Future countSessionsByUserId(int userId) async { + final countExpr = studySessions.id.count(); + final query = selectOnly(studySessions) + ..addColumns([countExpr]) + ..where(studySessions.userId.equals(userId)); + + return await query.map((row) => row.read(countExpr)!).getSingle(); + } +} diff --git a/mnemo_cards_backend/lib/database/daos/statistics_dao.g.dart b/mnemo_cards_backend/lib/database/daos/statistics_dao.g.dart new file mode 100644 index 0000000..f307fd8 --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/statistics_dao.g.dart @@ -0,0 +1,9 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'statistics_dao.dart'; + +// ignore_for_file: type=lint +mixin _$StatisticsDaoMixin on DatabaseAccessor { + $UsersTable get users => attachedDatabase.users; + $StudySessionsTable get studySessions => attachedDatabase.studySessions; +} diff --git a/mnemo_cards_backend/lib/database/daos/subscription_dao.dart b/mnemo_cards_backend/lib/database/daos/subscription_dao.dart new file mode 100644 index 0000000..8279861 --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/subscription_dao.dart @@ -0,0 +1,89 @@ +import 'package:drift/drift.dart'; +import '../database.dart'; +import '../tables/subscriptions.dart'; +import '../tables/users.dart'; + +part 'subscription_dao.g.dart'; + +@DriftAccessor(tables: [SubscriptionPlans, UserSubscriptions]) +class SubscriptionDao extends DatabaseAccessor with _$SubscriptionDaoMixin { + SubscriptionDao(super.db); + + // ==================== SubscriptionPlans ==================== + + /// Получить план подписки по ID + Future getPlanById(int id) { + return (select(subscriptionPlans)..where((p) => p.id.equals(id))).getSingleOrNull(); + } + + /// Получить все планы подписки + Future> getAllPlans() { + return (select(subscriptionPlans) + ..where((p) => p.isDeleted.equals(false)) + ).get(); + } + + /// Создать план подписки + Future createPlan(SubscriptionPlansCompanion plan) { + return into(subscriptionPlans).insert(plan); + } + + /// Обновить план подписки + Future updatePlan(SubscriptionPlan plan) { + return update(subscriptionPlans).replace(plan); + } + + // ==================== UserSubscriptions ==================== + + /// Получить подписку пользователя + Future getUserSubscription(int userId) { + return (select(userSubscriptions) + ..where((us) => us.userId.equals(userId)) + ..orderBy([(us) => OrderingTerm.desc(us.finish)]) + ).getSingleOrNull(); + } + + /// Получить активную подписку пользователя + Future getActiveSubscription(int userId) async { + final now = DateTime.now(); + return (select(userSubscriptions) + ..where((us) => us.userId.equals(userId)) + ..where((us) => us.start.isSmallerThanValue(now)) + ..where((us) => us.finish.isBiggerThanValue(now)) + ..orderBy([(us) => OrderingTerm.desc(us.finish)]) + ).getSingleOrNull(); + } + + /// Проверить, есть ли у пользователя активная подписка + Future hasActiveSubscription(int userId) async { + final subscription = await getActiveSubscription(userId); + return subscription != null; + } + + /// Создать подписку пользователя + Future createUserSubscription(UserSubscriptionsCompanion subscription) { + return into(userSubscriptions).insert(subscription); + } + + /// Обновить подписку пользователя + Future updateUserSubscription(UserSubscription subscription) { + return update(userSubscriptions).replace(subscription); + } + + /// Удалить подписку пользователя + Future deleteUserSubscription(int userId) { + return (delete(userSubscriptions) + ..where((us) => us.userId.equals(userId)) + ).go(); + } + + /// Получить всех пользователей с активными подписками + Future> getActiveSubscriptions() async { + final now = DateTime.now(); + return (select(userSubscriptions) + ..where((us) => us.start.isSmallerThanValue(now)) + ..where((us) => us.finish.isBiggerThanValue(now)) + ..orderBy([(us) => OrderingTerm.desc(us.finish)]) + ).get(); + } +} diff --git a/mnemo_cards_backend/lib/database/daos/subscription_dao.g.dart b/mnemo_cards_backend/lib/database/daos/subscription_dao.g.dart new file mode 100644 index 0000000..f08b1a7 --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/subscription_dao.g.dart @@ -0,0 +1,12 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'subscription_dao.dart'; + +// ignore_for_file: type=lint +mixin _$SubscriptionDaoMixin on DatabaseAccessor { + $SubscriptionPlansTable get subscriptionPlans => + attachedDatabase.subscriptionPlans; + $UsersTable get users => attachedDatabase.users; + $UserSubscriptionsTable get userSubscriptions => + attachedDatabase.userSubscriptions; +} diff --git a/mnemo_cards_backend/lib/database/daos/task_dao.dart b/mnemo_cards_backend/lib/database/daos/task_dao.dart new file mode 100644 index 0000000..5de13da --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/task_dao.dart @@ -0,0 +1,128 @@ +import 'package:drift/drift.dart'; +import '../database.dart'; +// import '../tables/tasks.dart'; // Causes conflict, using database.dart instead +import '../tables/users.dart'; + +part 'task_dao.g.dart'; + +@DriftAccessor() +class TaskDao extends DatabaseAccessor with _$TaskDaoMixin { + TaskDao(super.db); + + // ==================== Tasks ==================== + + /// Получить задачу по ID + Future getTaskById(int id) { + return (select(db.tasks)..where((t) => t.id.equals(id))).getSingleOrNull(); + } + + /// Получить все задачи + Future> getAllTasks() { + return select(db.tasks).get(); + } + + /// Создать задачу + Future createTask(TasksCompanion task) { + return into(db.tasks).insert(task); + } + + /// Обновить задачу + Future updateTask(Task task) { + return update(db.tasks).replace(task); + } + + /// Обновить время последнего выполнения + Future updateLastExecution(int taskId) { + return (update(db.tasks)..where((t) => t.id.equals(taskId))) + .write(TasksCompanion( + lastExecution: Value(DateTime.now()), + updatedAt: Value(DateTime.now()), + )); + } + + // ==================== UserTasks ==================== + + /// Получить задачу пользователя по ID + Future getUserTaskById(int id) { + return (select(db.userTasks)..where((ut) => ut.id.equals(id))).getSingleOrNull(); + } + + /// Получить задачи пользователя + /// Note: UserTasks table doesn't have userId column directly + /// This method needs to be implemented based on actual schema + Future> getUserTasks(int userId, { + String? status, + bool activeOnly = false, + }) { + // TODO: Implement based on actual UserTasks schema + // For now, return all tasks + final query = select(db.userTasks); + + if (status != null) { + query.where((ut) => ut.status.equals(status)); + } + + if (activeOnly) { + final now = DateTime.now(); + query.where((ut) => ut.expiresAt.isBiggerThanValue(now)); + } + + query.orderBy([(ut) => OrderingTerm.desc(ut.createdAt)]); + + return query.get(); + } + + /// Создать задачу пользователя + Future createUserTask(UserTasksCompanion task) { + return into(db.userTasks).insert(task); + } + + /// Обновить задачу пользователя + Future updateUserTask(UserTask task) { + return update(db.userTasks).replace(task); + } + + /// Завершить задачу пользователя + Future completeUserTask(int taskId) { + return (update(db.userTasks)..where((ut) => ut.id.equals(taskId))) + .write(UserTasksCompanion( + status: const Value('completed'), + completedAt: Value(DateTime.now()), + updatedAt: Value(DateTime.now()), + )); + } + + // ==================== UserTaskProgresses ==================== + + /// Получить прогресс задачи пользователя + Future getTaskProgress(int userId, int taskId) { + return (select(db.userTaskProgresses) + ..where((utp) => utp.userId.equals(userId) & utp.taskId.equals(taskId)) + ).getSingleOrNull(); + } + + /// Создать прогресс задачи + Future createTaskProgress(UserTaskProgressesCompanion progress) { + return into(db.userTaskProgresses).insert(progress); + } + + /// Обновить прогресс задачи + Future updateTaskProgress(UserTaskProgresses progress) { + return update(db.userTaskProgresses).replace(progress); + } + + // ==================== UserTaskResults ==================== + + /// Получить результаты задач пользователя + Future> getTaskResults(int userId) { + return (select(db.userTaskResults) + ..where((utr) => utr.userId.equals(userId)) + ..orderBy([(utr) => OrderingTerm.desc(utr.completedAt)]) + ).get(); + } + + /// Создать результат задачи + Future createTaskResult(UserTaskResultsCompanion result) { + return into(db.userTaskResults).insert(result); + } +} diff --git a/mnemo_cards_backend/lib/database/daos/task_dao.g.dart b/mnemo_cards_backend/lib/database/daos/task_dao.g.dart new file mode 100644 index 0000000..4ba9d4a --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/task_dao.g.dart @@ -0,0 +1,6 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'task_dao.dart'; + +// ignore_for_file: type=lint +mixin _$TaskDaoMixin on DatabaseAccessor {} diff --git a/mnemo_cards_backend/lib/database/daos/test_dao.dart b/mnemo_cards_backend/lib/database/daos/test_dao.dart new file mode 100644 index 0000000..3992bfb --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/test_dao.dart @@ -0,0 +1,111 @@ +import 'package:drift/drift.dart'; +import '../database.dart'; +import '../tables/tests.dart'; +import '../tables/packs.dart'; + +part 'test_dao.g.dart'; + +@DriftAccessor(tables: [Tests, TestQuestions, TestPackRelations, TestStatistics]) +class TestDao extends DatabaseAccessor with _$TestDaoMixin { + TestDao(super.db); + + // ==================== Tests ==================== + + /// Получить тест по ID + Future getTestById(int id) { + return (select(tests)..where((t) => t.id.equals(id))).getSingleOrNull(); + } + + /// Получить все тесты + Future> getAllTests() { + return (select(tests) + ..where((t) => t.isDeleted.equals(false)) + ).get(); + } + + /// Получить тесты пака + Future> getTestsByPackId(int packId) async { + final query = select(tests).join([ + innerJoin( + testPackRelations, + testPackRelations.testId.equalsExp(tests.id) & + testPackRelations.packId.equals(packId), + ), + ]); + + return query.map((row) => row.readTable(tests)).get(); + } + + /// Создать тест + Future createTest(TestsCompanion test) { + return into(tests).insert(test); + } + + /// Обновить тест + Future updateTest(Test test) { + return update(tests).replace(test); + } + + /// Удалить тест (soft delete) + Future softDeleteTest(int testId) { + return (update(tests)..where((t) => t.id.equals(testId))) + .write(TestsCompanion( + isDeleted: const Value(true), + updatedAt: Value(DateTime.now()), + )); + } + + /// Связать тест с паком + Future linkTestToPack(int testId, int packId) async { + await into(testPackRelations).insert( + TestPackRelationsCompanion.insert( + testId: testId, + packId: packId, + ), + mode: InsertMode.insertOrIgnore, + ); + } + + // ==================== TestQuestions ==================== + + /// Получить вопросы теста + Future> getTestQuestions(int testId) { + return (select(testQuestions) + ..where((tq) => tq.testId.equals(testId)) + ).get(); + } + + /// Создать вопрос теста + Future createTestQuestion(TestQuestionsCompanion question) { + return into(testQuestions).insert(question); + } + + /// Обновить вопрос теста + Future updateTestQuestion(TestQuestion question) { + return update(testQuestions).replace(question); + } + + /// Удалить вопрос теста + Future deleteTestQuestion(int questionId) { + return (delete(testQuestions)..where((tq) => tq.id.equals(questionId))).go(); + } + + // ==================== TestStatistics ==================== + + /// Получить статистику теста пользователя + Future getTestStatistics(int userId, int testId) { + return (select(testStatistics) + ..where((ts) => ts.userId.equals(userId) & ts.testId.equals(testId)) + ).getSingleOrNull(); + } + + /// Создать статистику теста + Future createTestStatistics(TestStatisticsCompanion statistics) { + return into(testStatistics).insert(statistics); + } + + /// Обновить статистику теста + Future updateTestStatistics(TestStatistic statistics) { + return update(testStatistics).replace(statistics); + } +} diff --git a/mnemo_cards_backend/lib/database/daos/test_dao.g.dart b/mnemo_cards_backend/lib/database/daos/test_dao.g.dart new file mode 100644 index 0000000..1dfe35a --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/test_dao.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'test_dao.dart'; + +// ignore_for_file: type=lint +mixin _$TestDaoMixin on DatabaseAccessor { + $TestsTable get tests => attachedDatabase.tests; + $TestQuestionsTable get testQuestions => attachedDatabase.testQuestions; + $CardPacksTable get cardPacks => attachedDatabase.cardPacks; + $TestPackRelationsTable get testPackRelations => + attachedDatabase.testPackRelations; + $TestStatisticsTable get testStatistics => attachedDatabase.testStatistics; +} diff --git a/mnemo_cards_backend/lib/database/daos/user_dao.dart b/mnemo_cards_backend/lib/database/daos/user_dao.dart new file mode 100644 index 0000000..e8baa9e --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/user_dao.dart @@ -0,0 +1,326 @@ +import 'package:drift/drift.dart'; +import '../database.dart'; +import '../tables/users.dart'; +import '../tables/auth.dart'; +import '../tables/packs.dart'; +import '../tables/relations.dart'; + +part 'user_dao.g.dart'; + +@DriftAccessor(tables: [Users, UserDatas, Tokens, RefreshTokens, UserPacks]) +class UserDao extends DatabaseAccessor with _$UserDaoMixin { + UserDao(super.db); + + // ==================== Users ==================== + + /// Получить пользователя по ID + Future getUserById(int id) { + return (select(users)..where((u) => u.id.equals(id))).getSingleOrNull(); + } + + /// Получить пользователя с UserData + Future getUserWithDataById(int id) async { + final query = select(users).join([ + leftOuterJoin(userDatas, userDatas.userId.equalsExp(users.id)), + ])..where(users.id.equals(id)); + + final result = await query.getSingleOrNull(); + if (result == null) return null; + + return UserWithData( + user: result.readTable(users), + userData: result.readTableOrNull(userDatas), + ); + } + + /// Получить пользователя по email + Future getUserByEmail(String email) { + return (select(users)..where((u) => u.email.equals(email))).getSingleOrNull(); + } + + /// Получить пользователя по externalUserId + Future getUserByExternalId(String externalId) { + return (select(users) + ..where((u) => u.externalUserId.equals(externalId)) + ).getSingleOrNull(); + } + + /// Создать пользователя + Future createUser(UsersCompanion user) { + return into(users).insert(user); + } + + /// Создать пользователя с UserData + Future createUserWithData({ + required UsersCompanion user, + required UserDatasCompanion userData, + }) async { + return await transaction(() async { + final userId = await into(users).insert(user); + await into(userDatas).insert(userData.copyWith(userId: Value(userId))); + return userId; + }); + } + + /// Обновить пользователя + Future updateUser(User user) { + return update(users).replace(user); + } + + /// Обновить пользователя частично + Future updateUserPartial(UsersCompanion updates) { + final userId = updates.id.value; + if (userId == null) throw ArgumentError('User ID is required'); + + return (update(users)..where((u) => u.id.equals(userId))).write(updates); + } + + /// Удалить пользователя (soft delete) + Future softDeleteUser(int userId) { + return (update(users)..where((u) => u.id.equals(userId))) + .write(UsersCompanion( + isDeleted: const Value(true), + updatedAt: Value(DateTime.now()), + )); + } + + /// Получить всех пользователей (для админки) + Future> getAllUsers({ + int? limit, + int? offset, + bool includeDeleted = false, + }) { + final query = select(users); + if (!includeDeleted) { + query.where((u) => u.isDeleted.equals(false)); + } + query.orderBy([(u) => OrderingTerm.desc(u.createdAt)]); + if (limit != null) { + query.limit(limit, offset: offset); + } + return query.get(); + } + + /// Подсчитать пользователей + Future countUsers({bool includeDeleted = false}) async { + final countExpr = users.id.count(); + final query = selectOnly(users)..addColumns([countExpr]); + if (!includeDeleted) { + query.where(users.isDeleted.equals(false)); + } + return await query.map((row) => row.read(countExpr)!).getSingle(); + } + + /// Получить пользователей с активной подпиской + Stream> watchUsersWithActiveSubscription() { + // TODO: implement with join to UserSubscriptions when SubscriptionDao is ready + return (select(users) + ..where((u) => u.isDeleted.equals(false)) + ).watch(); + } + + // ==================== UserData ==================== + + /// Получить UserData пользователя + Future getUserData(int userId) { + return (select(userDatas)..where((ud) => ud.userId.equals(userId))) + .getSingleOrNull(); + } + + /// Создать UserData + Future createUserData(UserDatasCompanion userData) { + return into(userDatas).insert(userData); + } + + /// Обновить UserData + Future updateUserData(UserData userData) { + return update(userDatas).replace(userData); + } + + /// Обновить UserData частично + Future updateUserDataPartial(UserDatasCompanion updates) { + final userId = updates.userId.value; + if (userId == null) throw ArgumentError('User ID is required'); + + return (update(userDatas)..where((ud) => ud.userId.equals(userId))) + .write(updates.copyWith(updatedAt: Value(DateTime.now()))); + } + + /// Обновить время последнего визита + Future updateLastOnline(int userId) async { + await (update(userDatas)..where((ud) => ud.userId.equals(userId))) + .write(UserDatasCompanion( + lastTimeOnline: Value(DateTime.now()), + updatedAt: Value(DateTime.now()), + )); + } + + // ==================== Tokens ==================== + + /// Получить токен по значению + Future getTokenByValue(String tokenValue) { + return (select(tokens)..where((t) => t.token.equals(tokenValue))) + .getSingleOrNull(); + } + + /// Получить токен пользователя + Future getTokenByUserId(int userId) { + return (select(tokens) + ..where((t) => t.userId.equals(userId)) + ..where((t) => t.expires.isBiggerThanValue(DateTime.now())) + ..orderBy([(t) => OrderingTerm.desc(t.created)]) + ).getSingleOrNull(); + } + + /// Создать токен + Future createToken(TokensCompanion token) { + return into(tokens).insert(token); + } + + /// Удалить токен + Future deleteToken(int tokenId) { + return (delete(tokens)..where((t) => t.id.equals(tokenId))).go(); + } + + /// Удалить токен по значению + Future deleteTokenByValue(String tokenValue) { + return (delete(tokens)..where((t) => t.token.equals(tokenValue))).go(); + } + + /// Удалить истекшие токены + Future deleteExpiredTokens() { + return (delete(tokens) + ..where((t) => t.expires.isSmallerThanValue(DateTime.now())) + ).go(); + } + + // ==================== RefreshTokens ==================== + + /// Получить refresh token по JTI + Future getRefreshTokenByJti(String jti) { + return (select(refreshTokens)..where((rt) => rt.jti.equals(jti))) + .getSingleOrNull(); + } + + /// Получить активные refresh токены пользователя + Future> getActiveRefreshTokens(int userId) { + return (select(refreshTokens) + ..where((rt) => rt.userId.equals(userId)) + ..where((rt) => rt.isBlacklisted.equals(false)) + ..where((rt) => rt.expiresAt.isBiggerThanValue(DateTime.now())) + ..orderBy([(rt) => OrderingTerm.desc(rt.createdAt)]) + ).get(); + } + + /// Создать refresh token + Future createRefreshToken(RefreshTokensCompanion token) { + return into(refreshTokens).insert(token); + } + + /// Отозвать refresh token (blacklist) + Future revokeRefreshToken(int tokenId) { + return (update(refreshTokens)..where((rt) => rt.id.equals(tokenId))) + .write(const RefreshTokensCompanion( + isBlacklisted: Value(true), + )); + } + + /// Отозвать refresh token по JTI + Future revokeRefreshTokenByJti(String jti) { + return (update(refreshTokens)..where((rt) => rt.jti.equals(jti))) + .write(const RefreshTokensCompanion( + isBlacklisted: Value(true), + )); + } + + /// Удалить истекшие refresh токены + Future deleteExpiredRefreshTokens() { + return (delete(refreshTokens) + ..where((rt) => rt.expiresAt.isSmallerThanValue(DateTime.now())) + ).go(); + } + + // ==================== TelegramAuthCodes ==================== + + /// Получить код авторизации + Future getAuthCode(String code) { + return (select(db.telegramAuthCodes)..where((ac) => ac.code.equals(code))) + .getSingleOrNull(); + } + + /// Создать код авторизации + Future createAuthCode(TelegramAuthCodesCompanion code) { + return into(db.telegramAuthCodes).insert(code); + } + + /// Отметить код как использованный + Future markAuthCodeAsUsed(String code) { + return (update(db.telegramAuthCodes)..where((ac) => ac.code.equals(code))) + .write(TelegramAuthCodesCompanion( + isUsed: const Value(true), + usedAt: Value(DateTime.now()), + )); + } + + /// Удалить истекшие коды + Future deleteExpiredAuthCodes() { + return (delete(db.telegramAuthCodes) + ..where((ac) => ac.expiresAt.isSmallerThanValue(DateTime.now())) + ).go(); + } + + // ==================== User Packs ==================== + + /// Получить паки пользователя + Future> getUserPacks(int userId) async { + final query = select(cardPacks).join([ + innerJoin( + userPacks, + userPacks.packId.equalsExp(cardPacks.id) & + userPacks.userId.equals(userId), + ), + ]); + + return query.map((row) => row.readTable(cardPacks)).get(); + } + + /// Проверить, есть ли у пользователя доступ к паку + Future hasPackAccess(int userId, int packId) async { + final query = select(userPacks) + ..where((up) => up.userId.equals(userId) & up.packId.equals(packId)); + + final result = await query.getSingleOrNull(); + return result != null; + } + + /// Дать пользователю доступ к паку + Future grantPackAccess({ + required int userId, + required int packId, + String grantType = 'purchase', + }) async { + await into(userPacks).insert( + UserPacksCompanion.insert( + userId: userId, + packId: packId, + grantType: Value(grantType), + ), + mode: InsertMode.insertOrIgnore, // игнорировать если уже есть + ); + } + + /// Отозвать доступ к паку + Future revokePackAccess(int userId, int packId) async { + await (delete(userPacks) + ..where((up) => up.userId.equals(userId) & up.packId.equals(packId)) + ).go(); + } +} + +/// Вспомогательный класс для User с UserData +class UserWithData { + final User user; + final UserData? userData; + + UserWithData({required this.user, this.userData}); +} diff --git a/mnemo_cards_backend/lib/database/daos/user_dao.g.dart b/mnemo_cards_backend/lib/database/daos/user_dao.g.dart new file mode 100644 index 0000000..a200f9b --- /dev/null +++ b/mnemo_cards_backend/lib/database/daos/user_dao.g.dart @@ -0,0 +1,13 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'user_dao.dart'; + +// ignore_for_file: type=lint +mixin _$UserDaoMixin on DatabaseAccessor { + $UsersTable get users => attachedDatabase.users; + $UserDatasTable get userDatas => attachedDatabase.userDatas; + $TokensTable get tokens => attachedDatabase.tokens; + $RefreshTokensTable get refreshTokens => attachedDatabase.refreshTokens; + $CardPacksTable get cardPacks => attachedDatabase.cardPacks; + $UserPacksTable get userPacks => attachedDatabase.userPacks; +} diff --git a/mnemo_cards_backend/lib/database/database.dart b/mnemo_cards_backend/lib/database/database.dart new file mode 100644 index 0000000..57ecf9f --- /dev/null +++ b/mnemo_cards_backend/lib/database/database.dart @@ -0,0 +1,242 @@ +import 'package:drift/drift.dart'; +import 'package:drift_postgres/drift_postgres.dart'; +import 'package:postgres/postgres.dart' as pg; +import 'dart:io'; + +// Импорт конвертеров (нужен для генерации кода) +import 'converters.dart'; + +// Импорт всех таблиц +import 'tables/users.dart'; +import 'tables/auth.dart'; +import 'tables/packs.dart'; +import 'tables/relations.dart'; +import 'tables/subscriptions.dart'; +import 'tables/payments.dart'; +import 'tables/tests.dart'; +import 'tables/tasks.dart'; +import 'tables/promo_codes.dart'; +import 'tables/discounts.dart'; +import 'tables/statistics.dart'; +import 'tables/telegram.dart'; +import 'tables/achievements.dart'; + +// Импорт DAOs +import 'daos/user_dao.dart'; +import 'daos/pack_dao.dart'; +import 'daos/test_dao.dart'; +import 'daos/payment_dao.dart'; +import 'daos/subscription_dao.dart'; +import 'daos/task_dao.dart'; +import 'daos/promo_code_dao.dart'; +import 'daos/discount_dao.dart'; +import 'daos/statistics_dao.dart'; +import 'daos/achievement_dao.dart'; + +// Сгенерированный код будет здесь +part 'database.g.dart'; + +@DriftDatabase( + tables: [ + // User tables + Users, + UserDatas, + + // Auth tables + Tokens, + RefreshTokens, + TelegramAuthCodes, + + // Pack tables + CardPacks, + GameCards, + VoiceModels, + + // Relations + UserPacks, + PreviewCards, + CardPackCards, + CardVoices, + + // Subscription tables + SubscriptionPlans, + UserSubscriptions, + + // Payment tables + Payments, + + // Test tables + Tests, + TestQuestions, + TestPackRelations, + TestStatistics, + + // Task tables + Tasks, + UserTasks, + UserTaskProgresses, + UserTaskResults, + + // Promo code tables + PromoCodesCampaigns, + PromoCodes, + + // Discount tables + DiscountCampaigns, + Discounts, + DiscountUserDatas, + + // Statistics tables + StudySessions, + + // Achievements tables + UserAchievements, + + // Telegram tables + ShareRequests, + ], + daos: [ + UserDao, + PackDao, + TestDao, + PaymentDao, + SubscriptionDao, + TaskDao, + PromoCodeDao, + DiscountDao, + StatisticsDao, + AchievementDao, + ], +) +class AppDatabase extends _$AppDatabase { + AppDatabase(super.e); + + @override + int get schemaVersion => 1; + + /// Factory для подключения к PostgreSQL + static AppDatabase connect({ + required String host, + required int port, + required String database, + required String username, + required String password, + bool useSsl = false, + }) { + final endpoint = pg.Endpoint( + host: host, + port: port, + database: database, + username: username, + password: password, + ); + + final connection = PgDatabase( + endpoint: endpoint, + settings: pg.ConnectionSettings( + sslMode: useSsl ? pg.SslMode.require : pg.SslMode.disable, + connectTimeout: const Duration(seconds: 10), + ), + ); + + return AppDatabase(connection); + } + + /// Factory для подключения из environment variables + static AppDatabase fromEnvironment() { + return connect( + host: Platform.environment['DB_HOST'] ?? 'localhost', + port: int.parse(Platform.environment['DB_PORT'] ?? '5432'), + database: Platform.environment['DB_NAME'] ?? 'mnemo_cards_dev', + username: Platform.environment['DB_USER'] ?? 'mnemo_user', + password: Platform.environment['DB_PASSWORD'] ?? '', + useSsl: Platform.environment['DB_SSL_MODE'] == 'require', + ); + } + + @override + MigrationStrategy get migration => MigrationStrategy( + onCreate: (Migrator m) async { + print('Creating database schema...'); + await m.createAll(); + print('Database schema created successfully'); + + // Создать индексы для оптимизации + await _createIndexes(); + }, + onUpgrade: (Migrator m, int from, int to) async { + print('Migrating database from version $from to $to'); + + // Миграции при обновлении схемы + // if (from < 2) { + // await m.addColumn(users, users.phoneNumber); + // } + }, + beforeOpen: (details) async { + print('Opening database connection...'); + + // Проверка подключения + final result = await customSelect('SELECT 1 as test').getSingle(); + print('Database connection successful: ${result.data}'); + + // Включить foreign key constraints + await customStatement('SET CONSTRAINTS ALL IMMEDIATE'); + }, + ); + + /// Создание индексов для оптимизации запросов + Future _createIndexes() async { + print('Creating indexes...'); + + // Users indexes + await customStatement('CREATE INDEX IF NOT EXISTS idx_users_email ON users(email) WHERE email IS NOT NULL'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_users_admin ON users(admin) WHERE admin = true'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_users_not_deleted ON users(is_deleted) WHERE is_deleted = false'); + + // UserDatas indexes + await customStatement('CREATE INDEX IF NOT EXISTS idx_user_datas_user_id ON user_datas(user_id)'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_user_datas_last_online ON user_datas(last_time_online DESC)'); + + // Auth indexes + await customStatement('CREATE INDEX IF NOT EXISTS idx_tokens_user_id ON tokens(user_id)'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_tokens_expires ON tokens(expires) WHERE expires > NOW()'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user_id ON refresh_tokens(user_id)'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_refresh_tokens_jti ON refresh_tokens(jti)'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_refresh_tokens_not_blacklisted ON refresh_tokens(is_blacklisted) WHERE is_blacklisted = false'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_telegram_codes_code ON telegram_auth_codes(code)'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_telegram_codes_not_used ON telegram_auth_codes(is_used) WHERE is_used = false'); + + // CardPacks indexes + await customStatement('CREATE INDEX IF NOT EXISTS idx_packs_enabled ON card_packs(enabled) WHERE enabled = true'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_packs_order ON card_packs("order")'); + + // GameCards indexes + await customStatement('CREATE INDEX IF NOT EXISTS idx_cards_original ON game_cards(original)'); + + // UserPacks indexes + await customStatement('CREATE INDEX IF NOT EXISTS idx_user_packs_user_id ON user_packs(user_id)'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_user_packs_pack_id ON user_packs(pack_id)'); + + // Payments indexes + await customStatement('CREATE INDEX IF NOT EXISTS idx_payments_user_id ON payments(user_id)'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_payments_status ON payments(status)'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_payments_created ON payments(date DESC)'); + + // Subscriptions indexes + await customStatement('CREATE INDEX IF NOT EXISTS idx_subscriptions_user_id ON user_subscriptions(user_id)'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_subscriptions_active ON user_subscriptions(finish) WHERE finish > NOW()'); + + // Tests indexes + await customStatement('CREATE INDEX IF NOT EXISTS idx_test_questions_test_id ON test_questions(test_id)'); + + // StudySessions indexes + await customStatement('CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON study_sessions(user_id)'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_sessions_started ON study_sessions(start_time DESC)'); + + // PromoCodes indexes + await customStatement('CREATE INDEX IF NOT EXISTS idx_promo_codes_code ON promo_codes(code)'); + await customStatement('CREATE INDEX IF NOT EXISTS idx_promo_codes_campaign_id ON promo_codes(campaign_id)'); + + print('Indexes created successfully'); + } +} diff --git a/mnemo_cards_backend/lib/database/database.g.dart b/mnemo_cards_backend/lib/database/database.g.dart new file mode 100644 index 0000000..536c50a --- /dev/null +++ b/mnemo_cards_backend/lib/database/database.g.dart @@ -0,0 +1,13643 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'database.dart'; + +// ignore_for_file: type=lint +class $UsersTable extends Users with TableInfo<$UsersTable, User> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UsersTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _externalUserIdMeta = + const VerificationMeta('externalUserId'); + @override + late final GeneratedColumn externalUserId = GeneratedColumn( + 'external_user_id', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _emailMeta = const VerificationMeta('email'); + @override + late final GeneratedColumn email = GeneratedColumn( + 'email', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _adminMeta = const VerificationMeta('admin'); + @override + late final GeneratedColumn admin = GeneratedColumn( + 'admin', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("admin" IN (0, 1))'), + defaultValue: const Constant(false)); + static const VerificationMeta _userSettingsMeta = + const VerificationMeta('userSettings'); + @override + late final GeneratedColumn userSettings = GeneratedColumn( + 'user_settings', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _purchasesMeta = + const VerificationMeta('purchases'); + @override + late final GeneratedColumnWithTypeConverter, String> purchases = + GeneratedColumn('purchases', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter>($UsersTable.$converterpurchases); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _isDeletedMeta = + const VerificationMeta('isDeleted'); + @override + late final GeneratedColumn isDeleted = GeneratedColumn( + 'is_deleted', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_deleted" IN (0, 1))'), + defaultValue: const Constant(false)); + @override + List get $columns => [ + id, + externalUserId, + name, + email, + admin, + userSettings, + purchases, + createdAt, + updatedAt, + isDeleted + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'users'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('external_user_id')) { + context.handle( + _externalUserIdMeta, + externalUserId.isAcceptableOrUnknown( + data['external_user_id']!, _externalUserIdMeta)); + } else if (isInserting) { + context.missing(_externalUserIdMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } + if (data.containsKey('email')) { + context.handle( + _emailMeta, email.isAcceptableOrUnknown(data['email']!, _emailMeta)); + } + if (data.containsKey('admin')) { + context.handle( + _adminMeta, admin.isAcceptableOrUnknown(data['admin']!, _adminMeta)); + } + if (data.containsKey('user_settings')) { + context.handle( + _userSettingsMeta, + userSettings.isAcceptableOrUnknown( + data['user_settings']!, _userSettingsMeta)); + } + context.handle(_purchasesMeta, const VerificationResult.success()); + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + if (data.containsKey('is_deleted')) { + context.handle(_isDeletedMeta, + isDeleted.isAcceptableOrUnknown(data['is_deleted']!, _isDeletedMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + User map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return User( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + externalUserId: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}external_user_id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name']), + email: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}email']), + admin: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}admin'])!, + userSettings: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}user_settings']), + purchases: $UsersTable.$converterpurchases.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}purchases'])!), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + isDeleted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_deleted'])!, + ); + } + + @override + $UsersTable createAlias(String alias) { + return $UsersTable(attachedDatabase, alias); + } + + static TypeConverter, String> $converterpurchases = + const StringListConverter(); +} + +class User extends DataClass implements Insertable { + final int id; + final String externalUserId; + final String? name; + final String? email; + final bool admin; + final String? userSettings; + final List purchases; + final DateTime createdAt; + final DateTime updatedAt; + final bool isDeleted; + const User( + {required this.id, + required this.externalUserId, + this.name, + this.email, + required this.admin, + this.userSettings, + required this.purchases, + required this.createdAt, + required this.updatedAt, + required this.isDeleted}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['external_user_id'] = Variable(externalUserId); + if (!nullToAbsent || name != null) { + map['name'] = Variable(name); + } + if (!nullToAbsent || email != null) { + map['email'] = Variable(email); + } + map['admin'] = Variable(admin); + if (!nullToAbsent || userSettings != null) { + map['user_settings'] = Variable(userSettings); + } + { + map['purchases'] = + Variable($UsersTable.$converterpurchases.toSql(purchases)); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['is_deleted'] = Variable(isDeleted); + return map; + } + + UsersCompanion toCompanion(bool nullToAbsent) { + return UsersCompanion( + id: Value(id), + externalUserId: Value(externalUserId), + name: name == null && nullToAbsent ? const Value.absent() : Value(name), + email: + email == null && nullToAbsent ? const Value.absent() : Value(email), + admin: Value(admin), + userSettings: userSettings == null && nullToAbsent + ? const Value.absent() + : Value(userSettings), + purchases: Value(purchases), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + isDeleted: Value(isDeleted), + ); + } + + factory User.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return User( + id: serializer.fromJson(json['id']), + externalUserId: serializer.fromJson(json['externalUserId']), + name: serializer.fromJson(json['name']), + email: serializer.fromJson(json['email']), + admin: serializer.fromJson(json['admin']), + userSettings: serializer.fromJson(json['userSettings']), + purchases: serializer.fromJson>(json['purchases']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + isDeleted: serializer.fromJson(json['isDeleted']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'externalUserId': serializer.toJson(externalUserId), + 'name': serializer.toJson(name), + 'email': serializer.toJson(email), + 'admin': serializer.toJson(admin), + 'userSettings': serializer.toJson(userSettings), + 'purchases': serializer.toJson>(purchases), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'isDeleted': serializer.toJson(isDeleted), + }; + } + + User copyWith( + {int? id, + String? externalUserId, + Value name = const Value.absent(), + Value email = const Value.absent(), + bool? admin, + Value userSettings = const Value.absent(), + List? purchases, + DateTime? createdAt, + DateTime? updatedAt, + bool? isDeleted}) => + User( + id: id ?? this.id, + externalUserId: externalUserId ?? this.externalUserId, + name: name.present ? name.value : this.name, + email: email.present ? email.value : this.email, + admin: admin ?? this.admin, + userSettings: + userSettings.present ? userSettings.value : this.userSettings, + purchases: purchases ?? this.purchases, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + @override + String toString() { + return (StringBuffer('User(') + ..write('id: $id, ') + ..write('externalUserId: $externalUserId, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('admin: $admin, ') + ..write('userSettings: $userSettings, ') + ..write('purchases: $purchases, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, externalUserId, name, email, admin, + userSettings, purchases, createdAt, updatedAt, isDeleted); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is User && + other.id == this.id && + other.externalUserId == this.externalUserId && + other.name == this.name && + other.email == this.email && + other.admin == this.admin && + other.userSettings == this.userSettings && + other.purchases == this.purchases && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.isDeleted == this.isDeleted); +} + +class UsersCompanion extends UpdateCompanion { + final Value id; + final Value externalUserId; + final Value name; + final Value email; + final Value admin; + final Value userSettings; + final Value> purchases; + final Value createdAt; + final Value updatedAt; + final Value isDeleted; + const UsersCompanion({ + this.id = const Value.absent(), + this.externalUserId = const Value.absent(), + this.name = const Value.absent(), + this.email = const Value.absent(), + this.admin = const Value.absent(), + this.userSettings = const Value.absent(), + this.purchases = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }); + UsersCompanion.insert({ + this.id = const Value.absent(), + required String externalUserId, + this.name = const Value.absent(), + this.email = const Value.absent(), + this.admin = const Value.absent(), + this.userSettings = const Value.absent(), + this.purchases = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }) : externalUserId = Value(externalUserId); + static Insertable custom({ + Expression? id, + Expression? externalUserId, + Expression? name, + Expression? email, + Expression? admin, + Expression? userSettings, + Expression? purchases, + Expression? createdAt, + Expression? updatedAt, + Expression? isDeleted, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (externalUserId != null) 'external_user_id': externalUserId, + if (name != null) 'name': name, + if (email != null) 'email': email, + if (admin != null) 'admin': admin, + if (userSettings != null) 'user_settings': userSettings, + if (purchases != null) 'purchases': purchases, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (isDeleted != null) 'is_deleted': isDeleted, + }); + } + + UsersCompanion copyWith( + {Value? id, + Value? externalUserId, + Value? name, + Value? email, + Value? admin, + Value? userSettings, + Value>? purchases, + Value? createdAt, + Value? updatedAt, + Value? isDeleted}) { + return UsersCompanion( + id: id ?? this.id, + externalUserId: externalUserId ?? this.externalUserId, + name: name ?? this.name, + email: email ?? this.email, + admin: admin ?? this.admin, + userSettings: userSettings ?? this.userSettings, + purchases: purchases ?? this.purchases, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (externalUserId.present) { + map['external_user_id'] = Variable(externalUserId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (email.present) { + map['email'] = Variable(email.value); + } + if (admin.present) { + map['admin'] = Variable(admin.value); + } + if (userSettings.present) { + map['user_settings'] = Variable(userSettings.value); + } + if (purchases.present) { + map['purchases'] = Variable( + $UsersTable.$converterpurchases.toSql(purchases.value)); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (isDeleted.present) { + map['is_deleted'] = Variable(isDeleted.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UsersCompanion(') + ..write('id: $id, ') + ..write('externalUserId: $externalUserId, ') + ..write('name: $name, ') + ..write('email: $email, ') + ..write('admin: $admin, ') + ..write('userSettings: $userSettings, ') + ..write('purchases: $purchases, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } +} + +class $UserDatasTable extends UserDatas + with TableInfo<$UserDatasTable, UserData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserDatasTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'UNIQUE REFERENCES users (id) ON DELETE CASCADE')); + static const VerificationMeta _totalStudyTimeMinutesMeta = + const VerificationMeta('totalStudyTimeMinutes'); + @override + late final GeneratedColumn totalStudyTimeMinutes = GeneratedColumn( + 'total_study_time_minutes', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _currentStreakMeta = + const VerificationMeta('currentStreak'); + @override + late final GeneratedColumn currentStreak = GeneratedColumn( + 'current_streak', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _longestStreakMeta = + const VerificationMeta('longestStreak'); + @override + late final GeneratedColumn longestStreak = GeneratedColumn( + 'longest_streak', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _totalCardsMeta = + const VerificationMeta('totalCards'); + @override + late final GeneratedColumn totalCards = GeneratedColumn( + 'total_cards', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _totalTestsMeta = + const VerificationMeta('totalTests'); + @override + late final GeneratedColumn totalTests = GeneratedColumn( + 'total_tests', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _lastTimeOnlineMeta = + const VerificationMeta('lastTimeOnline'); + @override + late final GeneratedColumn lastTimeOnline = + GeneratedColumn('last_time_online', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _registrationDateMeta = + const VerificationMeta('registrationDate'); + @override + late final GeneratedColumn registrationDate = + GeneratedColumn('registration_date', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _lastTestSessionTokenMeta = + const VerificationMeta('lastTestSessionToken'); + @override + late final GeneratedColumn lastTestSessionToken = + GeneratedColumn('last_test_session_token', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _wordsMeta = const VerificationMeta('words'); + @override + late final GeneratedColumnWithTypeConverter?, String> words = + GeneratedColumn('words', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter?>($UserDatasTable.$converterwords); + static const VerificationMeta _tagsMeta = const VerificationMeta('tags'); + @override + late final GeneratedColumnWithTypeConverter, String> tags = + GeneratedColumn('tags', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter>($UserDatasTable.$convertertags); + static const VerificationMeta _packProgressMeta = + const VerificationMeta('packProgress'); + @override + late final GeneratedColumnWithTypeConverter?, String> + packProgress = GeneratedColumn( + 'pack_progress', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter?>( + $UserDatasTable.$converterpackProgress); + static const VerificationMeta _studyDatesMeta = + const VerificationMeta('studyDates'); + @override + late final GeneratedColumnWithTypeConverter?, String> + studyDates = GeneratedColumn('study_dates', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter?>($UserDatasTable.$converterstudyDates); + static const VerificationMeta _achievementsMeta = + const VerificationMeta('achievements'); + @override + late final GeneratedColumnWithTypeConverter?, String> + achievements = GeneratedColumn('achievements', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter?>( + $UserDatasTable.$converterachievements); + static const VerificationMeta _categoryMinutesMeta = + const VerificationMeta('categoryMinutes'); + @override + late final GeneratedColumnWithTypeConverter?, String> + categoryMinutes = GeneratedColumn( + 'category_minutes', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('{}')) + .withConverter?>( + $UserDatasTable.$convertercategoryMinutes); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [ + id, + userId, + totalStudyTimeMinutes, + currentStreak, + longestStreak, + totalCards, + totalTests, + lastTimeOnline, + registrationDate, + lastTestSessionToken, + words, + tags, + packProgress, + studyDates, + achievements, + categoryMinutes, + createdAt, + updatedAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_datas'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('total_study_time_minutes')) { + context.handle( + _totalStudyTimeMinutesMeta, + totalStudyTimeMinutes.isAcceptableOrUnknown( + data['total_study_time_minutes']!, _totalStudyTimeMinutesMeta)); + } + if (data.containsKey('current_streak')) { + context.handle( + _currentStreakMeta, + currentStreak.isAcceptableOrUnknown( + data['current_streak']!, _currentStreakMeta)); + } + if (data.containsKey('longest_streak')) { + context.handle( + _longestStreakMeta, + longestStreak.isAcceptableOrUnknown( + data['longest_streak']!, _longestStreakMeta)); + } + if (data.containsKey('total_cards')) { + context.handle( + _totalCardsMeta, + totalCards.isAcceptableOrUnknown( + data['total_cards']!, _totalCardsMeta)); + } + if (data.containsKey('total_tests')) { + context.handle( + _totalTestsMeta, + totalTests.isAcceptableOrUnknown( + data['total_tests']!, _totalTestsMeta)); + } + if (data.containsKey('last_time_online')) { + context.handle( + _lastTimeOnlineMeta, + lastTimeOnline.isAcceptableOrUnknown( + data['last_time_online']!, _lastTimeOnlineMeta)); + } + if (data.containsKey('registration_date')) { + context.handle( + _registrationDateMeta, + registrationDate.isAcceptableOrUnknown( + data['registration_date']!, _registrationDateMeta)); + } + if (data.containsKey('last_test_session_token')) { + context.handle( + _lastTestSessionTokenMeta, + lastTestSessionToken.isAcceptableOrUnknown( + data['last_test_session_token']!, _lastTestSessionTokenMeta)); + } + context.handle(_wordsMeta, const VerificationResult.success()); + context.handle(_tagsMeta, const VerificationResult.success()); + context.handle(_packProgressMeta, const VerificationResult.success()); + context.handle(_studyDatesMeta, const VerificationResult.success()); + context.handle(_achievementsMeta, const VerificationResult.success()); + context.handle(_categoryMinutesMeta, const VerificationResult.success()); + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + UserData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + totalStudyTimeMinutes: attachedDatabase.typeMapping.read(DriftSqlType.int, + data['${effectivePrefix}total_study_time_minutes'])!, + currentStreak: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}current_streak'])!, + longestStreak: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}longest_streak'])!, + totalCards: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}total_cards'])!, + totalTests: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}total_tests'])!, + lastTimeOnline: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}last_time_online']), + registrationDate: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}registration_date'])!, + lastTestSessionToken: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}last_test_session_token']), + words: $UserDatasTable.$converterwords.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}words'])!), + tags: $UserDatasTable.$convertertags.fromSql(attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}tags'])!), + packProgress: $UserDatasTable.$converterpackProgress.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}pack_progress'])!), + studyDates: $UserDatasTable.$converterstudyDates.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}study_dates'])!), + achievements: $UserDatasTable.$converterachievements.fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}achievements'])!), + categoryMinutes: $UserDatasTable.$convertercategoryMinutes.fromSql( + attachedDatabase.typeMapping.read(DriftSqlType.string, + data['${effectivePrefix}category_minutes'])!), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $UserDatasTable createAlias(String alias) { + return $UserDatasTable(attachedDatabase, alias); + } + + static TypeConverter?, String> $converterwords = + const JsonListConverter(); + static TypeConverter, String> $convertertags = + const StringListConverter(); + static TypeConverter?, String> $converterpackProgress = + const JsonListConverter(); + static TypeConverter?, String> $converterstudyDates = + const DateTimeListConverter(); + static TypeConverter?, String> $converterachievements = + const JsonListConverter(); + static TypeConverter?, String> + $convertercategoryMinutes = const JsonMapConverter(); +} + +class UserData extends DataClass implements Insertable { + final int id; + final int userId; + final int totalStudyTimeMinutes; + final int currentStreak; + final int longestStreak; + final int totalCards; + final int totalTests; + final DateTime? lastTimeOnline; + final DateTime registrationDate; + final String? lastTestSessionToken; + final List? words; + final List tags; + final List? packProgress; + final List? studyDates; + final List? achievements; + final Map? categoryMinutes; + final DateTime createdAt; + final DateTime updatedAt; + const UserData( + {required this.id, + required this.userId, + required this.totalStudyTimeMinutes, + required this.currentStreak, + required this.longestStreak, + required this.totalCards, + required this.totalTests, + this.lastTimeOnline, + required this.registrationDate, + this.lastTestSessionToken, + this.words, + required this.tags, + this.packProgress, + this.studyDates, + this.achievements, + this.categoryMinutes, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['user_id'] = Variable(userId); + map['total_study_time_minutes'] = Variable(totalStudyTimeMinutes); + map['current_streak'] = Variable(currentStreak); + map['longest_streak'] = Variable(longestStreak); + map['total_cards'] = Variable(totalCards); + map['total_tests'] = Variable(totalTests); + if (!nullToAbsent || lastTimeOnline != null) { + map['last_time_online'] = Variable(lastTimeOnline); + } + map['registration_date'] = Variable(registrationDate); + if (!nullToAbsent || lastTestSessionToken != null) { + map['last_test_session_token'] = Variable(lastTestSessionToken); + } + if (!nullToAbsent || words != null) { + map['words'] = + Variable($UserDatasTable.$converterwords.toSql(words)); + } + { + map['tags'] = + Variable($UserDatasTable.$convertertags.toSql(tags)); + } + if (!nullToAbsent || packProgress != null) { + map['pack_progress'] = Variable( + $UserDatasTable.$converterpackProgress.toSql(packProgress)); + } + if (!nullToAbsent || studyDates != null) { + map['study_dates'] = Variable( + $UserDatasTable.$converterstudyDates.toSql(studyDates)); + } + if (!nullToAbsent || achievements != null) { + map['achievements'] = Variable( + $UserDatasTable.$converterachievements.toSql(achievements)); + } + if (!nullToAbsent || categoryMinutes != null) { + map['category_minutes'] = Variable( + $UserDatasTable.$convertercategoryMinutes.toSql(categoryMinutes)); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + UserDatasCompanion toCompanion(bool nullToAbsent) { + return UserDatasCompanion( + id: Value(id), + userId: Value(userId), + totalStudyTimeMinutes: Value(totalStudyTimeMinutes), + currentStreak: Value(currentStreak), + longestStreak: Value(longestStreak), + totalCards: Value(totalCards), + totalTests: Value(totalTests), + lastTimeOnline: lastTimeOnline == null && nullToAbsent + ? const Value.absent() + : Value(lastTimeOnline), + registrationDate: Value(registrationDate), + lastTestSessionToken: lastTestSessionToken == null && nullToAbsent + ? const Value.absent() + : Value(lastTestSessionToken), + words: + words == null && nullToAbsent ? const Value.absent() : Value(words), + tags: Value(tags), + packProgress: packProgress == null && nullToAbsent + ? const Value.absent() + : Value(packProgress), + studyDates: studyDates == null && nullToAbsent + ? const Value.absent() + : Value(studyDates), + achievements: achievements == null && nullToAbsent + ? const Value.absent() + : Value(achievements), + categoryMinutes: categoryMinutes == null && nullToAbsent + ? const Value.absent() + : Value(categoryMinutes), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory UserData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserData( + id: serializer.fromJson(json['id']), + userId: serializer.fromJson(json['userId']), + totalStudyTimeMinutes: + serializer.fromJson(json['totalStudyTimeMinutes']), + currentStreak: serializer.fromJson(json['currentStreak']), + longestStreak: serializer.fromJson(json['longestStreak']), + totalCards: serializer.fromJson(json['totalCards']), + totalTests: serializer.fromJson(json['totalTests']), + lastTimeOnline: serializer.fromJson(json['lastTimeOnline']), + registrationDate: serializer.fromJson(json['registrationDate']), + lastTestSessionToken: + serializer.fromJson(json['lastTestSessionToken']), + words: serializer.fromJson?>(json['words']), + tags: serializer.fromJson>(json['tags']), + packProgress: serializer.fromJson?>(json['packProgress']), + studyDates: serializer.fromJson?>(json['studyDates']), + achievements: serializer.fromJson?>(json['achievements']), + categoryMinutes: + serializer.fromJson?>(json['categoryMinutes']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'userId': serializer.toJson(userId), + 'totalStudyTimeMinutes': serializer.toJson(totalStudyTimeMinutes), + 'currentStreak': serializer.toJson(currentStreak), + 'longestStreak': serializer.toJson(longestStreak), + 'totalCards': serializer.toJson(totalCards), + 'totalTests': serializer.toJson(totalTests), + 'lastTimeOnline': serializer.toJson(lastTimeOnline), + 'registrationDate': serializer.toJson(registrationDate), + 'lastTestSessionToken': serializer.toJson(lastTestSessionToken), + 'words': serializer.toJson?>(words), + 'tags': serializer.toJson>(tags), + 'packProgress': serializer.toJson?>(packProgress), + 'studyDates': serializer.toJson?>(studyDates), + 'achievements': serializer.toJson?>(achievements), + 'categoryMinutes': + serializer.toJson?>(categoryMinutes), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + UserData copyWith( + {int? id, + int? userId, + int? totalStudyTimeMinutes, + int? currentStreak, + int? longestStreak, + int? totalCards, + int? totalTests, + Value lastTimeOnline = const Value.absent(), + DateTime? registrationDate, + Value lastTestSessionToken = const Value.absent(), + Value?> words = const Value.absent(), + List? tags, + Value?> packProgress = const Value.absent(), + Value?> studyDates = const Value.absent(), + Value?> achievements = const Value.absent(), + Value?> categoryMinutes = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt}) => + UserData( + id: id ?? this.id, + userId: userId ?? this.userId, + totalStudyTimeMinutes: + totalStudyTimeMinutes ?? this.totalStudyTimeMinutes, + currentStreak: currentStreak ?? this.currentStreak, + longestStreak: longestStreak ?? this.longestStreak, + totalCards: totalCards ?? this.totalCards, + totalTests: totalTests ?? this.totalTests, + lastTimeOnline: + lastTimeOnline.present ? lastTimeOnline.value : this.lastTimeOnline, + registrationDate: registrationDate ?? this.registrationDate, + lastTestSessionToken: lastTestSessionToken.present + ? lastTestSessionToken.value + : this.lastTestSessionToken, + words: words.present ? words.value : this.words, + tags: tags ?? this.tags, + packProgress: + packProgress.present ? packProgress.value : this.packProgress, + studyDates: studyDates.present ? studyDates.value : this.studyDates, + achievements: + achievements.present ? achievements.value : this.achievements, + categoryMinutes: categoryMinutes.present + ? categoryMinutes.value + : this.categoryMinutes, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + @override + String toString() { + return (StringBuffer('UserData(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('totalStudyTimeMinutes: $totalStudyTimeMinutes, ') + ..write('currentStreak: $currentStreak, ') + ..write('longestStreak: $longestStreak, ') + ..write('totalCards: $totalCards, ') + ..write('totalTests: $totalTests, ') + ..write('lastTimeOnline: $lastTimeOnline, ') + ..write('registrationDate: $registrationDate, ') + ..write('lastTestSessionToken: $lastTestSessionToken, ') + ..write('words: $words, ') + ..write('tags: $tags, ') + ..write('packProgress: $packProgress, ') + ..write('studyDates: $studyDates, ') + ..write('achievements: $achievements, ') + ..write('categoryMinutes: $categoryMinutes, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + userId, + totalStudyTimeMinutes, + currentStreak, + longestStreak, + totalCards, + totalTests, + lastTimeOnline, + registrationDate, + lastTestSessionToken, + words, + tags, + packProgress, + studyDates, + achievements, + categoryMinutes, + createdAt, + updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserData && + other.id == this.id && + other.userId == this.userId && + other.totalStudyTimeMinutes == this.totalStudyTimeMinutes && + other.currentStreak == this.currentStreak && + other.longestStreak == this.longestStreak && + other.totalCards == this.totalCards && + other.totalTests == this.totalTests && + other.lastTimeOnline == this.lastTimeOnline && + other.registrationDate == this.registrationDate && + other.lastTestSessionToken == this.lastTestSessionToken && + other.words == this.words && + other.tags == this.tags && + other.packProgress == this.packProgress && + other.studyDates == this.studyDates && + other.achievements == this.achievements && + other.categoryMinutes == this.categoryMinutes && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class UserDatasCompanion extends UpdateCompanion { + final Value id; + final Value userId; + final Value totalStudyTimeMinutes; + final Value currentStreak; + final Value longestStreak; + final Value totalCards; + final Value totalTests; + final Value lastTimeOnline; + final Value registrationDate; + final Value lastTestSessionToken; + final Value?> words; + final Value> tags; + final Value?> packProgress; + final Value?> studyDates; + final Value?> achievements; + final Value?> categoryMinutes; + final Value createdAt; + final Value updatedAt; + const UserDatasCompanion({ + this.id = const Value.absent(), + this.userId = const Value.absent(), + this.totalStudyTimeMinutes = const Value.absent(), + this.currentStreak = const Value.absent(), + this.longestStreak = const Value.absent(), + this.totalCards = const Value.absent(), + this.totalTests = const Value.absent(), + this.lastTimeOnline = const Value.absent(), + this.registrationDate = const Value.absent(), + this.lastTestSessionToken = const Value.absent(), + this.words = const Value.absent(), + this.tags = const Value.absent(), + this.packProgress = const Value.absent(), + this.studyDates = const Value.absent(), + this.achievements = const Value.absent(), + this.categoryMinutes = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + UserDatasCompanion.insert({ + this.id = const Value.absent(), + required int userId, + this.totalStudyTimeMinutes = const Value.absent(), + this.currentStreak = const Value.absent(), + this.longestStreak = const Value.absent(), + this.totalCards = const Value.absent(), + this.totalTests = const Value.absent(), + this.lastTimeOnline = const Value.absent(), + this.registrationDate = const Value.absent(), + this.lastTestSessionToken = const Value.absent(), + this.words = const Value.absent(), + this.tags = const Value.absent(), + this.packProgress = const Value.absent(), + this.studyDates = const Value.absent(), + this.achievements = const Value.absent(), + this.categoryMinutes = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : userId = Value(userId); + static Insertable custom({ + Expression? id, + Expression? userId, + Expression? totalStudyTimeMinutes, + Expression? currentStreak, + Expression? longestStreak, + Expression? totalCards, + Expression? totalTests, + Expression? lastTimeOnline, + Expression? registrationDate, + Expression? lastTestSessionToken, + Expression? words, + Expression? tags, + Expression? packProgress, + Expression? studyDates, + Expression? achievements, + Expression? categoryMinutes, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (userId != null) 'user_id': userId, + if (totalStudyTimeMinutes != null) + 'total_study_time_minutes': totalStudyTimeMinutes, + if (currentStreak != null) 'current_streak': currentStreak, + if (longestStreak != null) 'longest_streak': longestStreak, + if (totalCards != null) 'total_cards': totalCards, + if (totalTests != null) 'total_tests': totalTests, + if (lastTimeOnline != null) 'last_time_online': lastTimeOnline, + if (registrationDate != null) 'registration_date': registrationDate, + if (lastTestSessionToken != null) + 'last_test_session_token': lastTestSessionToken, + if (words != null) 'words': words, + if (tags != null) 'tags': tags, + if (packProgress != null) 'pack_progress': packProgress, + if (studyDates != null) 'study_dates': studyDates, + if (achievements != null) 'achievements': achievements, + if (categoryMinutes != null) 'category_minutes': categoryMinutes, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + UserDatasCompanion copyWith( + {Value? id, + Value? userId, + Value? totalStudyTimeMinutes, + Value? currentStreak, + Value? longestStreak, + Value? totalCards, + Value? totalTests, + Value? lastTimeOnline, + Value? registrationDate, + Value? lastTestSessionToken, + Value?>? words, + Value>? tags, + Value?>? packProgress, + Value?>? studyDates, + Value?>? achievements, + Value?>? categoryMinutes, + Value? createdAt, + Value? updatedAt}) { + return UserDatasCompanion( + id: id ?? this.id, + userId: userId ?? this.userId, + totalStudyTimeMinutes: + totalStudyTimeMinutes ?? this.totalStudyTimeMinutes, + currentStreak: currentStreak ?? this.currentStreak, + longestStreak: longestStreak ?? this.longestStreak, + totalCards: totalCards ?? this.totalCards, + totalTests: totalTests ?? this.totalTests, + lastTimeOnline: lastTimeOnline ?? this.lastTimeOnline, + registrationDate: registrationDate ?? this.registrationDate, + lastTestSessionToken: lastTestSessionToken ?? this.lastTestSessionToken, + words: words ?? this.words, + tags: tags ?? this.tags, + packProgress: packProgress ?? this.packProgress, + studyDates: studyDates ?? this.studyDates, + achievements: achievements ?? this.achievements, + categoryMinutes: categoryMinutes ?? this.categoryMinutes, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (totalStudyTimeMinutes.present) { + map['total_study_time_minutes'] = + Variable(totalStudyTimeMinutes.value); + } + if (currentStreak.present) { + map['current_streak'] = Variable(currentStreak.value); + } + if (longestStreak.present) { + map['longest_streak'] = Variable(longestStreak.value); + } + if (totalCards.present) { + map['total_cards'] = Variable(totalCards.value); + } + if (totalTests.present) { + map['total_tests'] = Variable(totalTests.value); + } + if (lastTimeOnline.present) { + map['last_time_online'] = Variable(lastTimeOnline.value); + } + if (registrationDate.present) { + map['registration_date'] = Variable(registrationDate.value); + } + if (lastTestSessionToken.present) { + map['last_test_session_token'] = + Variable(lastTestSessionToken.value); + } + if (words.present) { + map['words'] = + Variable($UserDatasTable.$converterwords.toSql(words.value)); + } + if (tags.present) { + map['tags'] = + Variable($UserDatasTable.$convertertags.toSql(tags.value)); + } + if (packProgress.present) { + map['pack_progress'] = Variable( + $UserDatasTable.$converterpackProgress.toSql(packProgress.value)); + } + if (studyDates.present) { + map['study_dates'] = Variable( + $UserDatasTable.$converterstudyDates.toSql(studyDates.value)); + } + if (achievements.present) { + map['achievements'] = Variable( + $UserDatasTable.$converterachievements.toSql(achievements.value)); + } + if (categoryMinutes.present) { + map['category_minutes'] = Variable($UserDatasTable + .$convertercategoryMinutes + .toSql(categoryMinutes.value)); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserDatasCompanion(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('totalStudyTimeMinutes: $totalStudyTimeMinutes, ') + ..write('currentStreak: $currentStreak, ') + ..write('longestStreak: $longestStreak, ') + ..write('totalCards: $totalCards, ') + ..write('totalTests: $totalTests, ') + ..write('lastTimeOnline: $lastTimeOnline, ') + ..write('registrationDate: $registrationDate, ') + ..write('lastTestSessionToken: $lastTestSessionToken, ') + ..write('words: $words, ') + ..write('tags: $tags, ') + ..write('packProgress: $packProgress, ') + ..write('studyDates: $studyDates, ') + ..write('achievements: $achievements, ') + ..write('categoryMinutes: $categoryMinutes, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $TokensTable extends Tokens with TableInfo<$TokensTable, Token> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TokensTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES users (id) ON DELETE CASCADE')); + static const VerificationMeta _tokenMeta = const VerificationMeta('token'); + @override + late final GeneratedColumn token = GeneratedColumn( + 'token', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); + static const VerificationMeta _externalUserIdMeta = + const VerificationMeta('externalUserId'); + @override + late final GeneratedColumn externalUserId = GeneratedColumn( + 'external_user_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _createdMeta = + const VerificationMeta('created'); + @override + late final GeneratedColumn created = GeneratedColumn( + 'created', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _expiresMeta = + const VerificationMeta('expires'); + @override + late final GeneratedColumn expires = GeneratedColumn( + 'expires', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => + [id, userId, token, externalUserId, created, expires]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'tokens'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('token')) { + context.handle( + _tokenMeta, token.isAcceptableOrUnknown(data['token']!, _tokenMeta)); + } else if (isInserting) { + context.missing(_tokenMeta); + } + if (data.containsKey('external_user_id')) { + context.handle( + _externalUserIdMeta, + externalUserId.isAcceptableOrUnknown( + data['external_user_id']!, _externalUserIdMeta)); + } else if (isInserting) { + context.missing(_externalUserIdMeta); + } + if (data.containsKey('created')) { + context.handle(_createdMeta, + created.isAcceptableOrUnknown(data['created']!, _createdMeta)); + } + if (data.containsKey('expires')) { + context.handle(_expiresMeta, + expires.isAcceptableOrUnknown(data['expires']!, _expiresMeta)); + } else if (isInserting) { + context.missing(_expiresMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Token map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Token( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + token: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}token'])!, + externalUserId: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}external_user_id'])!, + created: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created'])!, + expires: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}expires'])!, + ); + } + + @override + $TokensTable createAlias(String alias) { + return $TokensTable(attachedDatabase, alias); + } +} + +class Token extends DataClass implements Insertable { + final int id; + final int userId; + final String token; + final String externalUserId; + final DateTime created; + final DateTime expires; + const Token( + {required this.id, + required this.userId, + required this.token, + required this.externalUserId, + required this.created, + required this.expires}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['user_id'] = Variable(userId); + map['token'] = Variable(token); + map['external_user_id'] = Variable(externalUserId); + map['created'] = Variable(created); + map['expires'] = Variable(expires); + return map; + } + + TokensCompanion toCompanion(bool nullToAbsent) { + return TokensCompanion( + id: Value(id), + userId: Value(userId), + token: Value(token), + externalUserId: Value(externalUserId), + created: Value(created), + expires: Value(expires), + ); + } + + factory Token.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Token( + id: serializer.fromJson(json['id']), + userId: serializer.fromJson(json['userId']), + token: serializer.fromJson(json['token']), + externalUserId: serializer.fromJson(json['externalUserId']), + created: serializer.fromJson(json['created']), + expires: serializer.fromJson(json['expires']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'userId': serializer.toJson(userId), + 'token': serializer.toJson(token), + 'externalUserId': serializer.toJson(externalUserId), + 'created': serializer.toJson(created), + 'expires': serializer.toJson(expires), + }; + } + + Token copyWith( + {int? id, + int? userId, + String? token, + String? externalUserId, + DateTime? created, + DateTime? expires}) => + Token( + id: id ?? this.id, + userId: userId ?? this.userId, + token: token ?? this.token, + externalUserId: externalUserId ?? this.externalUserId, + created: created ?? this.created, + expires: expires ?? this.expires, + ); + @override + String toString() { + return (StringBuffer('Token(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('token: $token, ') + ..write('externalUserId: $externalUserId, ') + ..write('created: $created, ') + ..write('expires: $expires') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, userId, token, externalUserId, created, expires); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Token && + other.id == this.id && + other.userId == this.userId && + other.token == this.token && + other.externalUserId == this.externalUserId && + other.created == this.created && + other.expires == this.expires); +} + +class TokensCompanion extends UpdateCompanion { + final Value id; + final Value userId; + final Value token; + final Value externalUserId; + final Value created; + final Value expires; + const TokensCompanion({ + this.id = const Value.absent(), + this.userId = const Value.absent(), + this.token = const Value.absent(), + this.externalUserId = const Value.absent(), + this.created = const Value.absent(), + this.expires = const Value.absent(), + }); + TokensCompanion.insert({ + this.id = const Value.absent(), + required int userId, + required String token, + required String externalUserId, + this.created = const Value.absent(), + required DateTime expires, + }) : userId = Value(userId), + token = Value(token), + externalUserId = Value(externalUserId), + expires = Value(expires); + static Insertable custom({ + Expression? id, + Expression? userId, + Expression? token, + Expression? externalUserId, + Expression? created, + Expression? expires, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (userId != null) 'user_id': userId, + if (token != null) 'token': token, + if (externalUserId != null) 'external_user_id': externalUserId, + if (created != null) 'created': created, + if (expires != null) 'expires': expires, + }); + } + + TokensCompanion copyWith( + {Value? id, + Value? userId, + Value? token, + Value? externalUserId, + Value? created, + Value? expires}) { + return TokensCompanion( + id: id ?? this.id, + userId: userId ?? this.userId, + token: token ?? this.token, + externalUserId: externalUserId ?? this.externalUserId, + created: created ?? this.created, + expires: expires ?? this.expires, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (token.present) { + map['token'] = Variable(token.value); + } + if (externalUserId.present) { + map['external_user_id'] = Variable(externalUserId.value); + } + if (created.present) { + map['created'] = Variable(created.value); + } + if (expires.present) { + map['expires'] = Variable(expires.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TokensCompanion(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('token: $token, ') + ..write('externalUserId: $externalUserId, ') + ..write('created: $created, ') + ..write('expires: $expires') + ..write(')')) + .toString(); + } +} + +class $RefreshTokensTable extends RefreshTokens + with TableInfo<$RefreshTokensTable, RefreshToken> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $RefreshTokensTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES users (id) ON DELETE CASCADE')); + static const VerificationMeta _jtiMeta = const VerificationMeta('jti'); + @override + late final GeneratedColumn jti = GeneratedColumn( + 'jti', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); + static const VerificationMeta _isBlacklistedMeta = + const VerificationMeta('isBlacklisted'); + @override + late final GeneratedColumn isBlacklisted = GeneratedColumn( + 'is_blacklisted', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("is_blacklisted" IN (0, 1))'), + defaultValue: const Constant(false)); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _expiresAtMeta = + const VerificationMeta('expiresAt'); + @override + late final GeneratedColumn expiresAt = GeneratedColumn( + 'expires_at', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => + [id, userId, jti, isBlacklisted, createdAt, expiresAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'refresh_tokens'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('jti')) { + context.handle( + _jtiMeta, jti.isAcceptableOrUnknown(data['jti']!, _jtiMeta)); + } else if (isInserting) { + context.missing(_jtiMeta); + } + if (data.containsKey('is_blacklisted')) { + context.handle( + _isBlacklistedMeta, + isBlacklisted.isAcceptableOrUnknown( + data['is_blacklisted']!, _isBlacklistedMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('expires_at')) { + context.handle(_expiresAtMeta, + expiresAt.isAcceptableOrUnknown(data['expires_at']!, _expiresAtMeta)); + } else if (isInserting) { + context.missing(_expiresAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + RefreshToken map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return RefreshToken( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + jti: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}jti'])!, + isBlacklisted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_blacklisted'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + expiresAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}expires_at'])!, + ); + } + + @override + $RefreshTokensTable createAlias(String alias) { + return $RefreshTokensTable(attachedDatabase, alias); + } +} + +class RefreshToken extends DataClass implements Insertable { + final int id; + final int userId; + final String jti; + final bool isBlacklisted; + final DateTime createdAt; + final DateTime expiresAt; + const RefreshToken( + {required this.id, + required this.userId, + required this.jti, + required this.isBlacklisted, + required this.createdAt, + required this.expiresAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['user_id'] = Variable(userId); + map['jti'] = Variable(jti); + map['is_blacklisted'] = Variable(isBlacklisted); + map['created_at'] = Variable(createdAt); + map['expires_at'] = Variable(expiresAt); + return map; + } + + RefreshTokensCompanion toCompanion(bool nullToAbsent) { + return RefreshTokensCompanion( + id: Value(id), + userId: Value(userId), + jti: Value(jti), + isBlacklisted: Value(isBlacklisted), + createdAt: Value(createdAt), + expiresAt: Value(expiresAt), + ); + } + + factory RefreshToken.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return RefreshToken( + id: serializer.fromJson(json['id']), + userId: serializer.fromJson(json['userId']), + jti: serializer.fromJson(json['jti']), + isBlacklisted: serializer.fromJson(json['isBlacklisted']), + createdAt: serializer.fromJson(json['createdAt']), + expiresAt: serializer.fromJson(json['expiresAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'userId': serializer.toJson(userId), + 'jti': serializer.toJson(jti), + 'isBlacklisted': serializer.toJson(isBlacklisted), + 'createdAt': serializer.toJson(createdAt), + 'expiresAt': serializer.toJson(expiresAt), + }; + } + + RefreshToken copyWith( + {int? id, + int? userId, + String? jti, + bool? isBlacklisted, + DateTime? createdAt, + DateTime? expiresAt}) => + RefreshToken( + id: id ?? this.id, + userId: userId ?? this.userId, + jti: jti ?? this.jti, + isBlacklisted: isBlacklisted ?? this.isBlacklisted, + createdAt: createdAt ?? this.createdAt, + expiresAt: expiresAt ?? this.expiresAt, + ); + @override + String toString() { + return (StringBuffer('RefreshToken(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('jti: $jti, ') + ..write('isBlacklisted: $isBlacklisted, ') + ..write('createdAt: $createdAt, ') + ..write('expiresAt: $expiresAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, userId, jti, isBlacklisted, createdAt, expiresAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is RefreshToken && + other.id == this.id && + other.userId == this.userId && + other.jti == this.jti && + other.isBlacklisted == this.isBlacklisted && + other.createdAt == this.createdAt && + other.expiresAt == this.expiresAt); +} + +class RefreshTokensCompanion extends UpdateCompanion { + final Value id; + final Value userId; + final Value jti; + final Value isBlacklisted; + final Value createdAt; + final Value expiresAt; + const RefreshTokensCompanion({ + this.id = const Value.absent(), + this.userId = const Value.absent(), + this.jti = const Value.absent(), + this.isBlacklisted = const Value.absent(), + this.createdAt = const Value.absent(), + this.expiresAt = const Value.absent(), + }); + RefreshTokensCompanion.insert({ + this.id = const Value.absent(), + required int userId, + required String jti, + this.isBlacklisted = const Value.absent(), + this.createdAt = const Value.absent(), + required DateTime expiresAt, + }) : userId = Value(userId), + jti = Value(jti), + expiresAt = Value(expiresAt); + static Insertable custom({ + Expression? id, + Expression? userId, + Expression? jti, + Expression? isBlacklisted, + Expression? createdAt, + Expression? expiresAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (userId != null) 'user_id': userId, + if (jti != null) 'jti': jti, + if (isBlacklisted != null) 'is_blacklisted': isBlacklisted, + if (createdAt != null) 'created_at': createdAt, + if (expiresAt != null) 'expires_at': expiresAt, + }); + } + + RefreshTokensCompanion copyWith( + {Value? id, + Value? userId, + Value? jti, + Value? isBlacklisted, + Value? createdAt, + Value? expiresAt}) { + return RefreshTokensCompanion( + id: id ?? this.id, + userId: userId ?? this.userId, + jti: jti ?? this.jti, + isBlacklisted: isBlacklisted ?? this.isBlacklisted, + createdAt: createdAt ?? this.createdAt, + expiresAt: expiresAt ?? this.expiresAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (jti.present) { + map['jti'] = Variable(jti.value); + } + if (isBlacklisted.present) { + map['is_blacklisted'] = Variable(isBlacklisted.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (expiresAt.present) { + map['expires_at'] = Variable(expiresAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('RefreshTokensCompanion(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('jti: $jti, ') + ..write('isBlacklisted: $isBlacklisted, ') + ..write('createdAt: $createdAt, ') + ..write('expiresAt: $expiresAt') + ..write(')')) + .toString(); + } +} + +class $TelegramAuthCodesTable extends TelegramAuthCodes + with TableInfo<$TelegramAuthCodesTable, TelegramAuthCode> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TelegramAuthCodesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _codeMeta = const VerificationMeta('code'); + @override + late final GeneratedColumn code = GeneratedColumn( + 'code', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); + static const VerificationMeta _telegramUserIdMeta = + const VerificationMeta('telegramUserId'); + @override + late final GeneratedColumn telegramUserId = GeneratedColumn( + 'telegram_user_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _telegramUsernameMeta = + const VerificationMeta('telegramUsername'); + @override + late final GeneratedColumn telegramUsername = GeneratedColumn( + 'telegram_username', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _firstNameMeta = + const VerificationMeta('firstName'); + @override + late final GeneratedColumn firstName = GeneratedColumn( + 'first_name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _lastNameMeta = + const VerificationMeta('lastName'); + @override + late final GeneratedColumn lastName = GeneratedColumn( + 'last_name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _isUsedMeta = const VerificationMeta('isUsed'); + @override + late final GeneratedColumn isUsed = GeneratedColumn( + 'is_used', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_used" IN (0, 1))'), + defaultValue: const Constant(false)); + static const VerificationMeta _usedAtMeta = const VerificationMeta('usedAt'); + @override + late final GeneratedColumn usedAt = GeneratedColumn( + 'used_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _expiresAtMeta = + const VerificationMeta('expiresAt'); + @override + late final GeneratedColumn expiresAt = GeneratedColumn( + 'expires_at', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => [ + id, + code, + telegramUserId, + telegramUsername, + firstName, + lastName, + isUsed, + usedAt, + createdAt, + expiresAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'telegram_auth_codes'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('code')) { + context.handle( + _codeMeta, code.isAcceptableOrUnknown(data['code']!, _codeMeta)); + } else if (isInserting) { + context.missing(_codeMeta); + } + if (data.containsKey('telegram_user_id')) { + context.handle( + _telegramUserIdMeta, + telegramUserId.isAcceptableOrUnknown( + data['telegram_user_id']!, _telegramUserIdMeta)); + } else if (isInserting) { + context.missing(_telegramUserIdMeta); + } + if (data.containsKey('telegram_username')) { + context.handle( + _telegramUsernameMeta, + telegramUsername.isAcceptableOrUnknown( + data['telegram_username']!, _telegramUsernameMeta)); + } + if (data.containsKey('first_name')) { + context.handle(_firstNameMeta, + firstName.isAcceptableOrUnknown(data['first_name']!, _firstNameMeta)); + } + if (data.containsKey('last_name')) { + context.handle(_lastNameMeta, + lastName.isAcceptableOrUnknown(data['last_name']!, _lastNameMeta)); + } + if (data.containsKey('is_used')) { + context.handle(_isUsedMeta, + isUsed.isAcceptableOrUnknown(data['is_used']!, _isUsedMeta)); + } + if (data.containsKey('used_at')) { + context.handle(_usedAtMeta, + usedAt.isAcceptableOrUnknown(data['used_at']!, _usedAtMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('expires_at')) { + context.handle(_expiresAtMeta, + expiresAt.isAcceptableOrUnknown(data['expires_at']!, _expiresAtMeta)); + } else if (isInserting) { + context.missing(_expiresAtMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + TelegramAuthCode map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TelegramAuthCode( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + code: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}code'])!, + telegramUserId: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}telegram_user_id'])!, + telegramUsername: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}telegram_username']), + firstName: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}first_name']), + lastName: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}last_name']), + isUsed: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_used'])!, + usedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}used_at']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + expiresAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}expires_at'])!, + ); + } + + @override + $TelegramAuthCodesTable createAlias(String alias) { + return $TelegramAuthCodesTable(attachedDatabase, alias); + } +} + +class TelegramAuthCode extends DataClass + implements Insertable { + final int id; + final String code; + final String telegramUserId; + final String? telegramUsername; + final String? firstName; + final String? lastName; + final bool isUsed; + final DateTime? usedAt; + final DateTime createdAt; + final DateTime expiresAt; + const TelegramAuthCode( + {required this.id, + required this.code, + required this.telegramUserId, + this.telegramUsername, + this.firstName, + this.lastName, + required this.isUsed, + this.usedAt, + required this.createdAt, + required this.expiresAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['code'] = Variable(code); + map['telegram_user_id'] = Variable(telegramUserId); + if (!nullToAbsent || telegramUsername != null) { + map['telegram_username'] = Variable(telegramUsername); + } + if (!nullToAbsent || firstName != null) { + map['first_name'] = Variable(firstName); + } + if (!nullToAbsent || lastName != null) { + map['last_name'] = Variable(lastName); + } + map['is_used'] = Variable(isUsed); + if (!nullToAbsent || usedAt != null) { + map['used_at'] = Variable(usedAt); + } + map['created_at'] = Variable(createdAt); + map['expires_at'] = Variable(expiresAt); + return map; + } + + TelegramAuthCodesCompanion toCompanion(bool nullToAbsent) { + return TelegramAuthCodesCompanion( + id: Value(id), + code: Value(code), + telegramUserId: Value(telegramUserId), + telegramUsername: telegramUsername == null && nullToAbsent + ? const Value.absent() + : Value(telegramUsername), + firstName: firstName == null && nullToAbsent + ? const Value.absent() + : Value(firstName), + lastName: lastName == null && nullToAbsent + ? const Value.absent() + : Value(lastName), + isUsed: Value(isUsed), + usedAt: + usedAt == null && nullToAbsent ? const Value.absent() : Value(usedAt), + createdAt: Value(createdAt), + expiresAt: Value(expiresAt), + ); + } + + factory TelegramAuthCode.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TelegramAuthCode( + id: serializer.fromJson(json['id']), + code: serializer.fromJson(json['code']), + telegramUserId: serializer.fromJson(json['telegramUserId']), + telegramUsername: serializer.fromJson(json['telegramUsername']), + firstName: serializer.fromJson(json['firstName']), + lastName: serializer.fromJson(json['lastName']), + isUsed: serializer.fromJson(json['isUsed']), + usedAt: serializer.fromJson(json['usedAt']), + createdAt: serializer.fromJson(json['createdAt']), + expiresAt: serializer.fromJson(json['expiresAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'code': serializer.toJson(code), + 'telegramUserId': serializer.toJson(telegramUserId), + 'telegramUsername': serializer.toJson(telegramUsername), + 'firstName': serializer.toJson(firstName), + 'lastName': serializer.toJson(lastName), + 'isUsed': serializer.toJson(isUsed), + 'usedAt': serializer.toJson(usedAt), + 'createdAt': serializer.toJson(createdAt), + 'expiresAt': serializer.toJson(expiresAt), + }; + } + + TelegramAuthCode copyWith( + {int? id, + String? code, + String? telegramUserId, + Value telegramUsername = const Value.absent(), + Value firstName = const Value.absent(), + Value lastName = const Value.absent(), + bool? isUsed, + Value usedAt = const Value.absent(), + DateTime? createdAt, + DateTime? expiresAt}) => + TelegramAuthCode( + id: id ?? this.id, + code: code ?? this.code, + telegramUserId: telegramUserId ?? this.telegramUserId, + telegramUsername: telegramUsername.present + ? telegramUsername.value + : this.telegramUsername, + firstName: firstName.present ? firstName.value : this.firstName, + lastName: lastName.present ? lastName.value : this.lastName, + isUsed: isUsed ?? this.isUsed, + usedAt: usedAt.present ? usedAt.value : this.usedAt, + createdAt: createdAt ?? this.createdAt, + expiresAt: expiresAt ?? this.expiresAt, + ); + @override + String toString() { + return (StringBuffer('TelegramAuthCode(') + ..write('id: $id, ') + ..write('code: $code, ') + ..write('telegramUserId: $telegramUserId, ') + ..write('telegramUsername: $telegramUsername, ') + ..write('firstName: $firstName, ') + ..write('lastName: $lastName, ') + ..write('isUsed: $isUsed, ') + ..write('usedAt: $usedAt, ') + ..write('createdAt: $createdAt, ') + ..write('expiresAt: $expiresAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, code, telegramUserId, telegramUsername, + firstName, lastName, isUsed, usedAt, createdAt, expiresAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TelegramAuthCode && + other.id == this.id && + other.code == this.code && + other.telegramUserId == this.telegramUserId && + other.telegramUsername == this.telegramUsername && + other.firstName == this.firstName && + other.lastName == this.lastName && + other.isUsed == this.isUsed && + other.usedAt == this.usedAt && + other.createdAt == this.createdAt && + other.expiresAt == this.expiresAt); +} + +class TelegramAuthCodesCompanion extends UpdateCompanion { + final Value id; + final Value code; + final Value telegramUserId; + final Value telegramUsername; + final Value firstName; + final Value lastName; + final Value isUsed; + final Value usedAt; + final Value createdAt; + final Value expiresAt; + const TelegramAuthCodesCompanion({ + this.id = const Value.absent(), + this.code = const Value.absent(), + this.telegramUserId = const Value.absent(), + this.telegramUsername = const Value.absent(), + this.firstName = const Value.absent(), + this.lastName = const Value.absent(), + this.isUsed = const Value.absent(), + this.usedAt = const Value.absent(), + this.createdAt = const Value.absent(), + this.expiresAt = const Value.absent(), + }); + TelegramAuthCodesCompanion.insert({ + this.id = const Value.absent(), + required String code, + required String telegramUserId, + this.telegramUsername = const Value.absent(), + this.firstName = const Value.absent(), + this.lastName = const Value.absent(), + this.isUsed = const Value.absent(), + this.usedAt = const Value.absent(), + this.createdAt = const Value.absent(), + required DateTime expiresAt, + }) : code = Value(code), + telegramUserId = Value(telegramUserId), + expiresAt = Value(expiresAt); + static Insertable custom({ + Expression? id, + Expression? code, + Expression? telegramUserId, + Expression? telegramUsername, + Expression? firstName, + Expression? lastName, + Expression? isUsed, + Expression? usedAt, + Expression? createdAt, + Expression? expiresAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (code != null) 'code': code, + if (telegramUserId != null) 'telegram_user_id': telegramUserId, + if (telegramUsername != null) 'telegram_username': telegramUsername, + if (firstName != null) 'first_name': firstName, + if (lastName != null) 'last_name': lastName, + if (isUsed != null) 'is_used': isUsed, + if (usedAt != null) 'used_at': usedAt, + if (createdAt != null) 'created_at': createdAt, + if (expiresAt != null) 'expires_at': expiresAt, + }); + } + + TelegramAuthCodesCompanion copyWith( + {Value? id, + Value? code, + Value? telegramUserId, + Value? telegramUsername, + Value? firstName, + Value? lastName, + Value? isUsed, + Value? usedAt, + Value? createdAt, + Value? expiresAt}) { + return TelegramAuthCodesCompanion( + id: id ?? this.id, + code: code ?? this.code, + telegramUserId: telegramUserId ?? this.telegramUserId, + telegramUsername: telegramUsername ?? this.telegramUsername, + firstName: firstName ?? this.firstName, + lastName: lastName ?? this.lastName, + isUsed: isUsed ?? this.isUsed, + usedAt: usedAt ?? this.usedAt, + createdAt: createdAt ?? this.createdAt, + expiresAt: expiresAt ?? this.expiresAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (code.present) { + map['code'] = Variable(code.value); + } + if (telegramUserId.present) { + map['telegram_user_id'] = Variable(telegramUserId.value); + } + if (telegramUsername.present) { + map['telegram_username'] = Variable(telegramUsername.value); + } + if (firstName.present) { + map['first_name'] = Variable(firstName.value); + } + if (lastName.present) { + map['last_name'] = Variable(lastName.value); + } + if (isUsed.present) { + map['is_used'] = Variable(isUsed.value); + } + if (usedAt.present) { + map['used_at'] = Variable(usedAt.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (expiresAt.present) { + map['expires_at'] = Variable(expiresAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TelegramAuthCodesCompanion(') + ..write('id: $id, ') + ..write('code: $code, ') + ..write('telegramUserId: $telegramUserId, ') + ..write('telegramUsername: $telegramUsername, ') + ..write('firstName: $firstName, ') + ..write('lastName: $lastName, ') + ..write('isUsed: $isUsed, ') + ..write('usedAt: $usedAt, ') + ..write('createdAt: $createdAt, ') + ..write('expiresAt: $expiresAt') + ..write(')')) + .toString(); + } +} + +class $CardPacksTable extends CardPacks + with TableInfo<$CardPacksTable, CardPack> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $CardPacksTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _titleMeta = const VerificationMeta('title'); + @override + late final GeneratedColumn title = GeneratedColumn( + 'title', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _subtitleMeta = + const VerificationMeta('subtitle'); + @override + late final GeneratedColumn subtitle = GeneratedColumn( + 'subtitle', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _descriptionMeta = + const VerificationMeta('description'); + @override + late final GeneratedColumn description = GeneratedColumn( + 'description', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _colorMeta = const VerificationMeta('color'); + @override + late final GeneratedColumn color = GeneratedColumn( + 'color', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _coverMeta = const VerificationMeta('cover'); + @override + late final GeneratedColumn cover = GeneratedColumn( + 'cover', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _sizeMeta = const VerificationMeta('size'); + @override + late final GeneratedColumn size = GeneratedColumn( + 'size', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _versionMeta = + const VerificationMeta('version'); + @override + late final GeneratedColumn version = GeneratedColumn( + 'version', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _orderMeta = const VerificationMeta('order'); + @override + late final GeneratedColumn order = GeneratedColumn( + 'order', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _enabledMeta = + const VerificationMeta('enabled'); + @override + late final GeneratedColumn enabled = GeneratedColumn( + 'enabled', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("enabled" IN (0, 1))'), + defaultValue: const Constant(true)); + static const VerificationMeta _cardsOrderMeta = + const VerificationMeta('cardsOrder'); + @override + late final GeneratedColumnWithTypeConverter, String> cardsOrder = + GeneratedColumn('cards_order', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter>($CardPacksTable.$convertercardsOrder); + static const VerificationMeta _googlePlayIdMeta = + const VerificationMeta('googlePlayId'); + @override + late final GeneratedColumn googlePlayId = GeneratedColumn( + 'google_play_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _rustoreIdMeta = + const VerificationMeta('rustoreId'); + @override + late final GeneratedColumn rustoreId = GeneratedColumn( + 'rustore_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _appStoreIdMeta = + const VerificationMeta('appStoreId'); + @override + late final GeneratedColumn appStoreId = GeneratedColumn( + 'app_store_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _priceMeta = const VerificationMeta('price'); + @override + late final GeneratedColumn price = GeneratedColumn( + 'price', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _currencyMeta = + const VerificationMeta('currency'); + @override + late final GeneratedColumn currency = GeneratedColumn( + 'currency', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('RUB')); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _isDeletedMeta = + const VerificationMeta('isDeleted'); + @override + late final GeneratedColumn isDeleted = GeneratedColumn( + 'is_deleted', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_deleted" IN (0, 1))'), + defaultValue: const Constant(false)); + @override + List get $columns => [ + id, + title, + subtitle, + description, + color, + cover, + size, + version, + order, + enabled, + cardsOrder, + googlePlayId, + rustoreId, + appStoreId, + price, + currency, + createdAt, + updatedAt, + isDeleted + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'card_packs'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('title')) { + context.handle( + _titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta)); + } else if (isInserting) { + context.missing(_titleMeta); + } + if (data.containsKey('subtitle')) { + context.handle(_subtitleMeta, + subtitle.isAcceptableOrUnknown(data['subtitle']!, _subtitleMeta)); + } else if (isInserting) { + context.missing(_subtitleMeta); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, _descriptionMeta)); + } + if (data.containsKey('color')) { + context.handle( + _colorMeta, color.isAcceptableOrUnknown(data['color']!, _colorMeta)); + } + if (data.containsKey('cover')) { + context.handle( + _coverMeta, cover.isAcceptableOrUnknown(data['cover']!, _coverMeta)); + } + if (data.containsKey('size')) { + context.handle( + _sizeMeta, size.isAcceptableOrUnknown(data['size']!, _sizeMeta)); + } else if (isInserting) { + context.missing(_sizeMeta); + } + if (data.containsKey('version')) { + context.handle(_versionMeta, + version.isAcceptableOrUnknown(data['version']!, _versionMeta)); + } + if (data.containsKey('order')) { + context.handle( + _orderMeta, order.isAcceptableOrUnknown(data['order']!, _orderMeta)); + } + if (data.containsKey('enabled')) { + context.handle(_enabledMeta, + enabled.isAcceptableOrUnknown(data['enabled']!, _enabledMeta)); + } + context.handle(_cardsOrderMeta, const VerificationResult.success()); + if (data.containsKey('google_play_id')) { + context.handle( + _googlePlayIdMeta, + googlePlayId.isAcceptableOrUnknown( + data['google_play_id']!, _googlePlayIdMeta)); + } + if (data.containsKey('rustore_id')) { + context.handle(_rustoreIdMeta, + rustoreId.isAcceptableOrUnknown(data['rustore_id']!, _rustoreIdMeta)); + } + if (data.containsKey('app_store_id')) { + context.handle( + _appStoreIdMeta, + appStoreId.isAcceptableOrUnknown( + data['app_store_id']!, _appStoreIdMeta)); + } + if (data.containsKey('price')) { + context.handle( + _priceMeta, price.isAcceptableOrUnknown(data['price']!, _priceMeta)); + } + if (data.containsKey('currency')) { + context.handle(_currencyMeta, + currency.isAcceptableOrUnknown(data['currency']!, _currencyMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + if (data.containsKey('is_deleted')) { + context.handle(_isDeletedMeta, + isDeleted.isAcceptableOrUnknown(data['is_deleted']!, _isDeletedMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + CardPack map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return CardPack( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + title: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}title'])!, + subtitle: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}subtitle'])!, + description: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}description']), + color: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}color']), + cover: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}cover']), + size: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}size'])!, + version: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}version']), + order: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}order'])!, + enabled: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}enabled'])!, + cardsOrder: $CardPacksTable.$convertercardsOrder.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}cards_order'])!), + googlePlayId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}google_play_id']), + rustoreId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}rustore_id']), + appStoreId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}app_store_id']), + price: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}price']), + currency: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}currency'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + isDeleted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_deleted'])!, + ); + } + + @override + $CardPacksTable createAlias(String alias) { + return $CardPacksTable(attachedDatabase, alias); + } + + static TypeConverter, String> $convertercardsOrder = + const IntListConverter(); +} + +class CardPack extends DataClass implements Insertable { + final int id; + final String title; + final String subtitle; + final String? description; + final String? color; + final String? cover; + final int size; + final String? version; + final int order; + final bool enabled; + final List cardsOrder; + final String? googlePlayId; + final String? rustoreId; + final String? appStoreId; + final String? price; + final String currency; + final DateTime createdAt; + final DateTime updatedAt; + final bool isDeleted; + const CardPack( + {required this.id, + required this.title, + required this.subtitle, + this.description, + this.color, + this.cover, + required this.size, + this.version, + required this.order, + required this.enabled, + required this.cardsOrder, + this.googlePlayId, + this.rustoreId, + this.appStoreId, + this.price, + required this.currency, + required this.createdAt, + required this.updatedAt, + required this.isDeleted}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['title'] = Variable(title); + map['subtitle'] = Variable(subtitle); + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || cover != null) { + map['cover'] = Variable(cover); + } + map['size'] = Variable(size); + if (!nullToAbsent || version != null) { + map['version'] = Variable(version); + } + map['order'] = Variable(order); + map['enabled'] = Variable(enabled); + { + map['cards_order'] = Variable( + $CardPacksTable.$convertercardsOrder.toSql(cardsOrder)); + } + if (!nullToAbsent || googlePlayId != null) { + map['google_play_id'] = Variable(googlePlayId); + } + if (!nullToAbsent || rustoreId != null) { + map['rustore_id'] = Variable(rustoreId); + } + if (!nullToAbsent || appStoreId != null) { + map['app_store_id'] = Variable(appStoreId); + } + if (!nullToAbsent || price != null) { + map['price'] = Variable(price); + } + map['currency'] = Variable(currency); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['is_deleted'] = Variable(isDeleted); + return map; + } + + CardPacksCompanion toCompanion(bool nullToAbsent) { + return CardPacksCompanion( + id: Value(id), + title: Value(title), + subtitle: Value(subtitle), + description: description == null && nullToAbsent + ? const Value.absent() + : Value(description), + color: + color == null && nullToAbsent ? const Value.absent() : Value(color), + cover: + cover == null && nullToAbsent ? const Value.absent() : Value(cover), + size: Value(size), + version: version == null && nullToAbsent + ? const Value.absent() + : Value(version), + order: Value(order), + enabled: Value(enabled), + cardsOrder: Value(cardsOrder), + googlePlayId: googlePlayId == null && nullToAbsent + ? const Value.absent() + : Value(googlePlayId), + rustoreId: rustoreId == null && nullToAbsent + ? const Value.absent() + : Value(rustoreId), + appStoreId: appStoreId == null && nullToAbsent + ? const Value.absent() + : Value(appStoreId), + price: + price == null && nullToAbsent ? const Value.absent() : Value(price), + currency: Value(currency), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + isDeleted: Value(isDeleted), + ); + } + + factory CardPack.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return CardPack( + id: serializer.fromJson(json['id']), + title: serializer.fromJson(json['title']), + subtitle: serializer.fromJson(json['subtitle']), + description: serializer.fromJson(json['description']), + color: serializer.fromJson(json['color']), + cover: serializer.fromJson(json['cover']), + size: serializer.fromJson(json['size']), + version: serializer.fromJson(json['version']), + order: serializer.fromJson(json['order']), + enabled: serializer.fromJson(json['enabled']), + cardsOrder: serializer.fromJson>(json['cardsOrder']), + googlePlayId: serializer.fromJson(json['googlePlayId']), + rustoreId: serializer.fromJson(json['rustoreId']), + appStoreId: serializer.fromJson(json['appStoreId']), + price: serializer.fromJson(json['price']), + currency: serializer.fromJson(json['currency']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + isDeleted: serializer.fromJson(json['isDeleted']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'title': serializer.toJson(title), + 'subtitle': serializer.toJson(subtitle), + 'description': serializer.toJson(description), + 'color': serializer.toJson(color), + 'cover': serializer.toJson(cover), + 'size': serializer.toJson(size), + 'version': serializer.toJson(version), + 'order': serializer.toJson(order), + 'enabled': serializer.toJson(enabled), + 'cardsOrder': serializer.toJson>(cardsOrder), + 'googlePlayId': serializer.toJson(googlePlayId), + 'rustoreId': serializer.toJson(rustoreId), + 'appStoreId': serializer.toJson(appStoreId), + 'price': serializer.toJson(price), + 'currency': serializer.toJson(currency), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'isDeleted': serializer.toJson(isDeleted), + }; + } + + CardPack copyWith( + {int? id, + String? title, + String? subtitle, + Value description = const Value.absent(), + Value color = const Value.absent(), + Value cover = const Value.absent(), + int? size, + Value version = const Value.absent(), + int? order, + bool? enabled, + List? cardsOrder, + Value googlePlayId = const Value.absent(), + Value rustoreId = const Value.absent(), + Value appStoreId = const Value.absent(), + Value price = const Value.absent(), + String? currency, + DateTime? createdAt, + DateTime? updatedAt, + bool? isDeleted}) => + CardPack( + id: id ?? this.id, + title: title ?? this.title, + subtitle: subtitle ?? this.subtitle, + description: description.present ? description.value : this.description, + color: color.present ? color.value : this.color, + cover: cover.present ? cover.value : this.cover, + size: size ?? this.size, + version: version.present ? version.value : this.version, + order: order ?? this.order, + enabled: enabled ?? this.enabled, + cardsOrder: cardsOrder ?? this.cardsOrder, + googlePlayId: + googlePlayId.present ? googlePlayId.value : this.googlePlayId, + rustoreId: rustoreId.present ? rustoreId.value : this.rustoreId, + appStoreId: appStoreId.present ? appStoreId.value : this.appStoreId, + price: price.present ? price.value : this.price, + currency: currency ?? this.currency, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + @override + String toString() { + return (StringBuffer('CardPack(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('subtitle: $subtitle, ') + ..write('description: $description, ') + ..write('color: $color, ') + ..write('cover: $cover, ') + ..write('size: $size, ') + ..write('version: $version, ') + ..write('order: $order, ') + ..write('enabled: $enabled, ') + ..write('cardsOrder: $cardsOrder, ') + ..write('googlePlayId: $googlePlayId, ') + ..write('rustoreId: $rustoreId, ') + ..write('appStoreId: $appStoreId, ') + ..write('price: $price, ') + ..write('currency: $currency, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + title, + subtitle, + description, + color, + cover, + size, + version, + order, + enabled, + cardsOrder, + googlePlayId, + rustoreId, + appStoreId, + price, + currency, + createdAt, + updatedAt, + isDeleted); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is CardPack && + other.id == this.id && + other.title == this.title && + other.subtitle == this.subtitle && + other.description == this.description && + other.color == this.color && + other.cover == this.cover && + other.size == this.size && + other.version == this.version && + other.order == this.order && + other.enabled == this.enabled && + other.cardsOrder == this.cardsOrder && + other.googlePlayId == this.googlePlayId && + other.rustoreId == this.rustoreId && + other.appStoreId == this.appStoreId && + other.price == this.price && + other.currency == this.currency && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.isDeleted == this.isDeleted); +} + +class CardPacksCompanion extends UpdateCompanion { + final Value id; + final Value title; + final Value subtitle; + final Value description; + final Value color; + final Value cover; + final Value size; + final Value version; + final Value order; + final Value enabled; + final Value> cardsOrder; + final Value googlePlayId; + final Value rustoreId; + final Value appStoreId; + final Value price; + final Value currency; + final Value createdAt; + final Value updatedAt; + final Value isDeleted; + const CardPacksCompanion({ + this.id = const Value.absent(), + this.title = const Value.absent(), + this.subtitle = const Value.absent(), + this.description = const Value.absent(), + this.color = const Value.absent(), + this.cover = const Value.absent(), + this.size = const Value.absent(), + this.version = const Value.absent(), + this.order = const Value.absent(), + this.enabled = const Value.absent(), + this.cardsOrder = const Value.absent(), + this.googlePlayId = const Value.absent(), + this.rustoreId = const Value.absent(), + this.appStoreId = const Value.absent(), + this.price = const Value.absent(), + this.currency = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }); + CardPacksCompanion.insert({ + this.id = const Value.absent(), + required String title, + required String subtitle, + this.description = const Value.absent(), + this.color = const Value.absent(), + this.cover = const Value.absent(), + required int size, + this.version = const Value.absent(), + this.order = const Value.absent(), + this.enabled = const Value.absent(), + this.cardsOrder = const Value.absent(), + this.googlePlayId = const Value.absent(), + this.rustoreId = const Value.absent(), + this.appStoreId = const Value.absent(), + this.price = const Value.absent(), + this.currency = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }) : title = Value(title), + subtitle = Value(subtitle), + size = Value(size); + static Insertable custom({ + Expression? id, + Expression? title, + Expression? subtitle, + Expression? description, + Expression? color, + Expression? cover, + Expression? size, + Expression? version, + Expression? order, + Expression? enabled, + Expression? cardsOrder, + Expression? googlePlayId, + Expression? rustoreId, + Expression? appStoreId, + Expression? price, + Expression? currency, + Expression? createdAt, + Expression? updatedAt, + Expression? isDeleted, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (title != null) 'title': title, + if (subtitle != null) 'subtitle': subtitle, + if (description != null) 'description': description, + if (color != null) 'color': color, + if (cover != null) 'cover': cover, + if (size != null) 'size': size, + if (version != null) 'version': version, + if (order != null) 'order': order, + if (enabled != null) 'enabled': enabled, + if (cardsOrder != null) 'cards_order': cardsOrder, + if (googlePlayId != null) 'google_play_id': googlePlayId, + if (rustoreId != null) 'rustore_id': rustoreId, + if (appStoreId != null) 'app_store_id': appStoreId, + if (price != null) 'price': price, + if (currency != null) 'currency': currency, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (isDeleted != null) 'is_deleted': isDeleted, + }); + } + + CardPacksCompanion copyWith( + {Value? id, + Value? title, + Value? subtitle, + Value? description, + Value? color, + Value? cover, + Value? size, + Value? version, + Value? order, + Value? enabled, + Value>? cardsOrder, + Value? googlePlayId, + Value? rustoreId, + Value? appStoreId, + Value? price, + Value? currency, + Value? createdAt, + Value? updatedAt, + Value? isDeleted}) { + return CardPacksCompanion( + id: id ?? this.id, + title: title ?? this.title, + subtitle: subtitle ?? this.subtitle, + description: description ?? this.description, + color: color ?? this.color, + cover: cover ?? this.cover, + size: size ?? this.size, + version: version ?? this.version, + order: order ?? this.order, + enabled: enabled ?? this.enabled, + cardsOrder: cardsOrder ?? this.cardsOrder, + googlePlayId: googlePlayId ?? this.googlePlayId, + rustoreId: rustoreId ?? this.rustoreId, + appStoreId: appStoreId ?? this.appStoreId, + price: price ?? this.price, + currency: currency ?? this.currency, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (title.present) { + map['title'] = Variable(title.value); + } + if (subtitle.present) { + map['subtitle'] = Variable(subtitle.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (cover.present) { + map['cover'] = Variable(cover.value); + } + if (size.present) { + map['size'] = Variable(size.value); + } + if (version.present) { + map['version'] = Variable(version.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + if (enabled.present) { + map['enabled'] = Variable(enabled.value); + } + if (cardsOrder.present) { + map['cards_order'] = Variable( + $CardPacksTable.$convertercardsOrder.toSql(cardsOrder.value)); + } + if (googlePlayId.present) { + map['google_play_id'] = Variable(googlePlayId.value); + } + if (rustoreId.present) { + map['rustore_id'] = Variable(rustoreId.value); + } + if (appStoreId.present) { + map['app_store_id'] = Variable(appStoreId.value); + } + if (price.present) { + map['price'] = Variable(price.value); + } + if (currency.present) { + map['currency'] = Variable(currency.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (isDeleted.present) { + map['is_deleted'] = Variable(isDeleted.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CardPacksCompanion(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('subtitle: $subtitle, ') + ..write('description: $description, ') + ..write('color: $color, ') + ..write('cover: $cover, ') + ..write('size: $size, ') + ..write('version: $version, ') + ..write('order: $order, ') + ..write('enabled: $enabled, ') + ..write('cardsOrder: $cardsOrder, ') + ..write('googlePlayId: $googlePlayId, ') + ..write('rustoreId: $rustoreId, ') + ..write('appStoreId: $appStoreId, ') + ..write('price: $price, ') + ..write('currency: $currency, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } +} + +class $GameCardsTable extends GameCards + with TableInfo<$GameCardsTable, GameCard> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $GameCardsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _packIdMeta = const VerificationMeta('packId'); + @override + late final GeneratedColumn packId = GeneratedColumn( + 'pack_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES card_packs (id) ON DELETE CASCADE')); + static const VerificationMeta _originalMeta = + const VerificationMeta('original'); + @override + late final GeneratedColumn original = GeneratedColumn( + 'original', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _translationMeta = + const VerificationMeta('translation'); + @override + late final GeneratedColumn translation = GeneratedColumn( + 'translation', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _mnemoMeta = const VerificationMeta('mnemo'); + @override + late final GeneratedColumn mnemo = GeneratedColumn( + 'mnemo', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _imageMeta = const VerificationMeta('image'); + @override + late final GeneratedColumn image = GeneratedColumn( + 'image', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _imageBackMeta = + const VerificationMeta('imageBack'); + @override + late final GeneratedColumn imageBack = GeneratedColumn( + 'image_back', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _transcriptionMeta = + const VerificationMeta('transcription'); + @override + late final GeneratedColumn transcription = GeneratedColumn( + 'transcription', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _transcriptionMnemoMeta = + const VerificationMeta('transcriptionMnemo'); + @override + late final GeneratedColumn transcriptionMnemo = + GeneratedColumn('transcription_mnemo', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _backMeta = const VerificationMeta('back'); + @override + late final GeneratedColumn back = GeneratedColumn( + 'back', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _isDeletedMeta = + const VerificationMeta('isDeleted'); + @override + late final GeneratedColumn isDeleted = GeneratedColumn( + 'is_deleted', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_deleted" IN (0, 1))'), + defaultValue: const Constant(false)); + @override + List get $columns => [ + id, + packId, + original, + translation, + mnemo, + image, + imageBack, + transcription, + transcriptionMnemo, + back, + createdAt, + updatedAt, + isDeleted + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'game_cards'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('pack_id')) { + context.handle(_packIdMeta, + packId.isAcceptableOrUnknown(data['pack_id']!, _packIdMeta)); + } else if (isInserting) { + context.missing(_packIdMeta); + } + if (data.containsKey('original')) { + context.handle(_originalMeta, + original.isAcceptableOrUnknown(data['original']!, _originalMeta)); + } else if (isInserting) { + context.missing(_originalMeta); + } + if (data.containsKey('translation')) { + context.handle( + _translationMeta, + translation.isAcceptableOrUnknown( + data['translation']!, _translationMeta)); + } else if (isInserting) { + context.missing(_translationMeta); + } + if (data.containsKey('mnemo')) { + context.handle( + _mnemoMeta, mnemo.isAcceptableOrUnknown(data['mnemo']!, _mnemoMeta)); + } + if (data.containsKey('image')) { + context.handle( + _imageMeta, image.isAcceptableOrUnknown(data['image']!, _imageMeta)); + } else if (isInserting) { + context.missing(_imageMeta); + } + if (data.containsKey('image_back')) { + context.handle(_imageBackMeta, + imageBack.isAcceptableOrUnknown(data['image_back']!, _imageBackMeta)); + } + if (data.containsKey('transcription')) { + context.handle( + _transcriptionMeta, + transcription.isAcceptableOrUnknown( + data['transcription']!, _transcriptionMeta)); + } + if (data.containsKey('transcription_mnemo')) { + context.handle( + _transcriptionMnemoMeta, + transcriptionMnemo.isAcceptableOrUnknown( + data['transcription_mnemo']!, _transcriptionMnemoMeta)); + } + if (data.containsKey('back')) { + context.handle( + _backMeta, back.isAcceptableOrUnknown(data['back']!, _backMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + if (data.containsKey('is_deleted')) { + context.handle(_isDeletedMeta, + isDeleted.isAcceptableOrUnknown(data['is_deleted']!, _isDeletedMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + GameCard map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return GameCard( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + packId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}pack_id'])!, + original: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}original'])!, + translation: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}translation'])!, + mnemo: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}mnemo']), + image: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}image'])!, + imageBack: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}image_back']), + transcription: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}transcription']), + transcriptionMnemo: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}transcription_mnemo']), + back: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}back']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + isDeleted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_deleted'])!, + ); + } + + @override + $GameCardsTable createAlias(String alias) { + return $GameCardsTable(attachedDatabase, alias); + } +} + +class GameCard extends DataClass implements Insertable { + final int id; + final int packId; + final String original; + final String translation; + final String? mnemo; + final String image; + final String? imageBack; + final String? transcription; + final String? transcriptionMnemo; + final String? back; + final DateTime createdAt; + final DateTime updatedAt; + final bool isDeleted; + const GameCard( + {required this.id, + required this.packId, + required this.original, + required this.translation, + this.mnemo, + required this.image, + this.imageBack, + this.transcription, + this.transcriptionMnemo, + this.back, + required this.createdAt, + required this.updatedAt, + required this.isDeleted}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['pack_id'] = Variable(packId); + map['original'] = Variable(original); + map['translation'] = Variable(translation); + if (!nullToAbsent || mnemo != null) { + map['mnemo'] = Variable(mnemo); + } + map['image'] = Variable(image); + if (!nullToAbsent || imageBack != null) { + map['image_back'] = Variable(imageBack); + } + if (!nullToAbsent || transcription != null) { + map['transcription'] = Variable(transcription); + } + if (!nullToAbsent || transcriptionMnemo != null) { + map['transcription_mnemo'] = Variable(transcriptionMnemo); + } + if (!nullToAbsent || back != null) { + map['back'] = Variable(back); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['is_deleted'] = Variable(isDeleted); + return map; + } + + GameCardsCompanion toCompanion(bool nullToAbsent) { + return GameCardsCompanion( + id: Value(id), + packId: Value(packId), + original: Value(original), + translation: Value(translation), + mnemo: + mnemo == null && nullToAbsent ? const Value.absent() : Value(mnemo), + image: Value(image), + imageBack: imageBack == null && nullToAbsent + ? const Value.absent() + : Value(imageBack), + transcription: transcription == null && nullToAbsent + ? const Value.absent() + : Value(transcription), + transcriptionMnemo: transcriptionMnemo == null && nullToAbsent + ? const Value.absent() + : Value(transcriptionMnemo), + back: back == null && nullToAbsent ? const Value.absent() : Value(back), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + isDeleted: Value(isDeleted), + ); + } + + factory GameCard.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return GameCard( + id: serializer.fromJson(json['id']), + packId: serializer.fromJson(json['packId']), + original: serializer.fromJson(json['original']), + translation: serializer.fromJson(json['translation']), + mnemo: serializer.fromJson(json['mnemo']), + image: serializer.fromJson(json['image']), + imageBack: serializer.fromJson(json['imageBack']), + transcription: serializer.fromJson(json['transcription']), + transcriptionMnemo: + serializer.fromJson(json['transcriptionMnemo']), + back: serializer.fromJson(json['back']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + isDeleted: serializer.fromJson(json['isDeleted']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'packId': serializer.toJson(packId), + 'original': serializer.toJson(original), + 'translation': serializer.toJson(translation), + 'mnemo': serializer.toJson(mnemo), + 'image': serializer.toJson(image), + 'imageBack': serializer.toJson(imageBack), + 'transcription': serializer.toJson(transcription), + 'transcriptionMnemo': serializer.toJson(transcriptionMnemo), + 'back': serializer.toJson(back), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'isDeleted': serializer.toJson(isDeleted), + }; + } + + GameCard copyWith( + {int? id, + int? packId, + String? original, + String? translation, + Value mnemo = const Value.absent(), + String? image, + Value imageBack = const Value.absent(), + Value transcription = const Value.absent(), + Value transcriptionMnemo = const Value.absent(), + Value back = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + bool? isDeleted}) => + GameCard( + id: id ?? this.id, + packId: packId ?? this.packId, + original: original ?? this.original, + translation: translation ?? this.translation, + mnemo: mnemo.present ? mnemo.value : this.mnemo, + image: image ?? this.image, + imageBack: imageBack.present ? imageBack.value : this.imageBack, + transcription: + transcription.present ? transcription.value : this.transcription, + transcriptionMnemo: transcriptionMnemo.present + ? transcriptionMnemo.value + : this.transcriptionMnemo, + back: back.present ? back.value : this.back, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + @override + String toString() { + return (StringBuffer('GameCard(') + ..write('id: $id, ') + ..write('packId: $packId, ') + ..write('original: $original, ') + ..write('translation: $translation, ') + ..write('mnemo: $mnemo, ') + ..write('image: $image, ') + ..write('imageBack: $imageBack, ') + ..write('transcription: $transcription, ') + ..write('transcriptionMnemo: $transcriptionMnemo, ') + ..write('back: $back, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + packId, + original, + translation, + mnemo, + image, + imageBack, + transcription, + transcriptionMnemo, + back, + createdAt, + updatedAt, + isDeleted); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GameCard && + other.id == this.id && + other.packId == this.packId && + other.original == this.original && + other.translation == this.translation && + other.mnemo == this.mnemo && + other.image == this.image && + other.imageBack == this.imageBack && + other.transcription == this.transcription && + other.transcriptionMnemo == this.transcriptionMnemo && + other.back == this.back && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.isDeleted == this.isDeleted); +} + +class GameCardsCompanion extends UpdateCompanion { + final Value id; + final Value packId; + final Value original; + final Value translation; + final Value mnemo; + final Value image; + final Value imageBack; + final Value transcription; + final Value transcriptionMnemo; + final Value back; + final Value createdAt; + final Value updatedAt; + final Value isDeleted; + const GameCardsCompanion({ + this.id = const Value.absent(), + this.packId = const Value.absent(), + this.original = const Value.absent(), + this.translation = const Value.absent(), + this.mnemo = const Value.absent(), + this.image = const Value.absent(), + this.imageBack = const Value.absent(), + this.transcription = const Value.absent(), + this.transcriptionMnemo = const Value.absent(), + this.back = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }); + GameCardsCompanion.insert({ + this.id = const Value.absent(), + required int packId, + required String original, + required String translation, + this.mnemo = const Value.absent(), + required String image, + this.imageBack = const Value.absent(), + this.transcription = const Value.absent(), + this.transcriptionMnemo = const Value.absent(), + this.back = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }) : packId = Value(packId), + original = Value(original), + translation = Value(translation), + image = Value(image); + static Insertable custom({ + Expression? id, + Expression? packId, + Expression? original, + Expression? translation, + Expression? mnemo, + Expression? image, + Expression? imageBack, + Expression? transcription, + Expression? transcriptionMnemo, + Expression? back, + Expression? createdAt, + Expression? updatedAt, + Expression? isDeleted, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (packId != null) 'pack_id': packId, + if (original != null) 'original': original, + if (translation != null) 'translation': translation, + if (mnemo != null) 'mnemo': mnemo, + if (image != null) 'image': image, + if (imageBack != null) 'image_back': imageBack, + if (transcription != null) 'transcription': transcription, + if (transcriptionMnemo != null) 'transcription_mnemo': transcriptionMnemo, + if (back != null) 'back': back, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (isDeleted != null) 'is_deleted': isDeleted, + }); + } + + GameCardsCompanion copyWith( + {Value? id, + Value? packId, + Value? original, + Value? translation, + Value? mnemo, + Value? image, + Value? imageBack, + Value? transcription, + Value? transcriptionMnemo, + Value? back, + Value? createdAt, + Value? updatedAt, + Value? isDeleted}) { + return GameCardsCompanion( + id: id ?? this.id, + packId: packId ?? this.packId, + original: original ?? this.original, + translation: translation ?? this.translation, + mnemo: mnemo ?? this.mnemo, + image: image ?? this.image, + imageBack: imageBack ?? this.imageBack, + transcription: transcription ?? this.transcription, + transcriptionMnemo: transcriptionMnemo ?? this.transcriptionMnemo, + back: back ?? this.back, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (packId.present) { + map['pack_id'] = Variable(packId.value); + } + if (original.present) { + map['original'] = Variable(original.value); + } + if (translation.present) { + map['translation'] = Variable(translation.value); + } + if (mnemo.present) { + map['mnemo'] = Variable(mnemo.value); + } + if (image.present) { + map['image'] = Variable(image.value); + } + if (imageBack.present) { + map['image_back'] = Variable(imageBack.value); + } + if (transcription.present) { + map['transcription'] = Variable(transcription.value); + } + if (transcriptionMnemo.present) { + map['transcription_mnemo'] = Variable(transcriptionMnemo.value); + } + if (back.present) { + map['back'] = Variable(back.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (isDeleted.present) { + map['is_deleted'] = Variable(isDeleted.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('GameCardsCompanion(') + ..write('id: $id, ') + ..write('packId: $packId, ') + ..write('original: $original, ') + ..write('translation: $translation, ') + ..write('mnemo: $mnemo, ') + ..write('image: $image, ') + ..write('imageBack: $imageBack, ') + ..write('transcription: $transcription, ') + ..write('transcriptionMnemo: $transcriptionMnemo, ') + ..write('back: $back, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } +} + +class $VoiceModelsTable extends VoiceModels + with TableInfo<$VoiceModelsTable, VoiceModel> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $VoiceModelsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _cardIdMeta = const VerificationMeta('cardId'); + @override + late final GeneratedColumn cardId = GeneratedColumn( + 'card_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES game_cards (id) ON DELETE CASCADE')); + static const VerificationMeta _voiceUrlMeta = + const VerificationMeta('voiceUrl'); + @override + late final GeneratedColumn voiceUrl = GeneratedColumn( + 'voice_url', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _languageMeta = + const VerificationMeta('language'); + @override + late final GeneratedColumn language = GeneratedColumn( + 'language', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => + [id, cardId, voiceUrl, language, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'voice_models'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('card_id')) { + context.handle(_cardIdMeta, + cardId.isAcceptableOrUnknown(data['card_id']!, _cardIdMeta)); + } else if (isInserting) { + context.missing(_cardIdMeta); + } + if (data.containsKey('voice_url')) { + context.handle(_voiceUrlMeta, + voiceUrl.isAcceptableOrUnknown(data['voice_url']!, _voiceUrlMeta)); + } else if (isInserting) { + context.missing(_voiceUrlMeta); + } + if (data.containsKey('language')) { + context.handle(_languageMeta, + language.isAcceptableOrUnknown(data['language']!, _languageMeta)); + } else if (isInserting) { + context.missing(_languageMeta); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + VoiceModel map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return VoiceModel( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + cardId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}card_id'])!, + voiceUrl: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}voice_url'])!, + language: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}language'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + $VoiceModelsTable createAlias(String alias) { + return $VoiceModelsTable(attachedDatabase, alias); + } +} + +class VoiceModel extends DataClass implements Insertable { + final int id; + final int cardId; + final String voiceUrl; + final String language; + final DateTime createdAt; + const VoiceModel( + {required this.id, + required this.cardId, + required this.voiceUrl, + required this.language, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['card_id'] = Variable(cardId); + map['voice_url'] = Variable(voiceUrl); + map['language'] = Variable(language); + map['created_at'] = Variable(createdAt); + return map; + } + + VoiceModelsCompanion toCompanion(bool nullToAbsent) { + return VoiceModelsCompanion( + id: Value(id), + cardId: Value(cardId), + voiceUrl: Value(voiceUrl), + language: Value(language), + createdAt: Value(createdAt), + ); + } + + factory VoiceModel.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return VoiceModel( + id: serializer.fromJson(json['id']), + cardId: serializer.fromJson(json['cardId']), + voiceUrl: serializer.fromJson(json['voiceUrl']), + language: serializer.fromJson(json['language']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'cardId': serializer.toJson(cardId), + 'voiceUrl': serializer.toJson(voiceUrl), + 'language': serializer.toJson(language), + 'createdAt': serializer.toJson(createdAt), + }; + } + + VoiceModel copyWith( + {int? id, + int? cardId, + String? voiceUrl, + String? language, + DateTime? createdAt}) => + VoiceModel( + id: id ?? this.id, + cardId: cardId ?? this.cardId, + voiceUrl: voiceUrl ?? this.voiceUrl, + language: language ?? this.language, + createdAt: createdAt ?? this.createdAt, + ); + @override + String toString() { + return (StringBuffer('VoiceModel(') + ..write('id: $id, ') + ..write('cardId: $cardId, ') + ..write('voiceUrl: $voiceUrl, ') + ..write('language: $language, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, cardId, voiceUrl, language, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is VoiceModel && + other.id == this.id && + other.cardId == this.cardId && + other.voiceUrl == this.voiceUrl && + other.language == this.language && + other.createdAt == this.createdAt); +} + +class VoiceModelsCompanion extends UpdateCompanion { + final Value id; + final Value cardId; + final Value voiceUrl; + final Value language; + final Value createdAt; + const VoiceModelsCompanion({ + this.id = const Value.absent(), + this.cardId = const Value.absent(), + this.voiceUrl = const Value.absent(), + this.language = const Value.absent(), + this.createdAt = const Value.absent(), + }); + VoiceModelsCompanion.insert({ + this.id = const Value.absent(), + required int cardId, + required String voiceUrl, + required String language, + this.createdAt = const Value.absent(), + }) : cardId = Value(cardId), + voiceUrl = Value(voiceUrl), + language = Value(language); + static Insertable custom({ + Expression? id, + Expression? cardId, + Expression? voiceUrl, + Expression? language, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (cardId != null) 'card_id': cardId, + if (voiceUrl != null) 'voice_url': voiceUrl, + if (language != null) 'language': language, + if (createdAt != null) 'created_at': createdAt, + }); + } + + VoiceModelsCompanion copyWith( + {Value? id, + Value? cardId, + Value? voiceUrl, + Value? language, + Value? createdAt}) { + return VoiceModelsCompanion( + id: id ?? this.id, + cardId: cardId ?? this.cardId, + voiceUrl: voiceUrl ?? this.voiceUrl, + language: language ?? this.language, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (cardId.present) { + map['card_id'] = Variable(cardId.value); + } + if (voiceUrl.present) { + map['voice_url'] = Variable(voiceUrl.value); + } + if (language.present) { + map['language'] = Variable(language.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('VoiceModelsCompanion(') + ..write('id: $id, ') + ..write('cardId: $cardId, ') + ..write('voiceUrl: $voiceUrl, ') + ..write('language: $language, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class $UserPacksTable extends UserPacks + with TableInfo<$UserPacksTable, UserPack> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserPacksTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES users (id) ON DELETE CASCADE')); + static const VerificationMeta _packIdMeta = const VerificationMeta('packId'); + @override + late final GeneratedColumn packId = GeneratedColumn( + 'pack_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES card_packs (id) ON DELETE CASCADE')); + static const VerificationMeta _grantedAtMeta = + const VerificationMeta('grantedAt'); + @override + late final GeneratedColumn grantedAt = GeneratedColumn( + 'granted_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _grantTypeMeta = + const VerificationMeta('grantType'); + @override + late final GeneratedColumn grantType = GeneratedColumn( + 'grant_type', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('purchase')); + @override + List get $columns => [userId, packId, grantedAt, grantType]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_packs'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('pack_id')) { + context.handle(_packIdMeta, + packId.isAcceptableOrUnknown(data['pack_id']!, _packIdMeta)); + } else if (isInserting) { + context.missing(_packIdMeta); + } + if (data.containsKey('granted_at')) { + context.handle(_grantedAtMeta, + grantedAt.isAcceptableOrUnknown(data['granted_at']!, _grantedAtMeta)); + } + if (data.containsKey('grant_type')) { + context.handle(_grantTypeMeta, + grantType.isAcceptableOrUnknown(data['grant_type']!, _grantTypeMeta)); + } + return context; + } + + @override + Set get $primaryKey => {userId, packId}; + @override + UserPack map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserPack( + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + packId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}pack_id'])!, + grantedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}granted_at'])!, + grantType: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}grant_type'])!, + ); + } + + @override + $UserPacksTable createAlias(String alias) { + return $UserPacksTable(attachedDatabase, alias); + } +} + +class UserPack extends DataClass implements Insertable { + final int userId; + final int packId; + final DateTime grantedAt; + final String grantType; + const UserPack( + {required this.userId, + required this.packId, + required this.grantedAt, + required this.grantType}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['user_id'] = Variable(userId); + map['pack_id'] = Variable(packId); + map['granted_at'] = Variable(grantedAt); + map['grant_type'] = Variable(grantType); + return map; + } + + UserPacksCompanion toCompanion(bool nullToAbsent) { + return UserPacksCompanion( + userId: Value(userId), + packId: Value(packId), + grantedAt: Value(grantedAt), + grantType: Value(grantType), + ); + } + + factory UserPack.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserPack( + userId: serializer.fromJson(json['userId']), + packId: serializer.fromJson(json['packId']), + grantedAt: serializer.fromJson(json['grantedAt']), + grantType: serializer.fromJson(json['grantType']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'userId': serializer.toJson(userId), + 'packId': serializer.toJson(packId), + 'grantedAt': serializer.toJson(grantedAt), + 'grantType': serializer.toJson(grantType), + }; + } + + UserPack copyWith( + {int? userId, int? packId, DateTime? grantedAt, String? grantType}) => + UserPack( + userId: userId ?? this.userId, + packId: packId ?? this.packId, + grantedAt: grantedAt ?? this.grantedAt, + grantType: grantType ?? this.grantType, + ); + @override + String toString() { + return (StringBuffer('UserPack(') + ..write('userId: $userId, ') + ..write('packId: $packId, ') + ..write('grantedAt: $grantedAt, ') + ..write('grantType: $grantType') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(userId, packId, grantedAt, grantType); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserPack && + other.userId == this.userId && + other.packId == this.packId && + other.grantedAt == this.grantedAt && + other.grantType == this.grantType); +} + +class UserPacksCompanion extends UpdateCompanion { + final Value userId; + final Value packId; + final Value grantedAt; + final Value grantType; + final Value rowid; + const UserPacksCompanion({ + this.userId = const Value.absent(), + this.packId = const Value.absent(), + this.grantedAt = const Value.absent(), + this.grantType = const Value.absent(), + this.rowid = const Value.absent(), + }); + UserPacksCompanion.insert({ + required int userId, + required int packId, + this.grantedAt = const Value.absent(), + this.grantType = const Value.absent(), + this.rowid = const Value.absent(), + }) : userId = Value(userId), + packId = Value(packId); + static Insertable custom({ + Expression? userId, + Expression? packId, + Expression? grantedAt, + Expression? grantType, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (userId != null) 'user_id': userId, + if (packId != null) 'pack_id': packId, + if (grantedAt != null) 'granted_at': grantedAt, + if (grantType != null) 'grant_type': grantType, + if (rowid != null) 'rowid': rowid, + }); + } + + UserPacksCompanion copyWith( + {Value? userId, + Value? packId, + Value? grantedAt, + Value? grantType, + Value? rowid}) { + return UserPacksCompanion( + userId: userId ?? this.userId, + packId: packId ?? this.packId, + grantedAt: grantedAt ?? this.grantedAt, + grantType: grantType ?? this.grantType, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (packId.present) { + map['pack_id'] = Variable(packId.value); + } + if (grantedAt.present) { + map['granted_at'] = Variable(grantedAt.value); + } + if (grantType.present) { + map['grant_type'] = Variable(grantType.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserPacksCompanion(') + ..write('userId: $userId, ') + ..write('packId: $packId, ') + ..write('grantedAt: $grantedAt, ') + ..write('grantType: $grantType, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $PreviewCardsTable extends PreviewCards + with TableInfo<$PreviewCardsTable, PreviewCard> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $PreviewCardsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _packIdMeta = const VerificationMeta('packId'); + @override + late final GeneratedColumn packId = GeneratedColumn( + 'pack_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES card_packs (id) ON DELETE CASCADE')); + static const VerificationMeta _cardIdMeta = const VerificationMeta('cardId'); + @override + late final GeneratedColumn cardId = GeneratedColumn( + 'card_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES game_cards (id) ON DELETE CASCADE')); + static const VerificationMeta _orderMeta = const VerificationMeta('order'); + @override + late final GeneratedColumn order = GeneratedColumn( + 'order', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + @override + List get $columns => [packId, cardId, order]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'preview_cards'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('pack_id')) { + context.handle(_packIdMeta, + packId.isAcceptableOrUnknown(data['pack_id']!, _packIdMeta)); + } else if (isInserting) { + context.missing(_packIdMeta); + } + if (data.containsKey('card_id')) { + context.handle(_cardIdMeta, + cardId.isAcceptableOrUnknown(data['card_id']!, _cardIdMeta)); + } else if (isInserting) { + context.missing(_cardIdMeta); + } + if (data.containsKey('order')) { + context.handle( + _orderMeta, order.isAcceptableOrUnknown(data['order']!, _orderMeta)); + } + return context; + } + + @override + Set get $primaryKey => {packId, cardId}; + @override + PreviewCard map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PreviewCard( + packId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}pack_id'])!, + cardId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}card_id'])!, + order: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}order'])!, + ); + } + + @override + $PreviewCardsTable createAlias(String alias) { + return $PreviewCardsTable(attachedDatabase, alias); + } +} + +class PreviewCard extends DataClass implements Insertable { + final int packId; + final int cardId; + final int order; + const PreviewCard( + {required this.packId, required this.cardId, required this.order}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['pack_id'] = Variable(packId); + map['card_id'] = Variable(cardId); + map['order'] = Variable(order); + return map; + } + + PreviewCardsCompanion toCompanion(bool nullToAbsent) { + return PreviewCardsCompanion( + packId: Value(packId), + cardId: Value(cardId), + order: Value(order), + ); + } + + factory PreviewCard.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PreviewCard( + packId: serializer.fromJson(json['packId']), + cardId: serializer.fromJson(json['cardId']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'packId': serializer.toJson(packId), + 'cardId': serializer.toJson(cardId), + 'order': serializer.toJson(order), + }; + } + + PreviewCard copyWith({int? packId, int? cardId, int? order}) => PreviewCard( + packId: packId ?? this.packId, + cardId: cardId ?? this.cardId, + order: order ?? this.order, + ); + @override + String toString() { + return (StringBuffer('PreviewCard(') + ..write('packId: $packId, ') + ..write('cardId: $cardId, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(packId, cardId, order); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PreviewCard && + other.packId == this.packId && + other.cardId == this.cardId && + other.order == this.order); +} + +class PreviewCardsCompanion extends UpdateCompanion { + final Value packId; + final Value cardId; + final Value order; + final Value rowid; + const PreviewCardsCompanion({ + this.packId = const Value.absent(), + this.cardId = const Value.absent(), + this.order = const Value.absent(), + this.rowid = const Value.absent(), + }); + PreviewCardsCompanion.insert({ + required int packId, + required int cardId, + this.order = const Value.absent(), + this.rowid = const Value.absent(), + }) : packId = Value(packId), + cardId = Value(cardId); + static Insertable custom({ + Expression? packId, + Expression? cardId, + Expression? order, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (packId != null) 'pack_id': packId, + if (cardId != null) 'card_id': cardId, + if (order != null) 'order': order, + if (rowid != null) 'rowid': rowid, + }); + } + + PreviewCardsCompanion copyWith( + {Value? packId, + Value? cardId, + Value? order, + Value? rowid}) { + return PreviewCardsCompanion( + packId: packId ?? this.packId, + cardId: cardId ?? this.cardId, + order: order ?? this.order, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (packId.present) { + map['pack_id'] = Variable(packId.value); + } + if (cardId.present) { + map['card_id'] = Variable(cardId.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PreviewCardsCompanion(') + ..write('packId: $packId, ') + ..write('cardId: $cardId, ') + ..write('order: $order, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $CardPackCardsTable extends CardPackCards + with TableInfo<$CardPackCardsTable, CardPackCard> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $CardPackCardsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _packIdMeta = const VerificationMeta('packId'); + @override + late final GeneratedColumn packId = GeneratedColumn( + 'pack_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES card_packs (id) ON DELETE CASCADE')); + static const VerificationMeta _cardIdMeta = const VerificationMeta('cardId'); + @override + late final GeneratedColumn cardId = GeneratedColumn( + 'card_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES game_cards (id) ON DELETE CASCADE')); + static const VerificationMeta _orderMeta = const VerificationMeta('order'); + @override + late final GeneratedColumn order = GeneratedColumn( + 'order', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + @override + List get $columns => [packId, cardId, order]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'card_pack_cards'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('pack_id')) { + context.handle(_packIdMeta, + packId.isAcceptableOrUnknown(data['pack_id']!, _packIdMeta)); + } else if (isInserting) { + context.missing(_packIdMeta); + } + if (data.containsKey('card_id')) { + context.handle(_cardIdMeta, + cardId.isAcceptableOrUnknown(data['card_id']!, _cardIdMeta)); + } else if (isInserting) { + context.missing(_cardIdMeta); + } + if (data.containsKey('order')) { + context.handle( + _orderMeta, order.isAcceptableOrUnknown(data['order']!, _orderMeta)); + } + return context; + } + + @override + Set get $primaryKey => {packId, cardId}; + @override + CardPackCard map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return CardPackCard( + packId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}pack_id'])!, + cardId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}card_id'])!, + order: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}order'])!, + ); + } + + @override + $CardPackCardsTable createAlias(String alias) { + return $CardPackCardsTable(attachedDatabase, alias); + } +} + +class CardPackCard extends DataClass implements Insertable { + final int packId; + final int cardId; + final int order; + const CardPackCard( + {required this.packId, required this.cardId, required this.order}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['pack_id'] = Variable(packId); + map['card_id'] = Variable(cardId); + map['order'] = Variable(order); + return map; + } + + CardPackCardsCompanion toCompanion(bool nullToAbsent) { + return CardPackCardsCompanion( + packId: Value(packId), + cardId: Value(cardId), + order: Value(order), + ); + } + + factory CardPackCard.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return CardPackCard( + packId: serializer.fromJson(json['packId']), + cardId: serializer.fromJson(json['cardId']), + order: serializer.fromJson(json['order']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'packId': serializer.toJson(packId), + 'cardId': serializer.toJson(cardId), + 'order': serializer.toJson(order), + }; + } + + CardPackCard copyWith({int? packId, int? cardId, int? order}) => CardPackCard( + packId: packId ?? this.packId, + cardId: cardId ?? this.cardId, + order: order ?? this.order, + ); + @override + String toString() { + return (StringBuffer('CardPackCard(') + ..write('packId: $packId, ') + ..write('cardId: $cardId, ') + ..write('order: $order') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(packId, cardId, order); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is CardPackCard && + other.packId == this.packId && + other.cardId == this.cardId && + other.order == this.order); +} + +class CardPackCardsCompanion extends UpdateCompanion { + final Value packId; + final Value cardId; + final Value order; + final Value rowid; + const CardPackCardsCompanion({ + this.packId = const Value.absent(), + this.cardId = const Value.absent(), + this.order = const Value.absent(), + this.rowid = const Value.absent(), + }); + CardPackCardsCompanion.insert({ + required int packId, + required int cardId, + this.order = const Value.absent(), + this.rowid = const Value.absent(), + }) : packId = Value(packId), + cardId = Value(cardId); + static Insertable custom({ + Expression? packId, + Expression? cardId, + Expression? order, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (packId != null) 'pack_id': packId, + if (cardId != null) 'card_id': cardId, + if (order != null) 'order': order, + if (rowid != null) 'rowid': rowid, + }); + } + + CardPackCardsCompanion copyWith( + {Value? packId, + Value? cardId, + Value? order, + Value? rowid}) { + return CardPackCardsCompanion( + packId: packId ?? this.packId, + cardId: cardId ?? this.cardId, + order: order ?? this.order, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (packId.present) { + map['pack_id'] = Variable(packId.value); + } + if (cardId.present) { + map['card_id'] = Variable(cardId.value); + } + if (order.present) { + map['order'] = Variable(order.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CardPackCardsCompanion(') + ..write('packId: $packId, ') + ..write('cardId: $cardId, ') + ..write('order: $order, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $CardVoicesTable extends CardVoices + with TableInfo<$CardVoicesTable, CardVoice> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $CardVoicesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _cardIdMeta = const VerificationMeta('cardId'); + @override + late final GeneratedColumn cardId = GeneratedColumn( + 'card_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES game_cards (id) ON DELETE CASCADE')); + static const VerificationMeta _voiceIdMeta = + const VerificationMeta('voiceId'); + @override + late final GeneratedColumn voiceId = GeneratedColumn( + 'voice_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES voice_models (id) ON DELETE CASCADE')); + @override + List get $columns => [cardId, voiceId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'card_voices'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('card_id')) { + context.handle(_cardIdMeta, + cardId.isAcceptableOrUnknown(data['card_id']!, _cardIdMeta)); + } else if (isInserting) { + context.missing(_cardIdMeta); + } + if (data.containsKey('voice_id')) { + context.handle(_voiceIdMeta, + voiceId.isAcceptableOrUnknown(data['voice_id']!, _voiceIdMeta)); + } else if (isInserting) { + context.missing(_voiceIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {cardId, voiceId}; + @override + CardVoice map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return CardVoice( + cardId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}card_id'])!, + voiceId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}voice_id'])!, + ); + } + + @override + $CardVoicesTable createAlias(String alias) { + return $CardVoicesTable(attachedDatabase, alias); + } +} + +class CardVoice extends DataClass implements Insertable { + final int cardId; + final int voiceId; + const CardVoice({required this.cardId, required this.voiceId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['card_id'] = Variable(cardId); + map['voice_id'] = Variable(voiceId); + return map; + } + + CardVoicesCompanion toCompanion(bool nullToAbsent) { + return CardVoicesCompanion( + cardId: Value(cardId), + voiceId: Value(voiceId), + ); + } + + factory CardVoice.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return CardVoice( + cardId: serializer.fromJson(json['cardId']), + voiceId: serializer.fromJson(json['voiceId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'cardId': serializer.toJson(cardId), + 'voiceId': serializer.toJson(voiceId), + }; + } + + CardVoice copyWith({int? cardId, int? voiceId}) => CardVoice( + cardId: cardId ?? this.cardId, + voiceId: voiceId ?? this.voiceId, + ); + @override + String toString() { + return (StringBuffer('CardVoice(') + ..write('cardId: $cardId, ') + ..write('voiceId: $voiceId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(cardId, voiceId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is CardVoice && + other.cardId == this.cardId && + other.voiceId == this.voiceId); +} + +class CardVoicesCompanion extends UpdateCompanion { + final Value cardId; + final Value voiceId; + final Value rowid; + const CardVoicesCompanion({ + this.cardId = const Value.absent(), + this.voiceId = const Value.absent(), + this.rowid = const Value.absent(), + }); + CardVoicesCompanion.insert({ + required int cardId, + required int voiceId, + this.rowid = const Value.absent(), + }) : cardId = Value(cardId), + voiceId = Value(voiceId); + static Insertable custom({ + Expression? cardId, + Expression? voiceId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (cardId != null) 'card_id': cardId, + if (voiceId != null) 'voice_id': voiceId, + if (rowid != null) 'rowid': rowid, + }); + } + + CardVoicesCompanion copyWith( + {Value? cardId, Value? voiceId, Value? rowid}) { + return CardVoicesCompanion( + cardId: cardId ?? this.cardId, + voiceId: voiceId ?? this.voiceId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (cardId.present) { + map['card_id'] = Variable(cardId.value); + } + if (voiceId.present) { + map['voice_id'] = Variable(voiceId.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('CardVoicesCompanion(') + ..write('cardId: $cardId, ') + ..write('voiceId: $voiceId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $SubscriptionPlansTable extends SubscriptionPlans + with TableInfo<$SubscriptionPlansTable, SubscriptionPlan> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $SubscriptionPlansTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _uiMeta = const VerificationMeta('ui'); + @override + late final GeneratedColumnWithTypeConverter?, String> + ui = GeneratedColumn('ui', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false) + .withConverter?>( + $SubscriptionPlansTable.$converterui); + static const VerificationMeta _priceMeta = const VerificationMeta('price'); + @override + late final GeneratedColumn price = GeneratedColumn( + 'price', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _currencyMeta = + const VerificationMeta('currency'); + @override + late final GeneratedColumn currency = GeneratedColumn( + 'currency', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _durationDaysMeta = + const VerificationMeta('durationDays'); + @override + late final GeneratedColumn durationDays = GeneratedColumn( + 'duration_days', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _featuresMeta = + const VerificationMeta('features'); + @override + late final GeneratedColumnWithTypeConverter?, String> features = + GeneratedColumn('features', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter?>( + $SubscriptionPlansTable.$converterfeatures); + static const VerificationMeta _paymentIdMeta = + const VerificationMeta('paymentId'); + @override + late final GeneratedColumn paymentId = GeneratedColumn( + 'payment_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _paymentSystemMeta = + const VerificationMeta('paymentSystem'); + @override + late final GeneratedColumn paymentSystem = GeneratedColumn( + 'payment_system', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _isDeletedMeta = + const VerificationMeta('isDeleted'); + @override + late final GeneratedColumn isDeleted = GeneratedColumn( + 'is_deleted', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_deleted" IN (0, 1))'), + defaultValue: const Constant(false)); + @override + List get $columns => [ + id, + ui, + price, + currency, + durationDays, + features, + paymentId, + paymentSystem, + createdAt, + updatedAt, + isDeleted + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'subscription_plans'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + context.handle(_uiMeta, const VerificationResult.success()); + if (data.containsKey('price')) { + context.handle( + _priceMeta, price.isAcceptableOrUnknown(data['price']!, _priceMeta)); + } else if (isInserting) { + context.missing(_priceMeta); + } + if (data.containsKey('currency')) { + context.handle(_currencyMeta, + currency.isAcceptableOrUnknown(data['currency']!, _currencyMeta)); + } else if (isInserting) { + context.missing(_currencyMeta); + } + if (data.containsKey('duration_days')) { + context.handle( + _durationDaysMeta, + durationDays.isAcceptableOrUnknown( + data['duration_days']!, _durationDaysMeta)); + } else if (isInserting) { + context.missing(_durationDaysMeta); + } + context.handle(_featuresMeta, const VerificationResult.success()); + if (data.containsKey('payment_id')) { + context.handle(_paymentIdMeta, + paymentId.isAcceptableOrUnknown(data['payment_id']!, _paymentIdMeta)); + } + if (data.containsKey('payment_system')) { + context.handle( + _paymentSystemMeta, + paymentSystem.isAcceptableOrUnknown( + data['payment_system']!, _paymentSystemMeta)); + } else if (isInserting) { + context.missing(_paymentSystemMeta); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + if (data.containsKey('is_deleted')) { + context.handle(_isDeletedMeta, + isDeleted.isAcceptableOrUnknown(data['is_deleted']!, _isDeletedMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + SubscriptionPlan map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SubscriptionPlan( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + ui: $SubscriptionPlansTable.$converterui.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}ui'])), + price: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}price'])!, + currency: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}currency'])!, + durationDays: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}duration_days'])!, + features: $SubscriptionPlansTable.$converterfeatures.fromSql( + attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}features'])!), + paymentId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}payment_id']), + paymentSystem: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}payment_system'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + isDeleted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_deleted'])!, + ); + } + + @override + $SubscriptionPlansTable createAlias(String alias) { + return $SubscriptionPlansTable(attachedDatabase, alias); + } + + static TypeConverter?, String?> $converterui = + NullAwareTypeConverter.wrap(const JsonMapConverter()); + static TypeConverter?, String> $converterfeatures = + const JsonListConverter(); +} + +class SubscriptionPlan extends DataClass + implements Insertable { + final int id; + final Map? ui; + final String price; + final String currency; + final int durationDays; + final List? features; + final String? paymentId; + final String paymentSystem; + final DateTime createdAt; + final DateTime updatedAt; + final bool isDeleted; + const SubscriptionPlan( + {required this.id, + this.ui, + required this.price, + required this.currency, + required this.durationDays, + this.features, + this.paymentId, + required this.paymentSystem, + required this.createdAt, + required this.updatedAt, + required this.isDeleted}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || ui != null) { + map['ui'] = + Variable($SubscriptionPlansTable.$converterui.toSql(ui)); + } + map['price'] = Variable(price); + map['currency'] = Variable(currency); + map['duration_days'] = Variable(durationDays); + if (!nullToAbsent || features != null) { + map['features'] = Variable( + $SubscriptionPlansTable.$converterfeatures.toSql(features)); + } + if (!nullToAbsent || paymentId != null) { + map['payment_id'] = Variable(paymentId); + } + map['payment_system'] = Variable(paymentSystem); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['is_deleted'] = Variable(isDeleted); + return map; + } + + SubscriptionPlansCompanion toCompanion(bool nullToAbsent) { + return SubscriptionPlansCompanion( + id: Value(id), + ui: ui == null && nullToAbsent ? const Value.absent() : Value(ui), + price: Value(price), + currency: Value(currency), + durationDays: Value(durationDays), + features: features == null && nullToAbsent + ? const Value.absent() + : Value(features), + paymentId: paymentId == null && nullToAbsent + ? const Value.absent() + : Value(paymentId), + paymentSystem: Value(paymentSystem), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + isDeleted: Value(isDeleted), + ); + } + + factory SubscriptionPlan.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SubscriptionPlan( + id: serializer.fromJson(json['id']), + ui: serializer.fromJson?>(json['ui']), + price: serializer.fromJson(json['price']), + currency: serializer.fromJson(json['currency']), + durationDays: serializer.fromJson(json['durationDays']), + features: serializer.fromJson?>(json['features']), + paymentId: serializer.fromJson(json['paymentId']), + paymentSystem: serializer.fromJson(json['paymentSystem']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + isDeleted: serializer.fromJson(json['isDeleted']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'ui': serializer.toJson?>(ui), + 'price': serializer.toJson(price), + 'currency': serializer.toJson(currency), + 'durationDays': serializer.toJson(durationDays), + 'features': serializer.toJson?>(features), + 'paymentId': serializer.toJson(paymentId), + 'paymentSystem': serializer.toJson(paymentSystem), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'isDeleted': serializer.toJson(isDeleted), + }; + } + + SubscriptionPlan copyWith( + {int? id, + Value?> ui = const Value.absent(), + String? price, + String? currency, + int? durationDays, + Value?> features = const Value.absent(), + Value paymentId = const Value.absent(), + String? paymentSystem, + DateTime? createdAt, + DateTime? updatedAt, + bool? isDeleted}) => + SubscriptionPlan( + id: id ?? this.id, + ui: ui.present ? ui.value : this.ui, + price: price ?? this.price, + currency: currency ?? this.currency, + durationDays: durationDays ?? this.durationDays, + features: features.present ? features.value : this.features, + paymentId: paymentId.present ? paymentId.value : this.paymentId, + paymentSystem: paymentSystem ?? this.paymentSystem, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + @override + String toString() { + return (StringBuffer('SubscriptionPlan(') + ..write('id: $id, ') + ..write('ui: $ui, ') + ..write('price: $price, ') + ..write('currency: $currency, ') + ..write('durationDays: $durationDays, ') + ..write('features: $features, ') + ..write('paymentId: $paymentId, ') + ..write('paymentSystem: $paymentSystem, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, ui, price, currency, durationDays, + features, paymentId, paymentSystem, createdAt, updatedAt, isDeleted); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SubscriptionPlan && + other.id == this.id && + other.ui == this.ui && + other.price == this.price && + other.currency == this.currency && + other.durationDays == this.durationDays && + other.features == this.features && + other.paymentId == this.paymentId && + other.paymentSystem == this.paymentSystem && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.isDeleted == this.isDeleted); +} + +class SubscriptionPlansCompanion extends UpdateCompanion { + final Value id; + final Value?> ui; + final Value price; + final Value currency; + final Value durationDays; + final Value?> features; + final Value paymentId; + final Value paymentSystem; + final Value createdAt; + final Value updatedAt; + final Value isDeleted; + const SubscriptionPlansCompanion({ + this.id = const Value.absent(), + this.ui = const Value.absent(), + this.price = const Value.absent(), + this.currency = const Value.absent(), + this.durationDays = const Value.absent(), + this.features = const Value.absent(), + this.paymentId = const Value.absent(), + this.paymentSystem = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }); + SubscriptionPlansCompanion.insert({ + this.id = const Value.absent(), + this.ui = const Value.absent(), + required String price, + required String currency, + required int durationDays, + this.features = const Value.absent(), + this.paymentId = const Value.absent(), + required String paymentSystem, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }) : price = Value(price), + currency = Value(currency), + durationDays = Value(durationDays), + paymentSystem = Value(paymentSystem); + static Insertable custom({ + Expression? id, + Expression? ui, + Expression? price, + Expression? currency, + Expression? durationDays, + Expression? features, + Expression? paymentId, + Expression? paymentSystem, + Expression? createdAt, + Expression? updatedAt, + Expression? isDeleted, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (ui != null) 'ui': ui, + if (price != null) 'price': price, + if (currency != null) 'currency': currency, + if (durationDays != null) 'duration_days': durationDays, + if (features != null) 'features': features, + if (paymentId != null) 'payment_id': paymentId, + if (paymentSystem != null) 'payment_system': paymentSystem, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (isDeleted != null) 'is_deleted': isDeleted, + }); + } + + SubscriptionPlansCompanion copyWith( + {Value? id, + Value?>? ui, + Value? price, + Value? currency, + Value? durationDays, + Value?>? features, + Value? paymentId, + Value? paymentSystem, + Value? createdAt, + Value? updatedAt, + Value? isDeleted}) { + return SubscriptionPlansCompanion( + id: id ?? this.id, + ui: ui ?? this.ui, + price: price ?? this.price, + currency: currency ?? this.currency, + durationDays: durationDays ?? this.durationDays, + features: features ?? this.features, + paymentId: paymentId ?? this.paymentId, + paymentSystem: paymentSystem ?? this.paymentSystem, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (ui.present) { + map['ui'] = Variable( + $SubscriptionPlansTable.$converterui.toSql(ui.value)); + } + if (price.present) { + map['price'] = Variable(price.value); + } + if (currency.present) { + map['currency'] = Variable(currency.value); + } + if (durationDays.present) { + map['duration_days'] = Variable(durationDays.value); + } + if (features.present) { + map['features'] = Variable( + $SubscriptionPlansTable.$converterfeatures.toSql(features.value)); + } + if (paymentId.present) { + map['payment_id'] = Variable(paymentId.value); + } + if (paymentSystem.present) { + map['payment_system'] = Variable(paymentSystem.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (isDeleted.present) { + map['is_deleted'] = Variable(isDeleted.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SubscriptionPlansCompanion(') + ..write('id: $id, ') + ..write('ui: $ui, ') + ..write('price: $price, ') + ..write('currency: $currency, ') + ..write('durationDays: $durationDays, ') + ..write('features: $features, ') + ..write('paymentId: $paymentId, ') + ..write('paymentSystem: $paymentSystem, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } +} + +class $UserSubscriptionsTable extends UserSubscriptions + with TableInfo<$UserSubscriptionsTable, UserSubscription> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserSubscriptionsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'UNIQUE REFERENCES users (id) ON DELETE CASCADE')); + static const VerificationMeta _startMeta = const VerificationMeta('start'); + @override + late final GeneratedColumn start = GeneratedColumn( + 'start', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _finishMeta = const VerificationMeta('finish'); + @override + late final GeneratedColumn finish = GeneratedColumn( + 'finish', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _featuresMeta = + const VerificationMeta('features'); + @override + late final GeneratedColumnWithTypeConverter?, String> features = + GeneratedColumn('features', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter?>( + $UserSubscriptionsTable.$converterfeatures); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => + [id, userId, start, finish, features, createdAt, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_subscriptions'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('start')) { + context.handle( + _startMeta, start.isAcceptableOrUnknown(data['start']!, _startMeta)); + } else if (isInserting) { + context.missing(_startMeta); + } + if (data.containsKey('finish')) { + context.handle(_finishMeta, + finish.isAcceptableOrUnknown(data['finish']!, _finishMeta)); + } else if (isInserting) { + context.missing(_finishMeta); + } + context.handle(_featuresMeta, const VerificationResult.success()); + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + UserSubscription map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserSubscription( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + start: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}start'])!, + finish: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}finish'])!, + features: $UserSubscriptionsTable.$converterfeatures.fromSql( + attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}features'])!), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $UserSubscriptionsTable createAlias(String alias) { + return $UserSubscriptionsTable(attachedDatabase, alias); + } + + static TypeConverter?, String> $converterfeatures = + const JsonListConverter(); +} + +class UserSubscription extends DataClass + implements Insertable { + final int id; + final int userId; + final DateTime start; + final DateTime finish; + final List? features; + final DateTime createdAt; + final DateTime updatedAt; + const UserSubscription( + {required this.id, + required this.userId, + required this.start, + required this.finish, + this.features, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['user_id'] = Variable(userId); + map['start'] = Variable(start); + map['finish'] = Variable(finish); + if (!nullToAbsent || features != null) { + map['features'] = Variable( + $UserSubscriptionsTable.$converterfeatures.toSql(features)); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + UserSubscriptionsCompanion toCompanion(bool nullToAbsent) { + return UserSubscriptionsCompanion( + id: Value(id), + userId: Value(userId), + start: Value(start), + finish: Value(finish), + features: features == null && nullToAbsent + ? const Value.absent() + : Value(features), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory UserSubscription.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserSubscription( + id: serializer.fromJson(json['id']), + userId: serializer.fromJson(json['userId']), + start: serializer.fromJson(json['start']), + finish: serializer.fromJson(json['finish']), + features: serializer.fromJson?>(json['features']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'userId': serializer.toJson(userId), + 'start': serializer.toJson(start), + 'finish': serializer.toJson(finish), + 'features': serializer.toJson?>(features), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + UserSubscription copyWith( + {int? id, + int? userId, + DateTime? start, + DateTime? finish, + Value?> features = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt}) => + UserSubscription( + id: id ?? this.id, + userId: userId ?? this.userId, + start: start ?? this.start, + finish: finish ?? this.finish, + features: features.present ? features.value : this.features, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + @override + String toString() { + return (StringBuffer('UserSubscription(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('start: $start, ') + ..write('finish: $finish, ') + ..write('features: $features, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, userId, start, finish, features, createdAt, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserSubscription && + other.id == this.id && + other.userId == this.userId && + other.start == this.start && + other.finish == this.finish && + other.features == this.features && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class UserSubscriptionsCompanion extends UpdateCompanion { + final Value id; + final Value userId; + final Value start; + final Value finish; + final Value?> features; + final Value createdAt; + final Value updatedAt; + const UserSubscriptionsCompanion({ + this.id = const Value.absent(), + this.userId = const Value.absent(), + this.start = const Value.absent(), + this.finish = const Value.absent(), + this.features = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + UserSubscriptionsCompanion.insert({ + this.id = const Value.absent(), + required int userId, + required DateTime start, + required DateTime finish, + this.features = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : userId = Value(userId), + start = Value(start), + finish = Value(finish); + static Insertable custom({ + Expression? id, + Expression? userId, + Expression? start, + Expression? finish, + Expression? features, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (userId != null) 'user_id': userId, + if (start != null) 'start': start, + if (finish != null) 'finish': finish, + if (features != null) 'features': features, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + UserSubscriptionsCompanion copyWith( + {Value? id, + Value? userId, + Value? start, + Value? finish, + Value?>? features, + Value? createdAt, + Value? updatedAt}) { + return UserSubscriptionsCompanion( + id: id ?? this.id, + userId: userId ?? this.userId, + start: start ?? this.start, + finish: finish ?? this.finish, + features: features ?? this.features, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (start.present) { + map['start'] = Variable(start.value); + } + if (finish.present) { + map['finish'] = Variable(finish.value); + } + if (features.present) { + map['features'] = Variable( + $UserSubscriptionsTable.$converterfeatures.toSql(features.value)); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserSubscriptionsCompanion(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('start: $start, ') + ..write('finish: $finish, ') + ..write('features: $features, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $PaymentsTable extends Payments with TableInfo<$PaymentsTable, Payment> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $PaymentsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES users (id) ON DELETE CASCADE')); + static const VerificationMeta _amountMeta = const VerificationMeta('amount'); + @override + late final GeneratedColumn amount = GeneratedColumn( + 'amount', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _currencyMeta = + const VerificationMeta('currency'); + @override + late final GeneratedColumn currency = GeneratedColumn( + 'currency', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _statusMeta = const VerificationMeta('status'); + @override + late final GeneratedColumn status = GeneratedColumn( + 'status', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _paymentSystemMeta = + const VerificationMeta('paymentSystem'); + @override + late final GeneratedColumn paymentSystem = GeneratedColumn( + 'payment_system', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _externalTokenMeta = + const VerificationMeta('externalToken'); + @override + late final GeneratedColumn externalToken = GeneratedColumn( + 'external_token', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _metaMeta = const VerificationMeta('meta'); + @override + late final GeneratedColumn meta = GeneratedColumn( + 'meta', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _dateMeta = const VerificationMeta('date'); + @override + late final GeneratedColumn date = GeneratedColumn( + 'date', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _productsMeta = + const VerificationMeta('products'); + @override + late final GeneratedColumnWithTypeConverter?, String> products = + GeneratedColumn('products', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter?>($PaymentsTable.$converterproducts); + static const VerificationMeta _packsMeta = const VerificationMeta('packs'); + @override + late final GeneratedColumnWithTypeConverter, String> packs = + GeneratedColumn('packs', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter>($PaymentsTable.$converterpacks); + static const VerificationMeta _subscriptionMeta = + const VerificationMeta('subscription'); + @override + late final GeneratedColumn subscription = GeneratedColumn( + 'subscription', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("subscription" IN (0, 1))'), + defaultValue: const Constant(false)); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [ + id, + userId, + amount, + currency, + status, + paymentSystem, + externalToken, + meta, + date, + products, + packs, + subscription, + createdAt, + updatedAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'payments'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('amount')) { + context.handle(_amountMeta, + amount.isAcceptableOrUnknown(data['amount']!, _amountMeta)); + } else if (isInserting) { + context.missing(_amountMeta); + } + if (data.containsKey('currency')) { + context.handle(_currencyMeta, + currency.isAcceptableOrUnknown(data['currency']!, _currencyMeta)); + } else if (isInserting) { + context.missing(_currencyMeta); + } + if (data.containsKey('status')) { + context.handle(_statusMeta, + status.isAcceptableOrUnknown(data['status']!, _statusMeta)); + } else if (isInserting) { + context.missing(_statusMeta); + } + if (data.containsKey('payment_system')) { + context.handle( + _paymentSystemMeta, + paymentSystem.isAcceptableOrUnknown( + data['payment_system']!, _paymentSystemMeta)); + } else if (isInserting) { + context.missing(_paymentSystemMeta); + } + if (data.containsKey('external_token')) { + context.handle( + _externalTokenMeta, + externalToken.isAcceptableOrUnknown( + data['external_token']!, _externalTokenMeta)); + } + if (data.containsKey('meta')) { + context.handle( + _metaMeta, meta.isAcceptableOrUnknown(data['meta']!, _metaMeta)); + } + if (data.containsKey('date')) { + context.handle( + _dateMeta, date.isAcceptableOrUnknown(data['date']!, _dateMeta)); + } + context.handle(_productsMeta, const VerificationResult.success()); + context.handle(_packsMeta, const VerificationResult.success()); + if (data.containsKey('subscription')) { + context.handle( + _subscriptionMeta, + subscription.isAcceptableOrUnknown( + data['subscription']!, _subscriptionMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Payment map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Payment( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + amount: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}amount'])!, + currency: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}currency'])!, + status: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}status'])!, + paymentSystem: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}payment_system'])!, + externalToken: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}external_token']), + meta: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}meta']), + date: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}date'])!, + products: $PaymentsTable.$converterproducts.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}products'])!), + packs: $PaymentsTable.$converterpacks.fromSql(attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}packs'])!), + subscription: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}subscription'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $PaymentsTable createAlias(String alias) { + return $PaymentsTable(attachedDatabase, alias); + } + + static TypeConverter?, String> $converterproducts = + const JsonListConverter(); + static TypeConverter, String> $converterpacks = + const StringListConverter(); +} + +class Payment extends DataClass implements Insertable { + final int id; + final int userId; + final String amount; + final String currency; + final String status; + final String paymentSystem; + final String? externalToken; + final String? meta; + final DateTime date; + final List? products; + final List packs; + final bool subscription; + final DateTime createdAt; + final DateTime updatedAt; + const Payment( + {required this.id, + required this.userId, + required this.amount, + required this.currency, + required this.status, + required this.paymentSystem, + this.externalToken, + this.meta, + required this.date, + this.products, + required this.packs, + required this.subscription, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['user_id'] = Variable(userId); + map['amount'] = Variable(amount); + map['currency'] = Variable(currency); + map['status'] = Variable(status); + map['payment_system'] = Variable(paymentSystem); + if (!nullToAbsent || externalToken != null) { + map['external_token'] = Variable(externalToken); + } + if (!nullToAbsent || meta != null) { + map['meta'] = Variable(meta); + } + map['date'] = Variable(date); + if (!nullToAbsent || products != null) { + map['products'] = + Variable($PaymentsTable.$converterproducts.toSql(products)); + } + { + map['packs'] = + Variable($PaymentsTable.$converterpacks.toSql(packs)); + } + map['subscription'] = Variable(subscription); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + PaymentsCompanion toCompanion(bool nullToAbsent) { + return PaymentsCompanion( + id: Value(id), + userId: Value(userId), + amount: Value(amount), + currency: Value(currency), + status: Value(status), + paymentSystem: Value(paymentSystem), + externalToken: externalToken == null && nullToAbsent + ? const Value.absent() + : Value(externalToken), + meta: meta == null && nullToAbsent ? const Value.absent() : Value(meta), + date: Value(date), + products: products == null && nullToAbsent + ? const Value.absent() + : Value(products), + packs: Value(packs), + subscription: Value(subscription), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory Payment.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Payment( + id: serializer.fromJson(json['id']), + userId: serializer.fromJson(json['userId']), + amount: serializer.fromJson(json['amount']), + currency: serializer.fromJson(json['currency']), + status: serializer.fromJson(json['status']), + paymentSystem: serializer.fromJson(json['paymentSystem']), + externalToken: serializer.fromJson(json['externalToken']), + meta: serializer.fromJson(json['meta']), + date: serializer.fromJson(json['date']), + products: serializer.fromJson?>(json['products']), + packs: serializer.fromJson>(json['packs']), + subscription: serializer.fromJson(json['subscription']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'userId': serializer.toJson(userId), + 'amount': serializer.toJson(amount), + 'currency': serializer.toJson(currency), + 'status': serializer.toJson(status), + 'paymentSystem': serializer.toJson(paymentSystem), + 'externalToken': serializer.toJson(externalToken), + 'meta': serializer.toJson(meta), + 'date': serializer.toJson(date), + 'products': serializer.toJson?>(products), + 'packs': serializer.toJson>(packs), + 'subscription': serializer.toJson(subscription), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + Payment copyWith( + {int? id, + int? userId, + String? amount, + String? currency, + String? status, + String? paymentSystem, + Value externalToken = const Value.absent(), + Value meta = const Value.absent(), + DateTime? date, + Value?> products = const Value.absent(), + List? packs, + bool? subscription, + DateTime? createdAt, + DateTime? updatedAt}) => + Payment( + id: id ?? this.id, + userId: userId ?? this.userId, + amount: amount ?? this.amount, + currency: currency ?? this.currency, + status: status ?? this.status, + paymentSystem: paymentSystem ?? this.paymentSystem, + externalToken: + externalToken.present ? externalToken.value : this.externalToken, + meta: meta.present ? meta.value : this.meta, + date: date ?? this.date, + products: products.present ? products.value : this.products, + packs: packs ?? this.packs, + subscription: subscription ?? this.subscription, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + @override + String toString() { + return (StringBuffer('Payment(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('amount: $amount, ') + ..write('currency: $currency, ') + ..write('status: $status, ') + ..write('paymentSystem: $paymentSystem, ') + ..write('externalToken: $externalToken, ') + ..write('meta: $meta, ') + ..write('date: $date, ') + ..write('products: $products, ') + ..write('packs: $packs, ') + ..write('subscription: $subscription, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + userId, + amount, + currency, + status, + paymentSystem, + externalToken, + meta, + date, + products, + packs, + subscription, + createdAt, + updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Payment && + other.id == this.id && + other.userId == this.userId && + other.amount == this.amount && + other.currency == this.currency && + other.status == this.status && + other.paymentSystem == this.paymentSystem && + other.externalToken == this.externalToken && + other.meta == this.meta && + other.date == this.date && + other.products == this.products && + other.packs == this.packs && + other.subscription == this.subscription && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class PaymentsCompanion extends UpdateCompanion { + final Value id; + final Value userId; + final Value amount; + final Value currency; + final Value status; + final Value paymentSystem; + final Value externalToken; + final Value meta; + final Value date; + final Value?> products; + final Value> packs; + final Value subscription; + final Value createdAt; + final Value updatedAt; + const PaymentsCompanion({ + this.id = const Value.absent(), + this.userId = const Value.absent(), + this.amount = const Value.absent(), + this.currency = const Value.absent(), + this.status = const Value.absent(), + this.paymentSystem = const Value.absent(), + this.externalToken = const Value.absent(), + this.meta = const Value.absent(), + this.date = const Value.absent(), + this.products = const Value.absent(), + this.packs = const Value.absent(), + this.subscription = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + PaymentsCompanion.insert({ + this.id = const Value.absent(), + required int userId, + required String amount, + required String currency, + required String status, + required String paymentSystem, + this.externalToken = const Value.absent(), + this.meta = const Value.absent(), + this.date = const Value.absent(), + this.products = const Value.absent(), + this.packs = const Value.absent(), + this.subscription = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : userId = Value(userId), + amount = Value(amount), + currency = Value(currency), + status = Value(status), + paymentSystem = Value(paymentSystem); + static Insertable custom({ + Expression? id, + Expression? userId, + Expression? amount, + Expression? currency, + Expression? status, + Expression? paymentSystem, + Expression? externalToken, + Expression? meta, + Expression? date, + Expression? products, + Expression? packs, + Expression? subscription, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (userId != null) 'user_id': userId, + if (amount != null) 'amount': amount, + if (currency != null) 'currency': currency, + if (status != null) 'status': status, + if (paymentSystem != null) 'payment_system': paymentSystem, + if (externalToken != null) 'external_token': externalToken, + if (meta != null) 'meta': meta, + if (date != null) 'date': date, + if (products != null) 'products': products, + if (packs != null) 'packs': packs, + if (subscription != null) 'subscription': subscription, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + PaymentsCompanion copyWith( + {Value? id, + Value? userId, + Value? amount, + Value? currency, + Value? status, + Value? paymentSystem, + Value? externalToken, + Value? meta, + Value? date, + Value?>? products, + Value>? packs, + Value? subscription, + Value? createdAt, + Value? updatedAt}) { + return PaymentsCompanion( + id: id ?? this.id, + userId: userId ?? this.userId, + amount: amount ?? this.amount, + currency: currency ?? this.currency, + status: status ?? this.status, + paymentSystem: paymentSystem ?? this.paymentSystem, + externalToken: externalToken ?? this.externalToken, + meta: meta ?? this.meta, + date: date ?? this.date, + products: products ?? this.products, + packs: packs ?? this.packs, + subscription: subscription ?? this.subscription, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (amount.present) { + map['amount'] = Variable(amount.value); + } + if (currency.present) { + map['currency'] = Variable(currency.value); + } + if (status.present) { + map['status'] = Variable(status.value); + } + if (paymentSystem.present) { + map['payment_system'] = Variable(paymentSystem.value); + } + if (externalToken.present) { + map['external_token'] = Variable(externalToken.value); + } + if (meta.present) { + map['meta'] = Variable(meta.value); + } + if (date.present) { + map['date'] = Variable(date.value); + } + if (products.present) { + map['products'] = Variable( + $PaymentsTable.$converterproducts.toSql(products.value)); + } + if (packs.present) { + map['packs'] = + Variable($PaymentsTable.$converterpacks.toSql(packs.value)); + } + if (subscription.present) { + map['subscription'] = Variable(subscription.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PaymentsCompanion(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('amount: $amount, ') + ..write('currency: $currency, ') + ..write('status: $status, ') + ..write('paymentSystem: $paymentSystem, ') + ..write('externalToken: $externalToken, ') + ..write('meta: $meta, ') + ..write('date: $date, ') + ..write('products: $products, ') + ..write('packs: $packs, ') + ..write('subscription: $subscription, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $TestsTable extends Tests with TableInfo<$TestsTable, Test> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TestsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _colorMeta = const VerificationMeta('color'); + @override + late final GeneratedColumn color = GeneratedColumn( + 'color', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _coverMeta = const VerificationMeta('cover'); + @override + late final GeneratedColumn cover = GeneratedColumn( + 'cover', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _versionMeta = + const VerificationMeta('version'); + @override + late final GeneratedColumn version = GeneratedColumn( + 'version', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _timeMeta = const VerificationMeta('time'); + @override + late final GeneratedColumn time = GeneratedColumn( + 'time', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _timeSubtitleMeta = + const VerificationMeta('timeSubtitle'); + @override + late final GeneratedColumn timeSubtitle = GeneratedColumn( + 'time_subtitle', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _isDeletedMeta = + const VerificationMeta('isDeleted'); + @override + late final GeneratedColumn isDeleted = GeneratedColumn( + 'is_deleted', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_deleted" IN (0, 1))'), + defaultValue: const Constant(false)); + @override + List get $columns => [ + id, + name, + color, + cover, + version, + time, + timeSubtitle, + createdAt, + updatedAt, + isDeleted + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'tests'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('color')) { + context.handle( + _colorMeta, color.isAcceptableOrUnknown(data['color']!, _colorMeta)); + } + if (data.containsKey('cover')) { + context.handle( + _coverMeta, cover.isAcceptableOrUnknown(data['cover']!, _coverMeta)); + } + if (data.containsKey('version')) { + context.handle(_versionMeta, + version.isAcceptableOrUnknown(data['version']!, _versionMeta)); + } + if (data.containsKey('time')) { + context.handle( + _timeMeta, time.isAcceptableOrUnknown(data['time']!, _timeMeta)); + } + if (data.containsKey('time_subtitle')) { + context.handle( + _timeSubtitleMeta, + timeSubtitle.isAcceptableOrUnknown( + data['time_subtitle']!, _timeSubtitleMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + if (data.containsKey('is_deleted')) { + context.handle(_isDeletedMeta, + isDeleted.isAcceptableOrUnknown(data['is_deleted']!, _isDeletedMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Test map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Test( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + color: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}color']), + cover: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}cover']), + version: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}version']), + time: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}time']), + timeSubtitle: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}time_subtitle']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + isDeleted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_deleted'])!, + ); + } + + @override + $TestsTable createAlias(String alias) { + return $TestsTable(attachedDatabase, alias); + } +} + +class Test extends DataClass implements Insertable { + final int id; + final String name; + final String? color; + final String? cover; + final String? version; + final String? time; + final String? timeSubtitle; + final DateTime createdAt; + final DateTime updatedAt; + final bool isDeleted; + const Test( + {required this.id, + required this.name, + this.color, + this.cover, + this.version, + this.time, + this.timeSubtitle, + required this.createdAt, + required this.updatedAt, + required this.isDeleted}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + if (!nullToAbsent || color != null) { + map['color'] = Variable(color); + } + if (!nullToAbsent || cover != null) { + map['cover'] = Variable(cover); + } + if (!nullToAbsent || version != null) { + map['version'] = Variable(version); + } + if (!nullToAbsent || time != null) { + map['time'] = Variable(time); + } + if (!nullToAbsent || timeSubtitle != null) { + map['time_subtitle'] = Variable(timeSubtitle); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['is_deleted'] = Variable(isDeleted); + return map; + } + + TestsCompanion toCompanion(bool nullToAbsent) { + return TestsCompanion( + id: Value(id), + name: Value(name), + color: + color == null && nullToAbsent ? const Value.absent() : Value(color), + cover: + cover == null && nullToAbsent ? const Value.absent() : Value(cover), + version: version == null && nullToAbsent + ? const Value.absent() + : Value(version), + time: time == null && nullToAbsent ? const Value.absent() : Value(time), + timeSubtitle: timeSubtitle == null && nullToAbsent + ? const Value.absent() + : Value(timeSubtitle), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + isDeleted: Value(isDeleted), + ); + } + + factory Test.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Test( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + color: serializer.fromJson(json['color']), + cover: serializer.fromJson(json['cover']), + version: serializer.fromJson(json['version']), + time: serializer.fromJson(json['time']), + timeSubtitle: serializer.fromJson(json['timeSubtitle']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + isDeleted: serializer.fromJson(json['isDeleted']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'color': serializer.toJson(color), + 'cover': serializer.toJson(cover), + 'version': serializer.toJson(version), + 'time': serializer.toJson(time), + 'timeSubtitle': serializer.toJson(timeSubtitle), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'isDeleted': serializer.toJson(isDeleted), + }; + } + + Test copyWith( + {int? id, + String? name, + Value color = const Value.absent(), + Value cover = const Value.absent(), + Value version = const Value.absent(), + Value time = const Value.absent(), + Value timeSubtitle = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + bool? isDeleted}) => + Test( + id: id ?? this.id, + name: name ?? this.name, + color: color.present ? color.value : this.color, + cover: cover.present ? cover.value : this.cover, + version: version.present ? version.value : this.version, + time: time.present ? time.value : this.time, + timeSubtitle: + timeSubtitle.present ? timeSubtitle.value : this.timeSubtitle, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + @override + String toString() { + return (StringBuffer('Test(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('color: $color, ') + ..write('cover: $cover, ') + ..write('version: $version, ') + ..write('time: $time, ') + ..write('timeSubtitle: $timeSubtitle, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, color, cover, version, time, + timeSubtitle, createdAt, updatedAt, isDeleted); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Test && + other.id == this.id && + other.name == this.name && + other.color == this.color && + other.cover == this.cover && + other.version == this.version && + other.time == this.time && + other.timeSubtitle == this.timeSubtitle && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.isDeleted == this.isDeleted); +} + +class TestsCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value color; + final Value cover; + final Value version; + final Value time; + final Value timeSubtitle; + final Value createdAt; + final Value updatedAt; + final Value isDeleted; + const TestsCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.color = const Value.absent(), + this.cover = const Value.absent(), + this.version = const Value.absent(), + this.time = const Value.absent(), + this.timeSubtitle = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }); + TestsCompanion.insert({ + this.id = const Value.absent(), + required String name, + this.color = const Value.absent(), + this.cover = const Value.absent(), + this.version = const Value.absent(), + this.time = const Value.absent(), + this.timeSubtitle = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }) : name = Value(name); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? color, + Expression? cover, + Expression? version, + Expression? time, + Expression? timeSubtitle, + Expression? createdAt, + Expression? updatedAt, + Expression? isDeleted, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (color != null) 'color': color, + if (cover != null) 'cover': cover, + if (version != null) 'version': version, + if (time != null) 'time': time, + if (timeSubtitle != null) 'time_subtitle': timeSubtitle, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (isDeleted != null) 'is_deleted': isDeleted, + }); + } + + TestsCompanion copyWith( + {Value? id, + Value? name, + Value? color, + Value? cover, + Value? version, + Value? time, + Value? timeSubtitle, + Value? createdAt, + Value? updatedAt, + Value? isDeleted}) { + return TestsCompanion( + id: id ?? this.id, + name: name ?? this.name, + color: color ?? this.color, + cover: cover ?? this.cover, + version: version ?? this.version, + time: time ?? this.time, + timeSubtitle: timeSubtitle ?? this.timeSubtitle, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (cover.present) { + map['cover'] = Variable(cover.value); + } + if (version.present) { + map['version'] = Variable(version.value); + } + if (time.present) { + map['time'] = Variable(time.value); + } + if (timeSubtitle.present) { + map['time_subtitle'] = Variable(timeSubtitle.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (isDeleted.present) { + map['is_deleted'] = Variable(isDeleted.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TestsCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('color: $color, ') + ..write('cover: $cover, ') + ..write('version: $version, ') + ..write('time: $time, ') + ..write('timeSubtitle: $timeSubtitle, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } +} + +class $TestQuestionsTable extends TestQuestions + with TableInfo<$TestQuestionsTable, TestQuestion> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TestQuestionsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _testIdMeta = const VerificationMeta('testId'); + @override + late final GeneratedColumn testId = GeneratedColumn( + 'test_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES tests (id) ON DELETE CASCADE')); + static const VerificationMeta _questionTypeMeta = + const VerificationMeta('questionType'); + @override + late final GeneratedColumn questionType = GeneratedColumn( + 'question_type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _bodyMeta = const VerificationMeta('body'); + @override + late final GeneratedColumn body = GeneratedColumn( + 'body', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => + [id, testId, questionType, body, createdAt, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'test_questions'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('test_id')) { + context.handle(_testIdMeta, + testId.isAcceptableOrUnknown(data['test_id']!, _testIdMeta)); + } else if (isInserting) { + context.missing(_testIdMeta); + } + if (data.containsKey('question_type')) { + context.handle( + _questionTypeMeta, + questionType.isAcceptableOrUnknown( + data['question_type']!, _questionTypeMeta)); + } else if (isInserting) { + context.missing(_questionTypeMeta); + } + if (data.containsKey('body')) { + context.handle( + _bodyMeta, body.isAcceptableOrUnknown(data['body']!, _bodyMeta)); + } else if (isInserting) { + context.missing(_bodyMeta); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + TestQuestion map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TestQuestion( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + testId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}test_id'])!, + questionType: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}question_type'])!, + body: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}body'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $TestQuestionsTable createAlias(String alias) { + return $TestQuestionsTable(attachedDatabase, alias); + } +} + +class TestQuestion extends DataClass implements Insertable { + final int id; + final int testId; + final String questionType; + final String body; + final DateTime createdAt; + final DateTime updatedAt; + const TestQuestion( + {required this.id, + required this.testId, + required this.questionType, + required this.body, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['test_id'] = Variable(testId); + map['question_type'] = Variable(questionType); + map['body'] = Variable(body); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + TestQuestionsCompanion toCompanion(bool nullToAbsent) { + return TestQuestionsCompanion( + id: Value(id), + testId: Value(testId), + questionType: Value(questionType), + body: Value(body), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory TestQuestion.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TestQuestion( + id: serializer.fromJson(json['id']), + testId: serializer.fromJson(json['testId']), + questionType: serializer.fromJson(json['questionType']), + body: serializer.fromJson(json['body']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'testId': serializer.toJson(testId), + 'questionType': serializer.toJson(questionType), + 'body': serializer.toJson(body), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + TestQuestion copyWith( + {int? id, + int? testId, + String? questionType, + String? body, + DateTime? createdAt, + DateTime? updatedAt}) => + TestQuestion( + id: id ?? this.id, + testId: testId ?? this.testId, + questionType: questionType ?? this.questionType, + body: body ?? this.body, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + @override + String toString() { + return (StringBuffer('TestQuestion(') + ..write('id: $id, ') + ..write('testId: $testId, ') + ..write('questionType: $questionType, ') + ..write('body: $body, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, testId, questionType, body, createdAt, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TestQuestion && + other.id == this.id && + other.testId == this.testId && + other.questionType == this.questionType && + other.body == this.body && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class TestQuestionsCompanion extends UpdateCompanion { + final Value id; + final Value testId; + final Value questionType; + final Value body; + final Value createdAt; + final Value updatedAt; + const TestQuestionsCompanion({ + this.id = const Value.absent(), + this.testId = const Value.absent(), + this.questionType = const Value.absent(), + this.body = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + TestQuestionsCompanion.insert({ + this.id = const Value.absent(), + required int testId, + required String questionType, + required String body, + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : testId = Value(testId), + questionType = Value(questionType), + body = Value(body); + static Insertable custom({ + Expression? id, + Expression? testId, + Expression? questionType, + Expression? body, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (testId != null) 'test_id': testId, + if (questionType != null) 'question_type': questionType, + if (body != null) 'body': body, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + TestQuestionsCompanion copyWith( + {Value? id, + Value? testId, + Value? questionType, + Value? body, + Value? createdAt, + Value? updatedAt}) { + return TestQuestionsCompanion( + id: id ?? this.id, + testId: testId ?? this.testId, + questionType: questionType ?? this.questionType, + body: body ?? this.body, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (testId.present) { + map['test_id'] = Variable(testId.value); + } + if (questionType.present) { + map['question_type'] = Variable(questionType.value); + } + if (body.present) { + map['body'] = Variable(body.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TestQuestionsCompanion(') + ..write('id: $id, ') + ..write('testId: $testId, ') + ..write('questionType: $questionType, ') + ..write('body: $body, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $TestPackRelationsTable extends TestPackRelations + with TableInfo<$TestPackRelationsTable, TestPackRelation> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TestPackRelationsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _testIdMeta = const VerificationMeta('testId'); + @override + late final GeneratedColumn testId = GeneratedColumn( + 'test_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES tests (id) ON DELETE CASCADE')); + static const VerificationMeta _packIdMeta = const VerificationMeta('packId'); + @override + late final GeneratedColumn packId = GeneratedColumn( + 'pack_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES card_packs (id) ON DELETE CASCADE')); + @override + List get $columns => [testId, packId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'test_pack_relations'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('test_id')) { + context.handle(_testIdMeta, + testId.isAcceptableOrUnknown(data['test_id']!, _testIdMeta)); + } else if (isInserting) { + context.missing(_testIdMeta); + } + if (data.containsKey('pack_id')) { + context.handle(_packIdMeta, + packId.isAcceptableOrUnknown(data['pack_id']!, _packIdMeta)); + } else if (isInserting) { + context.missing(_packIdMeta); + } + return context; + } + + @override + Set get $primaryKey => {testId, packId}; + @override + TestPackRelation map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TestPackRelation( + testId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}test_id'])!, + packId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}pack_id'])!, + ); + } + + @override + $TestPackRelationsTable createAlias(String alias) { + return $TestPackRelationsTable(attachedDatabase, alias); + } +} + +class TestPackRelation extends DataClass + implements Insertable { + final int testId; + final int packId; + const TestPackRelation({required this.testId, required this.packId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['test_id'] = Variable(testId); + map['pack_id'] = Variable(packId); + return map; + } + + TestPackRelationsCompanion toCompanion(bool nullToAbsent) { + return TestPackRelationsCompanion( + testId: Value(testId), + packId: Value(packId), + ); + } + + factory TestPackRelation.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TestPackRelation( + testId: serializer.fromJson(json['testId']), + packId: serializer.fromJson(json['packId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'testId': serializer.toJson(testId), + 'packId': serializer.toJson(packId), + }; + } + + TestPackRelation copyWith({int? testId, int? packId}) => TestPackRelation( + testId: testId ?? this.testId, + packId: packId ?? this.packId, + ); + @override + String toString() { + return (StringBuffer('TestPackRelation(') + ..write('testId: $testId, ') + ..write('packId: $packId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(testId, packId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TestPackRelation && + other.testId == this.testId && + other.packId == this.packId); +} + +class TestPackRelationsCompanion extends UpdateCompanion { + final Value testId; + final Value packId; + final Value rowid; + const TestPackRelationsCompanion({ + this.testId = const Value.absent(), + this.packId = const Value.absent(), + this.rowid = const Value.absent(), + }); + TestPackRelationsCompanion.insert({ + required int testId, + required int packId, + this.rowid = const Value.absent(), + }) : testId = Value(testId), + packId = Value(packId); + static Insertable custom({ + Expression? testId, + Expression? packId, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (testId != null) 'test_id': testId, + if (packId != null) 'pack_id': packId, + if (rowid != null) 'rowid': rowid, + }); + } + + TestPackRelationsCompanion copyWith( + {Value? testId, Value? packId, Value? rowid}) { + return TestPackRelationsCompanion( + testId: testId ?? this.testId, + packId: packId ?? this.packId, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (testId.present) { + map['test_id'] = Variable(testId.value); + } + if (packId.present) { + map['pack_id'] = Variable(packId.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TestPackRelationsCompanion(') + ..write('testId: $testId, ') + ..write('packId: $packId, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $TestStatisticsTable extends TestStatistics + with TableInfo<$TestStatisticsTable, TestStatistic> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TestStatisticsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _testIdMeta = const VerificationMeta('testId'); + @override + late final GeneratedColumn testId = GeneratedColumn( + 'test_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES tests (id) ON DELETE CASCADE')); + static const VerificationMeta _resultsMeta = + const VerificationMeta('results'); + @override + late final GeneratedColumnWithTypeConverter?, String> + results = GeneratedColumn('results', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('{}')) + .withConverter?>( + $TestStatisticsTable.$converterresults); + static const VerificationMeta _completedAtMeta = + const VerificationMeta('completedAt'); + @override + late final GeneratedColumn completedAt = GeneratedColumn( + 'completed_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => + [id, userId, testId, results, completedAt, createdAt, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'test_statistics'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('test_id')) { + context.handle(_testIdMeta, + testId.isAcceptableOrUnknown(data['test_id']!, _testIdMeta)); + } else if (isInserting) { + context.missing(_testIdMeta); + } + context.handle(_resultsMeta, const VerificationResult.success()); + if (data.containsKey('completed_at')) { + context.handle( + _completedAtMeta, + completedAt.isAcceptableOrUnknown( + data['completed_at']!, _completedAtMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + TestStatistic map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TestStatistic( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + testId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}test_id'])!, + results: $TestStatisticsTable.$converterresults.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}results'])!), + completedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}completed_at'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $TestStatisticsTable createAlias(String alias) { + return $TestStatisticsTable(attachedDatabase, alias); + } + + static TypeConverter?, String> $converterresults = + const JsonMapConverter(); +} + +class TestStatistic extends DataClass implements Insertable { + final int id; + final int userId; + final int testId; + final Map? results; + final DateTime completedAt; + final DateTime createdAt; + final DateTime updatedAt; + const TestStatistic( + {required this.id, + required this.userId, + required this.testId, + this.results, + required this.completedAt, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['user_id'] = Variable(userId); + map['test_id'] = Variable(testId); + if (!nullToAbsent || results != null) { + map['results'] = Variable( + $TestStatisticsTable.$converterresults.toSql(results)); + } + map['completed_at'] = Variable(completedAt); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + TestStatisticsCompanion toCompanion(bool nullToAbsent) { + return TestStatisticsCompanion( + id: Value(id), + userId: Value(userId), + testId: Value(testId), + results: results == null && nullToAbsent + ? const Value.absent() + : Value(results), + completedAt: Value(completedAt), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory TestStatistic.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TestStatistic( + id: serializer.fromJson(json['id']), + userId: serializer.fromJson(json['userId']), + testId: serializer.fromJson(json['testId']), + results: serializer.fromJson?>(json['results']), + completedAt: serializer.fromJson(json['completedAt']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'userId': serializer.toJson(userId), + 'testId': serializer.toJson(testId), + 'results': serializer.toJson?>(results), + 'completedAt': serializer.toJson(completedAt), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + TestStatistic copyWith( + {int? id, + int? userId, + int? testId, + Value?> results = const Value.absent(), + DateTime? completedAt, + DateTime? createdAt, + DateTime? updatedAt}) => + TestStatistic( + id: id ?? this.id, + userId: userId ?? this.userId, + testId: testId ?? this.testId, + results: results.present ? results.value : this.results, + completedAt: completedAt ?? this.completedAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + @override + String toString() { + return (StringBuffer('TestStatistic(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('testId: $testId, ') + ..write('results: $results, ') + ..write('completedAt: $completedAt, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, userId, testId, results, completedAt, createdAt, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TestStatistic && + other.id == this.id && + other.userId == this.userId && + other.testId == this.testId && + other.results == this.results && + other.completedAt == this.completedAt && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class TestStatisticsCompanion extends UpdateCompanion { + final Value id; + final Value userId; + final Value testId; + final Value?> results; + final Value completedAt; + final Value createdAt; + final Value updatedAt; + const TestStatisticsCompanion({ + this.id = const Value.absent(), + this.userId = const Value.absent(), + this.testId = const Value.absent(), + this.results = const Value.absent(), + this.completedAt = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + TestStatisticsCompanion.insert({ + this.id = const Value.absent(), + required int userId, + required int testId, + this.results = const Value.absent(), + this.completedAt = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : userId = Value(userId), + testId = Value(testId); + static Insertable custom({ + Expression? id, + Expression? userId, + Expression? testId, + Expression? results, + Expression? completedAt, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (userId != null) 'user_id': userId, + if (testId != null) 'test_id': testId, + if (results != null) 'results': results, + if (completedAt != null) 'completed_at': completedAt, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + TestStatisticsCompanion copyWith( + {Value? id, + Value? userId, + Value? testId, + Value?>? results, + Value? completedAt, + Value? createdAt, + Value? updatedAt}) { + return TestStatisticsCompanion( + id: id ?? this.id, + userId: userId ?? this.userId, + testId: testId ?? this.testId, + results: results ?? this.results, + completedAt: completedAt ?? this.completedAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (testId.present) { + map['test_id'] = Variable(testId.value); + } + if (results.present) { + map['results'] = Variable( + $TestStatisticsTable.$converterresults.toSql(results.value)); + } + if (completedAt.present) { + map['completed_at'] = Variable(completedAt.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TestStatisticsCompanion(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('testId: $testId, ') + ..write('results: $results, ') + ..write('completedAt: $completedAt, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $TasksTable extends Tasks with TableInfo<$TasksTable, Task> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TasksTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _minCycleMillisMeta = + const VerificationMeta('minCycleMillis'); + @override + late final GeneratedColumn minCycleMillis = GeneratedColumn( + 'min_cycle_millis', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _maxCycleMillisMeta = + const VerificationMeta('maxCycleMillis'); + @override + late final GeneratedColumn maxCycleMillis = GeneratedColumn( + 'max_cycle_millis', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _intervalMillisMeta = + const VerificationMeta('intervalMillis'); + @override + late final GeneratedColumn intervalMillis = GeneratedColumn( + 'interval_millis', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _timeoutMillisMeta = + const VerificationMeta('timeoutMillis'); + @override + late final GeneratedColumn timeoutMillis = GeneratedColumn( + 'timeout_millis', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _statusMeta = const VerificationMeta('status'); + @override + late final GeneratedColumn status = GeneratedColumn( + 'status', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _descriptionMeta = + const VerificationMeta('description'); + @override + late final GeneratedColumn description = GeneratedColumn( + 'description', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _lastExecutionMeta = + const VerificationMeta('lastExecution'); + @override + late final GeneratedColumn lastExecution = + GeneratedColumn('last_execution', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [ + id, + name, + minCycleMillis, + maxCycleMillis, + intervalMillis, + timeoutMillis, + status, + description, + lastExecution, + createdAt, + updatedAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'tasks'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('min_cycle_millis')) { + context.handle( + _minCycleMillisMeta, + minCycleMillis.isAcceptableOrUnknown( + data['min_cycle_millis']!, _minCycleMillisMeta)); + } else if (isInserting) { + context.missing(_minCycleMillisMeta); + } + if (data.containsKey('max_cycle_millis')) { + context.handle( + _maxCycleMillisMeta, + maxCycleMillis.isAcceptableOrUnknown( + data['max_cycle_millis']!, _maxCycleMillisMeta)); + } else if (isInserting) { + context.missing(_maxCycleMillisMeta); + } + if (data.containsKey('interval_millis')) { + context.handle( + _intervalMillisMeta, + intervalMillis.isAcceptableOrUnknown( + data['interval_millis']!, _intervalMillisMeta)); + } else if (isInserting) { + context.missing(_intervalMillisMeta); + } + if (data.containsKey('timeout_millis')) { + context.handle( + _timeoutMillisMeta, + timeoutMillis.isAcceptableOrUnknown( + data['timeout_millis']!, _timeoutMillisMeta)); + } else if (isInserting) { + context.missing(_timeoutMillisMeta); + } + if (data.containsKey('status')) { + context.handle(_statusMeta, + status.isAcceptableOrUnknown(data['status']!, _statusMeta)); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, _descriptionMeta)); + } + if (data.containsKey('last_execution')) { + context.handle( + _lastExecutionMeta, + lastExecution.isAcceptableOrUnknown( + data['last_execution']!, _lastExecutionMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Task map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Task( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + minCycleMillis: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}min_cycle_millis'])!, + maxCycleMillis: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}max_cycle_millis'])!, + intervalMillis: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}interval_millis'])!, + timeoutMillis: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}timeout_millis'])!, + status: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}status']), + description: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}description']), + lastExecution: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, data['${effectivePrefix}last_execution']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $TasksTable createAlias(String alias) { + return $TasksTable(attachedDatabase, alias); + } +} + +class Task extends DataClass implements Insertable { + final int id; + final String name; + final int minCycleMillis; + final int maxCycleMillis; + final int intervalMillis; + final int timeoutMillis; + final String? status; + final String? description; + final DateTime? lastExecution; + final DateTime createdAt; + final DateTime updatedAt; + const Task( + {required this.id, + required this.name, + required this.minCycleMillis, + required this.maxCycleMillis, + required this.intervalMillis, + required this.timeoutMillis, + this.status, + this.description, + this.lastExecution, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['min_cycle_millis'] = Variable(minCycleMillis); + map['max_cycle_millis'] = Variable(maxCycleMillis); + map['interval_millis'] = Variable(intervalMillis); + map['timeout_millis'] = Variable(timeoutMillis); + if (!nullToAbsent || status != null) { + map['status'] = Variable(status); + } + if (!nullToAbsent || description != null) { + map['description'] = Variable(description); + } + if (!nullToAbsent || lastExecution != null) { + map['last_execution'] = Variable(lastExecution); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + TasksCompanion toCompanion(bool nullToAbsent) { + return TasksCompanion( + id: Value(id), + name: Value(name), + minCycleMillis: Value(minCycleMillis), + maxCycleMillis: Value(maxCycleMillis), + intervalMillis: Value(intervalMillis), + timeoutMillis: Value(timeoutMillis), + status: + status == null && nullToAbsent ? const Value.absent() : Value(status), + description: description == null && nullToAbsent + ? const Value.absent() + : Value(description), + lastExecution: lastExecution == null && nullToAbsent + ? const Value.absent() + : Value(lastExecution), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory Task.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Task( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + minCycleMillis: serializer.fromJson(json['minCycleMillis']), + maxCycleMillis: serializer.fromJson(json['maxCycleMillis']), + intervalMillis: serializer.fromJson(json['intervalMillis']), + timeoutMillis: serializer.fromJson(json['timeoutMillis']), + status: serializer.fromJson(json['status']), + description: serializer.fromJson(json['description']), + lastExecution: serializer.fromJson(json['lastExecution']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'minCycleMillis': serializer.toJson(minCycleMillis), + 'maxCycleMillis': serializer.toJson(maxCycleMillis), + 'intervalMillis': serializer.toJson(intervalMillis), + 'timeoutMillis': serializer.toJson(timeoutMillis), + 'status': serializer.toJson(status), + 'description': serializer.toJson(description), + 'lastExecution': serializer.toJson(lastExecution), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + Task copyWith( + {int? id, + String? name, + int? minCycleMillis, + int? maxCycleMillis, + int? intervalMillis, + int? timeoutMillis, + Value status = const Value.absent(), + Value description = const Value.absent(), + Value lastExecution = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt}) => + Task( + id: id ?? this.id, + name: name ?? this.name, + minCycleMillis: minCycleMillis ?? this.minCycleMillis, + maxCycleMillis: maxCycleMillis ?? this.maxCycleMillis, + intervalMillis: intervalMillis ?? this.intervalMillis, + timeoutMillis: timeoutMillis ?? this.timeoutMillis, + status: status.present ? status.value : this.status, + description: description.present ? description.value : this.description, + lastExecution: + lastExecution.present ? lastExecution.value : this.lastExecution, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + @override + String toString() { + return (StringBuffer('Task(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('minCycleMillis: $minCycleMillis, ') + ..write('maxCycleMillis: $maxCycleMillis, ') + ..write('intervalMillis: $intervalMillis, ') + ..write('timeoutMillis: $timeoutMillis, ') + ..write('status: $status, ') + ..write('description: $description, ') + ..write('lastExecution: $lastExecution, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + minCycleMillis, + maxCycleMillis, + intervalMillis, + timeoutMillis, + status, + description, + lastExecution, + createdAt, + updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Task && + other.id == this.id && + other.name == this.name && + other.minCycleMillis == this.minCycleMillis && + other.maxCycleMillis == this.maxCycleMillis && + other.intervalMillis == this.intervalMillis && + other.timeoutMillis == this.timeoutMillis && + other.status == this.status && + other.description == this.description && + other.lastExecution == this.lastExecution && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class TasksCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value minCycleMillis; + final Value maxCycleMillis; + final Value intervalMillis; + final Value timeoutMillis; + final Value status; + final Value description; + final Value lastExecution; + final Value createdAt; + final Value updatedAt; + const TasksCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.minCycleMillis = const Value.absent(), + this.maxCycleMillis = const Value.absent(), + this.intervalMillis = const Value.absent(), + this.timeoutMillis = const Value.absent(), + this.status = const Value.absent(), + this.description = const Value.absent(), + this.lastExecution = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + TasksCompanion.insert({ + this.id = const Value.absent(), + required String name, + required int minCycleMillis, + required int maxCycleMillis, + required int intervalMillis, + required int timeoutMillis, + this.status = const Value.absent(), + this.description = const Value.absent(), + this.lastExecution = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : name = Value(name), + minCycleMillis = Value(minCycleMillis), + maxCycleMillis = Value(maxCycleMillis), + intervalMillis = Value(intervalMillis), + timeoutMillis = Value(timeoutMillis); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? minCycleMillis, + Expression? maxCycleMillis, + Expression? intervalMillis, + Expression? timeoutMillis, + Expression? status, + Expression? description, + Expression? lastExecution, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (minCycleMillis != null) 'min_cycle_millis': minCycleMillis, + if (maxCycleMillis != null) 'max_cycle_millis': maxCycleMillis, + if (intervalMillis != null) 'interval_millis': intervalMillis, + if (timeoutMillis != null) 'timeout_millis': timeoutMillis, + if (status != null) 'status': status, + if (description != null) 'description': description, + if (lastExecution != null) 'last_execution': lastExecution, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + TasksCompanion copyWith( + {Value? id, + Value? name, + Value? minCycleMillis, + Value? maxCycleMillis, + Value? intervalMillis, + Value? timeoutMillis, + Value? status, + Value? description, + Value? lastExecution, + Value? createdAt, + Value? updatedAt}) { + return TasksCompanion( + id: id ?? this.id, + name: name ?? this.name, + minCycleMillis: minCycleMillis ?? this.minCycleMillis, + maxCycleMillis: maxCycleMillis ?? this.maxCycleMillis, + intervalMillis: intervalMillis ?? this.intervalMillis, + timeoutMillis: timeoutMillis ?? this.timeoutMillis, + status: status ?? this.status, + description: description ?? this.description, + lastExecution: lastExecution ?? this.lastExecution, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (minCycleMillis.present) { + map['min_cycle_millis'] = Variable(minCycleMillis.value); + } + if (maxCycleMillis.present) { + map['max_cycle_millis'] = Variable(maxCycleMillis.value); + } + if (intervalMillis.present) { + map['interval_millis'] = Variable(intervalMillis.value); + } + if (timeoutMillis.present) { + map['timeout_millis'] = Variable(timeoutMillis.value); + } + if (status.present) { + map['status'] = Variable(status.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (lastExecution.present) { + map['last_execution'] = Variable(lastExecution.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TasksCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('minCycleMillis: $minCycleMillis, ') + ..write('maxCycleMillis: $maxCycleMillis, ') + ..write('intervalMillis: $intervalMillis, ') + ..write('timeoutMillis: $timeoutMillis, ') + ..write('status: $status, ') + ..write('description: $description, ') + ..write('lastExecution: $lastExecution, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $UserTasksTable extends UserTasks + with TableInfo<$UserTasksTable, UserTask> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserTasksTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _titleMeta = const VerificationMeta('title'); + @override + late final GeneratedColumn title = GeneratedColumn( + 'title', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _descriptionMeta = + const VerificationMeta('description'); + @override + late final GeneratedColumn description = GeneratedColumn( + 'description', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _typeMeta = const VerificationMeta('type'); + @override + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _difficultyMeta = + const VerificationMeta('difficulty'); + @override + late final GeneratedColumn difficulty = GeneratedColumn( + 'difficulty', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _statusMeta = const VerificationMeta('status'); + @override + late final GeneratedColumn status = GeneratedColumn( + 'status', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _rewardsMeta = + const VerificationMeta('rewards'); + @override + late final GeneratedColumnWithTypeConverter?, String> rewards = + GeneratedColumn('rewards', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter?>($UserTasksTable.$converterrewards); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _expiresAtMeta = + const VerificationMeta('expiresAt'); + @override + late final GeneratedColumn expiresAt = GeneratedColumn( + 'expires_at', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _completedAtMeta = + const VerificationMeta('completedAt'); + @override + late final GeneratedColumn completedAt = GeneratedColumn( + 'completed_at', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _proofUrlMeta = + const VerificationMeta('proofUrl'); + @override + late final GeneratedColumn proofUrl = GeneratedColumn( + 'proof_url', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _instructionsMeta = + const VerificationMeta('instructions'); + @override + late final GeneratedColumn instructions = GeneratedColumn( + 'instructions', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _tagsMeta = const VerificationMeta('tags'); + @override + late final GeneratedColumnWithTypeConverter, String> tags = + GeneratedColumn('tags', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter>($UserTasksTable.$convertertags); + static const VerificationMeta _imageUrlMeta = + const VerificationMeta('imageUrl'); + @override + late final GeneratedColumn imageUrl = GeneratedColumn( + 'image_url', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [ + id, + title, + description, + type, + difficulty, + status, + rewards, + createdAt, + expiresAt, + completedAt, + proofUrl, + instructions, + tags, + imageUrl, + updatedAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_tasks'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('title')) { + context.handle( + _titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta)); + } else if (isInserting) { + context.missing(_titleMeta); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, _descriptionMeta)); + } else if (isInserting) { + context.missing(_descriptionMeta); + } + if (data.containsKey('type')) { + context.handle( + _typeMeta, type.isAcceptableOrUnknown(data['type']!, _typeMeta)); + } else if (isInserting) { + context.missing(_typeMeta); + } + if (data.containsKey('difficulty')) { + context.handle( + _difficultyMeta, + difficulty.isAcceptableOrUnknown( + data['difficulty']!, _difficultyMeta)); + } else if (isInserting) { + context.missing(_difficultyMeta); + } + if (data.containsKey('status')) { + context.handle(_statusMeta, + status.isAcceptableOrUnknown(data['status']!, _statusMeta)); + } else if (isInserting) { + context.missing(_statusMeta); + } + context.handle(_rewardsMeta, const VerificationResult.success()); + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('expires_at')) { + context.handle(_expiresAtMeta, + expiresAt.isAcceptableOrUnknown(data['expires_at']!, _expiresAtMeta)); + } else if (isInserting) { + context.missing(_expiresAtMeta); + } + if (data.containsKey('completed_at')) { + context.handle( + _completedAtMeta, + completedAt.isAcceptableOrUnknown( + data['completed_at']!, _completedAtMeta)); + } + if (data.containsKey('proof_url')) { + context.handle(_proofUrlMeta, + proofUrl.isAcceptableOrUnknown(data['proof_url']!, _proofUrlMeta)); + } + if (data.containsKey('instructions')) { + context.handle( + _instructionsMeta, + instructions.isAcceptableOrUnknown( + data['instructions']!, _instructionsMeta)); + } + context.handle(_tagsMeta, const VerificationResult.success()); + if (data.containsKey('image_url')) { + context.handle(_imageUrlMeta, + imageUrl.isAcceptableOrUnknown(data['image_url']!, _imageUrlMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + UserTask map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserTask( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + title: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}title'])!, + description: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}description'])!, + type: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + difficulty: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}difficulty'])!, + status: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}status'])!, + rewards: $UserTasksTable.$converterrewards.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}rewards'])!), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + expiresAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}expires_at'])!, + completedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}completed_at']), + proofUrl: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}proof_url']), + instructions: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}instructions']), + tags: $UserTasksTable.$convertertags.fromSql(attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}tags'])!), + imageUrl: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}image_url']), + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $UserTasksTable createAlias(String alias) { + return $UserTasksTable(attachedDatabase, alias); + } + + static TypeConverter?, String> $converterrewards = + const JsonListConverter(); + static TypeConverter, String> $convertertags = + const StringListConverter(); +} + +class UserTask extends DataClass implements Insertable { + final int id; + final String title; + final String description; + final String type; + final String difficulty; + final String status; + final List? rewards; + final DateTime createdAt; + final DateTime expiresAt; + final DateTime? completedAt; + final String? proofUrl; + final String? instructions; + final List tags; + final String? imageUrl; + final DateTime updatedAt; + const UserTask( + {required this.id, + required this.title, + required this.description, + required this.type, + required this.difficulty, + required this.status, + this.rewards, + required this.createdAt, + required this.expiresAt, + this.completedAt, + this.proofUrl, + this.instructions, + required this.tags, + this.imageUrl, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['title'] = Variable(title); + map['description'] = Variable(description); + map['type'] = Variable(type); + map['difficulty'] = Variable(difficulty); + map['status'] = Variable(status); + if (!nullToAbsent || rewards != null) { + map['rewards'] = + Variable($UserTasksTable.$converterrewards.toSql(rewards)); + } + map['created_at'] = Variable(createdAt); + map['expires_at'] = Variable(expiresAt); + if (!nullToAbsent || completedAt != null) { + map['completed_at'] = Variable(completedAt); + } + if (!nullToAbsent || proofUrl != null) { + map['proof_url'] = Variable(proofUrl); + } + if (!nullToAbsent || instructions != null) { + map['instructions'] = Variable(instructions); + } + { + map['tags'] = + Variable($UserTasksTable.$convertertags.toSql(tags)); + } + if (!nullToAbsent || imageUrl != null) { + map['image_url'] = Variable(imageUrl); + } + map['updated_at'] = Variable(updatedAt); + return map; + } + + UserTasksCompanion toCompanion(bool nullToAbsent) { + return UserTasksCompanion( + id: Value(id), + title: Value(title), + description: Value(description), + type: Value(type), + difficulty: Value(difficulty), + status: Value(status), + rewards: rewards == null && nullToAbsent + ? const Value.absent() + : Value(rewards), + createdAt: Value(createdAt), + expiresAt: Value(expiresAt), + completedAt: completedAt == null && nullToAbsent + ? const Value.absent() + : Value(completedAt), + proofUrl: proofUrl == null && nullToAbsent + ? const Value.absent() + : Value(proofUrl), + instructions: instructions == null && nullToAbsent + ? const Value.absent() + : Value(instructions), + tags: Value(tags), + imageUrl: imageUrl == null && nullToAbsent + ? const Value.absent() + : Value(imageUrl), + updatedAt: Value(updatedAt), + ); + } + + factory UserTask.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserTask( + id: serializer.fromJson(json['id']), + title: serializer.fromJson(json['title']), + description: serializer.fromJson(json['description']), + type: serializer.fromJson(json['type']), + difficulty: serializer.fromJson(json['difficulty']), + status: serializer.fromJson(json['status']), + rewards: serializer.fromJson?>(json['rewards']), + createdAt: serializer.fromJson(json['createdAt']), + expiresAt: serializer.fromJson(json['expiresAt']), + completedAt: serializer.fromJson(json['completedAt']), + proofUrl: serializer.fromJson(json['proofUrl']), + instructions: serializer.fromJson(json['instructions']), + tags: serializer.fromJson>(json['tags']), + imageUrl: serializer.fromJson(json['imageUrl']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'title': serializer.toJson(title), + 'description': serializer.toJson(description), + 'type': serializer.toJson(type), + 'difficulty': serializer.toJson(difficulty), + 'status': serializer.toJson(status), + 'rewards': serializer.toJson?>(rewards), + 'createdAt': serializer.toJson(createdAt), + 'expiresAt': serializer.toJson(expiresAt), + 'completedAt': serializer.toJson(completedAt), + 'proofUrl': serializer.toJson(proofUrl), + 'instructions': serializer.toJson(instructions), + 'tags': serializer.toJson>(tags), + 'imageUrl': serializer.toJson(imageUrl), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + UserTask copyWith( + {int? id, + String? title, + String? description, + String? type, + String? difficulty, + String? status, + Value?> rewards = const Value.absent(), + DateTime? createdAt, + DateTime? expiresAt, + Value completedAt = const Value.absent(), + Value proofUrl = const Value.absent(), + Value instructions = const Value.absent(), + List? tags, + Value imageUrl = const Value.absent(), + DateTime? updatedAt}) => + UserTask( + id: id ?? this.id, + title: title ?? this.title, + description: description ?? this.description, + type: type ?? this.type, + difficulty: difficulty ?? this.difficulty, + status: status ?? this.status, + rewards: rewards.present ? rewards.value : this.rewards, + createdAt: createdAt ?? this.createdAt, + expiresAt: expiresAt ?? this.expiresAt, + completedAt: completedAt.present ? completedAt.value : this.completedAt, + proofUrl: proofUrl.present ? proofUrl.value : this.proofUrl, + instructions: + instructions.present ? instructions.value : this.instructions, + tags: tags ?? this.tags, + imageUrl: imageUrl.present ? imageUrl.value : this.imageUrl, + updatedAt: updatedAt ?? this.updatedAt, + ); + @override + String toString() { + return (StringBuffer('UserTask(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('description: $description, ') + ..write('type: $type, ') + ..write('difficulty: $difficulty, ') + ..write('status: $status, ') + ..write('rewards: $rewards, ') + ..write('createdAt: $createdAt, ') + ..write('expiresAt: $expiresAt, ') + ..write('completedAt: $completedAt, ') + ..write('proofUrl: $proofUrl, ') + ..write('instructions: $instructions, ') + ..write('tags: $tags, ') + ..write('imageUrl: $imageUrl, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + title, + description, + type, + difficulty, + status, + rewards, + createdAt, + expiresAt, + completedAt, + proofUrl, + instructions, + tags, + imageUrl, + updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserTask && + other.id == this.id && + other.title == this.title && + other.description == this.description && + other.type == this.type && + other.difficulty == this.difficulty && + other.status == this.status && + other.rewards == this.rewards && + other.createdAt == this.createdAt && + other.expiresAt == this.expiresAt && + other.completedAt == this.completedAt && + other.proofUrl == this.proofUrl && + other.instructions == this.instructions && + other.tags == this.tags && + other.imageUrl == this.imageUrl && + other.updatedAt == this.updatedAt); +} + +class UserTasksCompanion extends UpdateCompanion { + final Value id; + final Value title; + final Value description; + final Value type; + final Value difficulty; + final Value status; + final Value?> rewards; + final Value createdAt; + final Value expiresAt; + final Value completedAt; + final Value proofUrl; + final Value instructions; + final Value> tags; + final Value imageUrl; + final Value updatedAt; + const UserTasksCompanion({ + this.id = const Value.absent(), + this.title = const Value.absent(), + this.description = const Value.absent(), + this.type = const Value.absent(), + this.difficulty = const Value.absent(), + this.status = const Value.absent(), + this.rewards = const Value.absent(), + this.createdAt = const Value.absent(), + this.expiresAt = const Value.absent(), + this.completedAt = const Value.absent(), + this.proofUrl = const Value.absent(), + this.instructions = const Value.absent(), + this.tags = const Value.absent(), + this.imageUrl = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + UserTasksCompanion.insert({ + this.id = const Value.absent(), + required String title, + required String description, + required String type, + required String difficulty, + required String status, + this.rewards = const Value.absent(), + this.createdAt = const Value.absent(), + required DateTime expiresAt, + this.completedAt = const Value.absent(), + this.proofUrl = const Value.absent(), + this.instructions = const Value.absent(), + this.tags = const Value.absent(), + this.imageUrl = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : title = Value(title), + description = Value(description), + type = Value(type), + difficulty = Value(difficulty), + status = Value(status), + expiresAt = Value(expiresAt); + static Insertable custom({ + Expression? id, + Expression? title, + Expression? description, + Expression? type, + Expression? difficulty, + Expression? status, + Expression? rewards, + Expression? createdAt, + Expression? expiresAt, + Expression? completedAt, + Expression? proofUrl, + Expression? instructions, + Expression? tags, + Expression? imageUrl, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (title != null) 'title': title, + if (description != null) 'description': description, + if (type != null) 'type': type, + if (difficulty != null) 'difficulty': difficulty, + if (status != null) 'status': status, + if (rewards != null) 'rewards': rewards, + if (createdAt != null) 'created_at': createdAt, + if (expiresAt != null) 'expires_at': expiresAt, + if (completedAt != null) 'completed_at': completedAt, + if (proofUrl != null) 'proof_url': proofUrl, + if (instructions != null) 'instructions': instructions, + if (tags != null) 'tags': tags, + if (imageUrl != null) 'image_url': imageUrl, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + UserTasksCompanion copyWith( + {Value? id, + Value? title, + Value? description, + Value? type, + Value? difficulty, + Value? status, + Value?>? rewards, + Value? createdAt, + Value? expiresAt, + Value? completedAt, + Value? proofUrl, + Value? instructions, + Value>? tags, + Value? imageUrl, + Value? updatedAt}) { + return UserTasksCompanion( + id: id ?? this.id, + title: title ?? this.title, + description: description ?? this.description, + type: type ?? this.type, + difficulty: difficulty ?? this.difficulty, + status: status ?? this.status, + rewards: rewards ?? this.rewards, + createdAt: createdAt ?? this.createdAt, + expiresAt: expiresAt ?? this.expiresAt, + completedAt: completedAt ?? this.completedAt, + proofUrl: proofUrl ?? this.proofUrl, + instructions: instructions ?? this.instructions, + tags: tags ?? this.tags, + imageUrl: imageUrl ?? this.imageUrl, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (title.present) { + map['title'] = Variable(title.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (difficulty.present) { + map['difficulty'] = Variable(difficulty.value); + } + if (status.present) { + map['status'] = Variable(status.value); + } + if (rewards.present) { + map['rewards'] = Variable( + $UserTasksTable.$converterrewards.toSql(rewards.value)); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (expiresAt.present) { + map['expires_at'] = Variable(expiresAt.value); + } + if (completedAt.present) { + map['completed_at'] = Variable(completedAt.value); + } + if (proofUrl.present) { + map['proof_url'] = Variable(proofUrl.value); + } + if (instructions.present) { + map['instructions'] = Variable(instructions.value); + } + if (tags.present) { + map['tags'] = + Variable($UserTasksTable.$convertertags.toSql(tags.value)); + } + if (imageUrl.present) { + map['image_url'] = Variable(imageUrl.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserTasksCompanion(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('description: $description, ') + ..write('type: $type, ') + ..write('difficulty: $difficulty, ') + ..write('status: $status, ') + ..write('rewards: $rewards, ') + ..write('createdAt: $createdAt, ') + ..write('expiresAt: $expiresAt, ') + ..write('completedAt: $completedAt, ') + ..write('proofUrl: $proofUrl, ') + ..write('instructions: $instructions, ') + ..write('tags: $tags, ') + ..write('imageUrl: $imageUrl, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $UserTaskProgressesTable extends UserTaskProgresses + with TableInfo<$UserTaskProgressesTable, UserTaskProgresses> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserTaskProgressesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES users (id) ON DELETE CASCADE')); + static const VerificationMeta _taskIdMeta = const VerificationMeta('taskId'); + @override + late final GeneratedColumn taskId = GeneratedColumn( + 'task_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _progressMeta = + const VerificationMeta('progress'); + @override + late final GeneratedColumnWithTypeConverter?, String> + progress = GeneratedColumn('progress', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('{}')) + .withConverter?>( + $UserTaskProgressesTable.$converterprogress); + static const VerificationMeta _startedAtMeta = + const VerificationMeta('startedAt'); + @override + late final GeneratedColumn startedAt = GeneratedColumn( + 'started_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => + [id, userId, taskId, progress, startedAt, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_task_progresses'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('task_id')) { + context.handle(_taskIdMeta, + taskId.isAcceptableOrUnknown(data['task_id']!, _taskIdMeta)); + } else if (isInserting) { + context.missing(_taskIdMeta); + } + context.handle(_progressMeta, const VerificationResult.success()); + if (data.containsKey('started_at')) { + context.handle(_startedAtMeta, + startedAt.isAcceptableOrUnknown(data['started_at']!, _startedAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + UserTaskProgresses map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserTaskProgresses( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + taskId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}task_id'])!, + progress: $UserTaskProgressesTable.$converterprogress.fromSql( + attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}progress'])!), + startedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}started_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $UserTaskProgressesTable createAlias(String alias) { + return $UserTaskProgressesTable(attachedDatabase, alias); + } + + static TypeConverter?, String> $converterprogress = + const JsonMapConverter(); +} + +class UserTaskProgresses extends DataClass + implements Insertable { + final int id; + final int userId; + final int taskId; + final Map? progress; + final DateTime startedAt; + final DateTime updatedAt; + const UserTaskProgresses( + {required this.id, + required this.userId, + required this.taskId, + this.progress, + required this.startedAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['user_id'] = Variable(userId); + map['task_id'] = Variable(taskId); + if (!nullToAbsent || progress != null) { + map['progress'] = Variable( + $UserTaskProgressesTable.$converterprogress.toSql(progress)); + } + map['started_at'] = Variable(startedAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + UserTaskProgressesCompanion toCompanion(bool nullToAbsent) { + return UserTaskProgressesCompanion( + id: Value(id), + userId: Value(userId), + taskId: Value(taskId), + progress: progress == null && nullToAbsent + ? const Value.absent() + : Value(progress), + startedAt: Value(startedAt), + updatedAt: Value(updatedAt), + ); + } + + factory UserTaskProgresses.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserTaskProgresses( + id: serializer.fromJson(json['id']), + userId: serializer.fromJson(json['userId']), + taskId: serializer.fromJson(json['taskId']), + progress: serializer.fromJson?>(json['progress']), + startedAt: serializer.fromJson(json['startedAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'userId': serializer.toJson(userId), + 'taskId': serializer.toJson(taskId), + 'progress': serializer.toJson?>(progress), + 'startedAt': serializer.toJson(startedAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + UserTaskProgresses copyWith( + {int? id, + int? userId, + int? taskId, + Value?> progress = const Value.absent(), + DateTime? startedAt, + DateTime? updatedAt}) => + UserTaskProgresses( + id: id ?? this.id, + userId: userId ?? this.userId, + taskId: taskId ?? this.taskId, + progress: progress.present ? progress.value : this.progress, + startedAt: startedAt ?? this.startedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + @override + String toString() { + return (StringBuffer('UserTaskProgresses(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('taskId: $taskId, ') + ..write('progress: $progress, ') + ..write('startedAt: $startedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, userId, taskId, progress, startedAt, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserTaskProgresses && + other.id == this.id && + other.userId == this.userId && + other.taskId == this.taskId && + other.progress == this.progress && + other.startedAt == this.startedAt && + other.updatedAt == this.updatedAt); +} + +class UserTaskProgressesCompanion extends UpdateCompanion { + final Value id; + final Value userId; + final Value taskId; + final Value?> progress; + final Value startedAt; + final Value updatedAt; + const UserTaskProgressesCompanion({ + this.id = const Value.absent(), + this.userId = const Value.absent(), + this.taskId = const Value.absent(), + this.progress = const Value.absent(), + this.startedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + UserTaskProgressesCompanion.insert({ + this.id = const Value.absent(), + required int userId, + required int taskId, + this.progress = const Value.absent(), + this.startedAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : userId = Value(userId), + taskId = Value(taskId); + static Insertable custom({ + Expression? id, + Expression? userId, + Expression? taskId, + Expression? progress, + Expression? startedAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (userId != null) 'user_id': userId, + if (taskId != null) 'task_id': taskId, + if (progress != null) 'progress': progress, + if (startedAt != null) 'started_at': startedAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + UserTaskProgressesCompanion copyWith( + {Value? id, + Value? userId, + Value? taskId, + Value?>? progress, + Value? startedAt, + Value? updatedAt}) { + return UserTaskProgressesCompanion( + id: id ?? this.id, + userId: userId ?? this.userId, + taskId: taskId ?? this.taskId, + progress: progress ?? this.progress, + startedAt: startedAt ?? this.startedAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (taskId.present) { + map['task_id'] = Variable(taskId.value); + } + if (progress.present) { + map['progress'] = Variable( + $UserTaskProgressesTable.$converterprogress.toSql(progress.value)); + } + if (startedAt.present) { + map['started_at'] = Variable(startedAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserTaskProgressesCompanion(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('taskId: $taskId, ') + ..write('progress: $progress, ') + ..write('startedAt: $startedAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $UserTaskResultsTable extends UserTaskResults + with TableInfo<$UserTaskResultsTable, UserTaskResult> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserTaskResultsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES users (id) ON DELETE CASCADE')); + static const VerificationMeta _taskIdMeta = const VerificationMeta('taskId'); + @override + late final GeneratedColumn taskId = GeneratedColumn( + 'task_id', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _resultsMeta = + const VerificationMeta('results'); + @override + late final GeneratedColumnWithTypeConverter?, String> + results = GeneratedColumn('results', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('{}')) + .withConverter?>( + $UserTaskResultsTable.$converterresults); + static const VerificationMeta _completedAtMeta = + const VerificationMeta('completedAt'); + @override + late final GeneratedColumn completedAt = GeneratedColumn( + 'completed_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => + [id, userId, taskId, results, completedAt, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_task_results'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('task_id')) { + context.handle(_taskIdMeta, + taskId.isAcceptableOrUnknown(data['task_id']!, _taskIdMeta)); + } else if (isInserting) { + context.missing(_taskIdMeta); + } + context.handle(_resultsMeta, const VerificationResult.success()); + if (data.containsKey('completed_at')) { + context.handle( + _completedAtMeta, + completedAt.isAcceptableOrUnknown( + data['completed_at']!, _completedAtMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + UserTaskResult map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserTaskResult( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + taskId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}task_id'])!, + results: $UserTaskResultsTable.$converterresults.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}results'])!), + completedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}completed_at'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + $UserTaskResultsTable createAlias(String alias) { + return $UserTaskResultsTable(attachedDatabase, alias); + } + + static TypeConverter?, String> $converterresults = + const JsonMapConverter(); +} + +class UserTaskResult extends DataClass implements Insertable { + final int id; + final int userId; + final int taskId; + final Map? results; + final DateTime completedAt; + final DateTime createdAt; + const UserTaskResult( + {required this.id, + required this.userId, + required this.taskId, + this.results, + required this.completedAt, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['user_id'] = Variable(userId); + map['task_id'] = Variable(taskId); + if (!nullToAbsent || results != null) { + map['results'] = Variable( + $UserTaskResultsTable.$converterresults.toSql(results)); + } + map['completed_at'] = Variable(completedAt); + map['created_at'] = Variable(createdAt); + return map; + } + + UserTaskResultsCompanion toCompanion(bool nullToAbsent) { + return UserTaskResultsCompanion( + id: Value(id), + userId: Value(userId), + taskId: Value(taskId), + results: results == null && nullToAbsent + ? const Value.absent() + : Value(results), + completedAt: Value(completedAt), + createdAt: Value(createdAt), + ); + } + + factory UserTaskResult.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserTaskResult( + id: serializer.fromJson(json['id']), + userId: serializer.fromJson(json['userId']), + taskId: serializer.fromJson(json['taskId']), + results: serializer.fromJson?>(json['results']), + completedAt: serializer.fromJson(json['completedAt']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'userId': serializer.toJson(userId), + 'taskId': serializer.toJson(taskId), + 'results': serializer.toJson?>(results), + 'completedAt': serializer.toJson(completedAt), + 'createdAt': serializer.toJson(createdAt), + }; + } + + UserTaskResult copyWith( + {int? id, + int? userId, + int? taskId, + Value?> results = const Value.absent(), + DateTime? completedAt, + DateTime? createdAt}) => + UserTaskResult( + id: id ?? this.id, + userId: userId ?? this.userId, + taskId: taskId ?? this.taskId, + results: results.present ? results.value : this.results, + completedAt: completedAt ?? this.completedAt, + createdAt: createdAt ?? this.createdAt, + ); + @override + String toString() { + return (StringBuffer('UserTaskResult(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('taskId: $taskId, ') + ..write('results: $results, ') + ..write('completedAt: $completedAt, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, userId, taskId, results, completedAt, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserTaskResult && + other.id == this.id && + other.userId == this.userId && + other.taskId == this.taskId && + other.results == this.results && + other.completedAt == this.completedAt && + other.createdAt == this.createdAt); +} + +class UserTaskResultsCompanion extends UpdateCompanion { + final Value id; + final Value userId; + final Value taskId; + final Value?> results; + final Value completedAt; + final Value createdAt; + const UserTaskResultsCompanion({ + this.id = const Value.absent(), + this.userId = const Value.absent(), + this.taskId = const Value.absent(), + this.results = const Value.absent(), + this.completedAt = const Value.absent(), + this.createdAt = const Value.absent(), + }); + UserTaskResultsCompanion.insert({ + this.id = const Value.absent(), + required int userId, + required int taskId, + this.results = const Value.absent(), + this.completedAt = const Value.absent(), + this.createdAt = const Value.absent(), + }) : userId = Value(userId), + taskId = Value(taskId); + static Insertable custom({ + Expression? id, + Expression? userId, + Expression? taskId, + Expression? results, + Expression? completedAt, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (userId != null) 'user_id': userId, + if (taskId != null) 'task_id': taskId, + if (results != null) 'results': results, + if (completedAt != null) 'completed_at': completedAt, + if (createdAt != null) 'created_at': createdAt, + }); + } + + UserTaskResultsCompanion copyWith( + {Value? id, + Value? userId, + Value? taskId, + Value?>? results, + Value? completedAt, + Value? createdAt}) { + return UserTaskResultsCompanion( + id: id ?? this.id, + userId: userId ?? this.userId, + taskId: taskId ?? this.taskId, + results: results ?? this.results, + completedAt: completedAt ?? this.completedAt, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (taskId.present) { + map['task_id'] = Variable(taskId.value); + } + if (results.present) { + map['results'] = Variable( + $UserTaskResultsTable.$converterresults.toSql(results.value)); + } + if (completedAt.present) { + map['completed_at'] = Variable(completedAt.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserTaskResultsCompanion(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('taskId: $taskId, ') + ..write('results: $results, ') + ..write('completedAt: $completedAt, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class $PromoCodesCampaignsTable extends PromoCodesCampaigns + with TableInfo<$PromoCodesCampaignsTable, PromoCodesCampaign> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $PromoCodesCampaignsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _templateMeta = + const VerificationMeta('template'); + @override + late final GeneratedColumn template = GeneratedColumn( + 'template', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _productsMeta = + const VerificationMeta('products'); + @override + late final GeneratedColumnWithTypeConverter?, String> products = + GeneratedColumn('products', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter?>( + $PromoCodesCampaignsTable.$converterproducts); + static const VerificationMeta _activationsPerCodeMeta = + const VerificationMeta('activationsPerCode'); + @override + late final GeneratedColumn activationsPerCode = GeneratedColumn( + 'activations_per_code', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _activationsPerUserMeta = + const VerificationMeta('activationsPerUser'); + @override + late final GeneratedColumn activationsPerUser = GeneratedColumn( + 'activations_per_user', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _generationSizeMeta = + const VerificationMeta('generationSize'); + @override + late final GeneratedColumn generationSize = GeneratedColumn( + 'generation_size', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + static const VerificationMeta _startMeta = const VerificationMeta('start'); + @override + late final GeneratedColumn start = GeneratedColumn( + 'start', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _finishMeta = const VerificationMeta('finish'); + @override + late final GeneratedColumn finish = GeneratedColumn( + 'finish', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _statusMeta = const VerificationMeta('status'); + @override + late final GeneratedColumn status = GeneratedColumn( + 'status', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _tagsMeta = const VerificationMeta('tags'); + @override + late final GeneratedColumnWithTypeConverter, String> tags = + GeneratedColumn('tags', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter>( + $PromoCodesCampaignsTable.$convertertags); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _isDeletedMeta = + const VerificationMeta('isDeleted'); + @override + late final GeneratedColumn isDeleted = GeneratedColumn( + 'is_deleted', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_deleted" IN (0, 1))'), + defaultValue: const Constant(false)); + @override + List get $columns => [ + id, + template, + name, + products, + activationsPerCode, + activationsPerUser, + generationSize, + start, + finish, + status, + tags, + createdAt, + updatedAt, + isDeleted + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'promo_codes_campaigns'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('template')) { + context.handle(_templateMeta, + template.isAcceptableOrUnknown(data['template']!, _templateMeta)); + } else if (isInserting) { + context.missing(_templateMeta); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } + context.handle(_productsMeta, const VerificationResult.success()); + if (data.containsKey('activations_per_code')) { + context.handle( + _activationsPerCodeMeta, + activationsPerCode.isAcceptableOrUnknown( + data['activations_per_code']!, _activationsPerCodeMeta)); + } else if (isInserting) { + context.missing(_activationsPerCodeMeta); + } + if (data.containsKey('activations_per_user')) { + context.handle( + _activationsPerUserMeta, + activationsPerUser.isAcceptableOrUnknown( + data['activations_per_user']!, _activationsPerUserMeta)); + } else if (isInserting) { + context.missing(_activationsPerUserMeta); + } + if (data.containsKey('generation_size')) { + context.handle( + _generationSizeMeta, + generationSize.isAcceptableOrUnknown( + data['generation_size']!, _generationSizeMeta)); + } else if (isInserting) { + context.missing(_generationSizeMeta); + } + if (data.containsKey('start')) { + context.handle( + _startMeta, start.isAcceptableOrUnknown(data['start']!, _startMeta)); + } else if (isInserting) { + context.missing(_startMeta); + } + if (data.containsKey('finish')) { + context.handle(_finishMeta, + finish.isAcceptableOrUnknown(data['finish']!, _finishMeta)); + } else if (isInserting) { + context.missing(_finishMeta); + } + if (data.containsKey('status')) { + context.handle(_statusMeta, + status.isAcceptableOrUnknown(data['status']!, _statusMeta)); + } else if (isInserting) { + context.missing(_statusMeta); + } + context.handle(_tagsMeta, const VerificationResult.success()); + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + if (data.containsKey('is_deleted')) { + context.handle(_isDeletedMeta, + isDeleted.isAcceptableOrUnknown(data['is_deleted']!, _isDeletedMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + PromoCodesCampaign map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PromoCodesCampaign( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + template: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}template'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name']), + products: $PromoCodesCampaignsTable.$converterproducts.fromSql( + attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}products'])!), + activationsPerCode: attachedDatabase.typeMapping.read( + DriftSqlType.int, data['${effectivePrefix}activations_per_code'])!, + activationsPerUser: attachedDatabase.typeMapping.read( + DriftSqlType.int, data['${effectivePrefix}activations_per_user'])!, + generationSize: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}generation_size'])!, + start: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}start'])!, + finish: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}finish'])!, + status: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}status'])!, + tags: $PromoCodesCampaignsTable.$convertertags.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}tags'])!), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + isDeleted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_deleted'])!, + ); + } + + @override + $PromoCodesCampaignsTable createAlias(String alias) { + return $PromoCodesCampaignsTable(attachedDatabase, alias); + } + + static TypeConverter?, String> $converterproducts = + const JsonListConverter(); + static TypeConverter, String> $convertertags = + const StringListConverter(); +} + +class PromoCodesCampaign extends DataClass + implements Insertable { + final int id; + final String template; + final String? name; + final List? products; + final int activationsPerCode; + final int activationsPerUser; + final int generationSize; + final DateTime start; + final DateTime finish; + final String status; + final List tags; + final DateTime createdAt; + final DateTime updatedAt; + final bool isDeleted; + const PromoCodesCampaign( + {required this.id, + required this.template, + this.name, + this.products, + required this.activationsPerCode, + required this.activationsPerUser, + required this.generationSize, + required this.start, + required this.finish, + required this.status, + required this.tags, + required this.createdAt, + required this.updatedAt, + required this.isDeleted}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['template'] = Variable(template); + if (!nullToAbsent || name != null) { + map['name'] = Variable(name); + } + if (!nullToAbsent || products != null) { + map['products'] = Variable( + $PromoCodesCampaignsTable.$converterproducts.toSql(products)); + } + map['activations_per_code'] = Variable(activationsPerCode); + map['activations_per_user'] = Variable(activationsPerUser); + map['generation_size'] = Variable(generationSize); + map['start'] = Variable(start); + map['finish'] = Variable(finish); + map['status'] = Variable(status); + { + map['tags'] = Variable( + $PromoCodesCampaignsTable.$convertertags.toSql(tags)); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['is_deleted'] = Variable(isDeleted); + return map; + } + + PromoCodesCampaignsCompanion toCompanion(bool nullToAbsent) { + return PromoCodesCampaignsCompanion( + id: Value(id), + template: Value(template), + name: name == null && nullToAbsent ? const Value.absent() : Value(name), + products: products == null && nullToAbsent + ? const Value.absent() + : Value(products), + activationsPerCode: Value(activationsPerCode), + activationsPerUser: Value(activationsPerUser), + generationSize: Value(generationSize), + start: Value(start), + finish: Value(finish), + status: Value(status), + tags: Value(tags), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + isDeleted: Value(isDeleted), + ); + } + + factory PromoCodesCampaign.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PromoCodesCampaign( + id: serializer.fromJson(json['id']), + template: serializer.fromJson(json['template']), + name: serializer.fromJson(json['name']), + products: serializer.fromJson?>(json['products']), + activationsPerCode: serializer.fromJson(json['activationsPerCode']), + activationsPerUser: serializer.fromJson(json['activationsPerUser']), + generationSize: serializer.fromJson(json['generationSize']), + start: serializer.fromJson(json['start']), + finish: serializer.fromJson(json['finish']), + status: serializer.fromJson(json['status']), + tags: serializer.fromJson>(json['tags']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + isDeleted: serializer.fromJson(json['isDeleted']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'template': serializer.toJson(template), + 'name': serializer.toJson(name), + 'products': serializer.toJson?>(products), + 'activationsPerCode': serializer.toJson(activationsPerCode), + 'activationsPerUser': serializer.toJson(activationsPerUser), + 'generationSize': serializer.toJson(generationSize), + 'start': serializer.toJson(start), + 'finish': serializer.toJson(finish), + 'status': serializer.toJson(status), + 'tags': serializer.toJson>(tags), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'isDeleted': serializer.toJson(isDeleted), + }; + } + + PromoCodesCampaign copyWith( + {int? id, + String? template, + Value name = const Value.absent(), + Value?> products = const Value.absent(), + int? activationsPerCode, + int? activationsPerUser, + int? generationSize, + DateTime? start, + DateTime? finish, + String? status, + List? tags, + DateTime? createdAt, + DateTime? updatedAt, + bool? isDeleted}) => + PromoCodesCampaign( + id: id ?? this.id, + template: template ?? this.template, + name: name.present ? name.value : this.name, + products: products.present ? products.value : this.products, + activationsPerCode: activationsPerCode ?? this.activationsPerCode, + activationsPerUser: activationsPerUser ?? this.activationsPerUser, + generationSize: generationSize ?? this.generationSize, + start: start ?? this.start, + finish: finish ?? this.finish, + status: status ?? this.status, + tags: tags ?? this.tags, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + @override + String toString() { + return (StringBuffer('PromoCodesCampaign(') + ..write('id: $id, ') + ..write('template: $template, ') + ..write('name: $name, ') + ..write('products: $products, ') + ..write('activationsPerCode: $activationsPerCode, ') + ..write('activationsPerUser: $activationsPerUser, ') + ..write('generationSize: $generationSize, ') + ..write('start: $start, ') + ..write('finish: $finish, ') + ..write('status: $status, ') + ..write('tags: $tags, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + template, + name, + products, + activationsPerCode, + activationsPerUser, + generationSize, + start, + finish, + status, + tags, + createdAt, + updatedAt, + isDeleted); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PromoCodesCampaign && + other.id == this.id && + other.template == this.template && + other.name == this.name && + other.products == this.products && + other.activationsPerCode == this.activationsPerCode && + other.activationsPerUser == this.activationsPerUser && + other.generationSize == this.generationSize && + other.start == this.start && + other.finish == this.finish && + other.status == this.status && + other.tags == this.tags && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.isDeleted == this.isDeleted); +} + +class PromoCodesCampaignsCompanion extends UpdateCompanion { + final Value id; + final Value template; + final Value name; + final Value?> products; + final Value activationsPerCode; + final Value activationsPerUser; + final Value generationSize; + final Value start; + final Value finish; + final Value status; + final Value> tags; + final Value createdAt; + final Value updatedAt; + final Value isDeleted; + const PromoCodesCampaignsCompanion({ + this.id = const Value.absent(), + this.template = const Value.absent(), + this.name = const Value.absent(), + this.products = const Value.absent(), + this.activationsPerCode = const Value.absent(), + this.activationsPerUser = const Value.absent(), + this.generationSize = const Value.absent(), + this.start = const Value.absent(), + this.finish = const Value.absent(), + this.status = const Value.absent(), + this.tags = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }); + PromoCodesCampaignsCompanion.insert({ + this.id = const Value.absent(), + required String template, + this.name = const Value.absent(), + this.products = const Value.absent(), + required int activationsPerCode, + required int activationsPerUser, + required int generationSize, + required DateTime start, + required DateTime finish, + required String status, + this.tags = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }) : template = Value(template), + activationsPerCode = Value(activationsPerCode), + activationsPerUser = Value(activationsPerUser), + generationSize = Value(generationSize), + start = Value(start), + finish = Value(finish), + status = Value(status); + static Insertable custom({ + Expression? id, + Expression? template, + Expression? name, + Expression? products, + Expression? activationsPerCode, + Expression? activationsPerUser, + Expression? generationSize, + Expression? start, + Expression? finish, + Expression? status, + Expression? tags, + Expression? createdAt, + Expression? updatedAt, + Expression? isDeleted, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (template != null) 'template': template, + if (name != null) 'name': name, + if (products != null) 'products': products, + if (activationsPerCode != null) + 'activations_per_code': activationsPerCode, + if (activationsPerUser != null) + 'activations_per_user': activationsPerUser, + if (generationSize != null) 'generation_size': generationSize, + if (start != null) 'start': start, + if (finish != null) 'finish': finish, + if (status != null) 'status': status, + if (tags != null) 'tags': tags, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (isDeleted != null) 'is_deleted': isDeleted, + }); + } + + PromoCodesCampaignsCompanion copyWith( + {Value? id, + Value? template, + Value? name, + Value?>? products, + Value? activationsPerCode, + Value? activationsPerUser, + Value? generationSize, + Value? start, + Value? finish, + Value? status, + Value>? tags, + Value? createdAt, + Value? updatedAt, + Value? isDeleted}) { + return PromoCodesCampaignsCompanion( + id: id ?? this.id, + template: template ?? this.template, + name: name ?? this.name, + products: products ?? this.products, + activationsPerCode: activationsPerCode ?? this.activationsPerCode, + activationsPerUser: activationsPerUser ?? this.activationsPerUser, + generationSize: generationSize ?? this.generationSize, + start: start ?? this.start, + finish: finish ?? this.finish, + status: status ?? this.status, + tags: tags ?? this.tags, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (template.present) { + map['template'] = Variable(template.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (products.present) { + map['products'] = Variable( + $PromoCodesCampaignsTable.$converterproducts.toSql(products.value)); + } + if (activationsPerCode.present) { + map['activations_per_code'] = Variable(activationsPerCode.value); + } + if (activationsPerUser.present) { + map['activations_per_user'] = Variable(activationsPerUser.value); + } + if (generationSize.present) { + map['generation_size'] = Variable(generationSize.value); + } + if (start.present) { + map['start'] = Variable(start.value); + } + if (finish.present) { + map['finish'] = Variable(finish.value); + } + if (status.present) { + map['status'] = Variable(status.value); + } + if (tags.present) { + map['tags'] = Variable( + $PromoCodesCampaignsTable.$convertertags.toSql(tags.value)); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (isDeleted.present) { + map['is_deleted'] = Variable(isDeleted.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PromoCodesCampaignsCompanion(') + ..write('id: $id, ') + ..write('template: $template, ') + ..write('name: $name, ') + ..write('products: $products, ') + ..write('activationsPerCode: $activationsPerCode, ') + ..write('activationsPerUser: $activationsPerUser, ') + ..write('generationSize: $generationSize, ') + ..write('start: $start, ') + ..write('finish: $finish, ') + ..write('status: $status, ') + ..write('tags: $tags, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } +} + +class $PromoCodesTable extends PromoCodes + with TableInfo<$PromoCodesTable, PromoCode> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $PromoCodesTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _campaignIdMeta = + const VerificationMeta('campaignId'); + @override + late final GeneratedColumn campaignId = GeneratedColumn( + 'campaign_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES promo_codes_campaigns (id) ON DELETE CASCADE')); + static const VerificationMeta _codeMeta = const VerificationMeta('code'); + @override + late final GeneratedColumn code = GeneratedColumn( + 'code', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); + static const VerificationMeta _activationsMeta = + const VerificationMeta('activations'); + @override + late final GeneratedColumn activations = GeneratedColumn( + 'activations', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES users (id) ON DELETE CASCADE')); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => + [id, campaignId, code, activations, userId, createdAt, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'promo_codes'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('campaign_id')) { + context.handle( + _campaignIdMeta, + campaignId.isAcceptableOrUnknown( + data['campaign_id']!, _campaignIdMeta)); + } else if (isInserting) { + context.missing(_campaignIdMeta); + } + if (data.containsKey('code')) { + context.handle( + _codeMeta, code.isAcceptableOrUnknown(data['code']!, _codeMeta)); + } else if (isInserting) { + context.missing(_codeMeta); + } + if (data.containsKey('activations')) { + context.handle( + _activationsMeta, + activations.isAcceptableOrUnknown( + data['activations']!, _activationsMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + PromoCode map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PromoCode( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + campaignId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}campaign_id'])!, + code: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}code'])!, + activations: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}activations'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $PromoCodesTable createAlias(String alias) { + return $PromoCodesTable(attachedDatabase, alias); + } +} + +class PromoCode extends DataClass implements Insertable { + final int id; + final int campaignId; + final String code; + final int activations; + final int? userId; + final DateTime createdAt; + final DateTime updatedAt; + const PromoCode( + {required this.id, + required this.campaignId, + required this.code, + required this.activations, + this.userId, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['campaign_id'] = Variable(campaignId); + map['code'] = Variable(code); + map['activations'] = Variable(activations); + if (!nullToAbsent || userId != null) { + map['user_id'] = Variable(userId); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + PromoCodesCompanion toCompanion(bool nullToAbsent) { + return PromoCodesCompanion( + id: Value(id), + campaignId: Value(campaignId), + code: Value(code), + activations: Value(activations), + userId: + userId == null && nullToAbsent ? const Value.absent() : Value(userId), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory PromoCode.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PromoCode( + id: serializer.fromJson(json['id']), + campaignId: serializer.fromJson(json['campaignId']), + code: serializer.fromJson(json['code']), + activations: serializer.fromJson(json['activations']), + userId: serializer.fromJson(json['userId']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'campaignId': serializer.toJson(campaignId), + 'code': serializer.toJson(code), + 'activations': serializer.toJson(activations), + 'userId': serializer.toJson(userId), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + PromoCode copyWith( + {int? id, + int? campaignId, + String? code, + int? activations, + Value userId = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt}) => + PromoCode( + id: id ?? this.id, + campaignId: campaignId ?? this.campaignId, + code: code ?? this.code, + activations: activations ?? this.activations, + userId: userId.present ? userId.value : this.userId, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + @override + String toString() { + return (StringBuffer('PromoCode(') + ..write('id: $id, ') + ..write('campaignId: $campaignId, ') + ..write('code: $code, ') + ..write('activations: $activations, ') + ..write('userId: $userId, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, campaignId, code, activations, userId, createdAt, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PromoCode && + other.id == this.id && + other.campaignId == this.campaignId && + other.code == this.code && + other.activations == this.activations && + other.userId == this.userId && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class PromoCodesCompanion extends UpdateCompanion { + final Value id; + final Value campaignId; + final Value code; + final Value activations; + final Value userId; + final Value createdAt; + final Value updatedAt; + const PromoCodesCompanion({ + this.id = const Value.absent(), + this.campaignId = const Value.absent(), + this.code = const Value.absent(), + this.activations = const Value.absent(), + this.userId = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + PromoCodesCompanion.insert({ + this.id = const Value.absent(), + required int campaignId, + required String code, + this.activations = const Value.absent(), + this.userId = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : campaignId = Value(campaignId), + code = Value(code); + static Insertable custom({ + Expression? id, + Expression? campaignId, + Expression? code, + Expression? activations, + Expression? userId, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (campaignId != null) 'campaign_id': campaignId, + if (code != null) 'code': code, + if (activations != null) 'activations': activations, + if (userId != null) 'user_id': userId, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + PromoCodesCompanion copyWith( + {Value? id, + Value? campaignId, + Value? code, + Value? activations, + Value? userId, + Value? createdAt, + Value? updatedAt}) { + return PromoCodesCompanion( + id: id ?? this.id, + campaignId: campaignId ?? this.campaignId, + code: code ?? this.code, + activations: activations ?? this.activations, + userId: userId ?? this.userId, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (campaignId.present) { + map['campaign_id'] = Variable(campaignId.value); + } + if (code.present) { + map['code'] = Variable(code.value); + } + if (activations.present) { + map['activations'] = Variable(activations.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PromoCodesCompanion(') + ..write('id: $id, ') + ..write('campaignId: $campaignId, ') + ..write('code: $code, ') + ..write('activations: $activations, ') + ..write('userId: $userId, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $DiscountCampaignsTable extends DiscountCampaigns + with TableInfo<$DiscountCampaignsTable, DiscountCampaign> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $DiscountCampaignsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _startMeta = const VerificationMeta('start'); + @override + late final GeneratedColumn start = GeneratedColumn( + 'start', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _finishMeta = const VerificationMeta('finish'); + @override + late final GeneratedColumn finish = GeneratedColumn( + 'finish', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _statusMeta = const VerificationMeta('status'); + @override + late final GeneratedColumn status = GeneratedColumn( + 'status', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _tagsMeta = const VerificationMeta('tags'); + @override + late final GeneratedColumnWithTypeConverter, String> tags = + GeneratedColumn('tags', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter>($DiscountCampaignsTable.$convertertags); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _isDeletedMeta = + const VerificationMeta('isDeleted'); + @override + late final GeneratedColumn isDeleted = GeneratedColumn( + 'is_deleted', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_deleted" IN (0, 1))'), + defaultValue: const Constant(false)); + @override + List get $columns => + [id, name, start, finish, status, tags, createdAt, updatedAt, isDeleted]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'discount_campaigns'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } + if (data.containsKey('start')) { + context.handle( + _startMeta, start.isAcceptableOrUnknown(data['start']!, _startMeta)); + } else if (isInserting) { + context.missing(_startMeta); + } + if (data.containsKey('finish')) { + context.handle(_finishMeta, + finish.isAcceptableOrUnknown(data['finish']!, _finishMeta)); + } else if (isInserting) { + context.missing(_finishMeta); + } + if (data.containsKey('status')) { + context.handle(_statusMeta, + status.isAcceptableOrUnknown(data['status']!, _statusMeta)); + } else if (isInserting) { + context.missing(_statusMeta); + } + context.handle(_tagsMeta, const VerificationResult.success()); + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + if (data.containsKey('is_deleted')) { + context.handle(_isDeletedMeta, + isDeleted.isAcceptableOrUnknown(data['is_deleted']!, _isDeletedMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + DiscountCampaign map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return DiscountCampaign( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name']), + start: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}start'])!, + finish: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}finish'])!, + status: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}status'])!, + tags: $DiscountCampaignsTable.$convertertags.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}tags'])!), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + isDeleted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_deleted'])!, + ); + } + + @override + $DiscountCampaignsTable createAlias(String alias) { + return $DiscountCampaignsTable(attachedDatabase, alias); + } + + static TypeConverter, String> $convertertags = + const StringListConverter(); +} + +class DiscountCampaign extends DataClass + implements Insertable { + final int id; + final String? name; + final DateTime start; + final DateTime finish; + final String status; + final List tags; + final DateTime createdAt; + final DateTime updatedAt; + final bool isDeleted; + const DiscountCampaign( + {required this.id, + this.name, + required this.start, + required this.finish, + required this.status, + required this.tags, + required this.createdAt, + required this.updatedAt, + required this.isDeleted}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + if (!nullToAbsent || name != null) { + map['name'] = Variable(name); + } + map['start'] = Variable(start); + map['finish'] = Variable(finish); + map['status'] = Variable(status); + { + map['tags'] = + Variable($DiscountCampaignsTable.$convertertags.toSql(tags)); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['is_deleted'] = Variable(isDeleted); + return map; + } + + DiscountCampaignsCompanion toCompanion(bool nullToAbsent) { + return DiscountCampaignsCompanion( + id: Value(id), + name: name == null && nullToAbsent ? const Value.absent() : Value(name), + start: Value(start), + finish: Value(finish), + status: Value(status), + tags: Value(tags), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + isDeleted: Value(isDeleted), + ); + } + + factory DiscountCampaign.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return DiscountCampaign( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + start: serializer.fromJson(json['start']), + finish: serializer.fromJson(json['finish']), + status: serializer.fromJson(json['status']), + tags: serializer.fromJson>(json['tags']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + isDeleted: serializer.fromJson(json['isDeleted']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'start': serializer.toJson(start), + 'finish': serializer.toJson(finish), + 'status': serializer.toJson(status), + 'tags': serializer.toJson>(tags), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'isDeleted': serializer.toJson(isDeleted), + }; + } + + DiscountCampaign copyWith( + {int? id, + Value name = const Value.absent(), + DateTime? start, + DateTime? finish, + String? status, + List? tags, + DateTime? createdAt, + DateTime? updatedAt, + bool? isDeleted}) => + DiscountCampaign( + id: id ?? this.id, + name: name.present ? name.value : this.name, + start: start ?? this.start, + finish: finish ?? this.finish, + status: status ?? this.status, + tags: tags ?? this.tags, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + @override + String toString() { + return (StringBuffer('DiscountCampaign(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('start: $start, ') + ..write('finish: $finish, ') + ..write('status: $status, ') + ..write('tags: $tags, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, name, start, finish, status, tags, createdAt, updatedAt, isDeleted); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is DiscountCampaign && + other.id == this.id && + other.name == this.name && + other.start == this.start && + other.finish == this.finish && + other.status == this.status && + other.tags == this.tags && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.isDeleted == this.isDeleted); +} + +class DiscountCampaignsCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value start; + final Value finish; + final Value status; + final Value> tags; + final Value createdAt; + final Value updatedAt; + final Value isDeleted; + const DiscountCampaignsCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.start = const Value.absent(), + this.finish = const Value.absent(), + this.status = const Value.absent(), + this.tags = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }); + DiscountCampaignsCompanion.insert({ + this.id = const Value.absent(), + this.name = const Value.absent(), + required DateTime start, + required DateTime finish, + required String status, + this.tags = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }) : start = Value(start), + finish = Value(finish), + status = Value(status); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? start, + Expression? finish, + Expression? status, + Expression? tags, + Expression? createdAt, + Expression? updatedAt, + Expression? isDeleted, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (start != null) 'start': start, + if (finish != null) 'finish': finish, + if (status != null) 'status': status, + if (tags != null) 'tags': tags, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (isDeleted != null) 'is_deleted': isDeleted, + }); + } + + DiscountCampaignsCompanion copyWith( + {Value? id, + Value? name, + Value? start, + Value? finish, + Value? status, + Value>? tags, + Value? createdAt, + Value? updatedAt, + Value? isDeleted}) { + return DiscountCampaignsCompanion( + id: id ?? this.id, + name: name ?? this.name, + start: start ?? this.start, + finish: finish ?? this.finish, + status: status ?? this.status, + tags: tags ?? this.tags, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (start.present) { + map['start'] = Variable(start.value); + } + if (finish.present) { + map['finish'] = Variable(finish.value); + } + if (status.present) { + map['status'] = Variable(status.value); + } + if (tags.present) { + map['tags'] = Variable( + $DiscountCampaignsTable.$convertertags.toSql(tags.value)); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (isDeleted.present) { + map['is_deleted'] = Variable(isDeleted.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DiscountCampaignsCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('start: $start, ') + ..write('finish: $finish, ') + ..write('status: $status, ') + ..write('tags: $tags, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } +} + +class $DiscountsTable extends Discounts + with TableInfo<$DiscountsTable, Discount> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $DiscountsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _campaignIdMeta = + const VerificationMeta('campaignId'); + @override + late final GeneratedColumn campaignId = GeneratedColumn( + 'campaign_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES discount_campaigns (id) ON DELETE CASCADE')); + static const VerificationMeta _discountPercentMeta = + const VerificationMeta('discountPercent'); + @override + late final GeneratedColumn discountPercent = GeneratedColumn( + 'discount_percent', aliasedName, false, + type: DriftSqlType.double, requiredDuringInsert: true); + static const VerificationMeta _productsMeta = + const VerificationMeta('products'); + @override + late final GeneratedColumnWithTypeConverter?, String> products = + GeneratedColumn('products', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant('[]')) + .withConverter?>($DiscountsTable.$converterproducts); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _isDeletedMeta = + const VerificationMeta('isDeleted'); + @override + late final GeneratedColumn isDeleted = GeneratedColumn( + 'is_deleted', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("is_deleted" IN (0, 1))'), + defaultValue: const Constant(false)); + @override + List get $columns => [ + id, + campaignId, + discountPercent, + products, + createdAt, + updatedAt, + isDeleted + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'discounts'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('campaign_id')) { + context.handle( + _campaignIdMeta, + campaignId.isAcceptableOrUnknown( + data['campaign_id']!, _campaignIdMeta)); + } else if (isInserting) { + context.missing(_campaignIdMeta); + } + if (data.containsKey('discount_percent')) { + context.handle( + _discountPercentMeta, + discountPercent.isAcceptableOrUnknown( + data['discount_percent']!, _discountPercentMeta)); + } else if (isInserting) { + context.missing(_discountPercentMeta); + } + context.handle(_productsMeta, const VerificationResult.success()); + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + if (data.containsKey('is_deleted')) { + context.handle(_isDeletedMeta, + isDeleted.isAcceptableOrUnknown(data['is_deleted']!, _isDeletedMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + Discount map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return Discount( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + campaignId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}campaign_id'])!, + discountPercent: attachedDatabase.typeMapping.read( + DriftSqlType.double, data['${effectivePrefix}discount_percent'])!, + products: $DiscountsTable.$converterproducts.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}products'])!), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + isDeleted: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}is_deleted'])!, + ); + } + + @override + $DiscountsTable createAlias(String alias) { + return $DiscountsTable(attachedDatabase, alias); + } + + static TypeConverter?, String> $converterproducts = + const JsonListConverter(); +} + +class Discount extends DataClass implements Insertable { + final int id; + final int campaignId; + final double discountPercent; + final List? products; + final DateTime createdAt; + final DateTime updatedAt; + final bool isDeleted; + const Discount( + {required this.id, + required this.campaignId, + required this.discountPercent, + this.products, + required this.createdAt, + required this.updatedAt, + required this.isDeleted}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['campaign_id'] = Variable(campaignId); + map['discount_percent'] = Variable(discountPercent); + if (!nullToAbsent || products != null) { + map['products'] = + Variable($DiscountsTable.$converterproducts.toSql(products)); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + map['is_deleted'] = Variable(isDeleted); + return map; + } + + DiscountsCompanion toCompanion(bool nullToAbsent) { + return DiscountsCompanion( + id: Value(id), + campaignId: Value(campaignId), + discountPercent: Value(discountPercent), + products: products == null && nullToAbsent + ? const Value.absent() + : Value(products), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + isDeleted: Value(isDeleted), + ); + } + + factory Discount.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return Discount( + id: serializer.fromJson(json['id']), + campaignId: serializer.fromJson(json['campaignId']), + discountPercent: serializer.fromJson(json['discountPercent']), + products: serializer.fromJson?>(json['products']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + isDeleted: serializer.fromJson(json['isDeleted']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'campaignId': serializer.toJson(campaignId), + 'discountPercent': serializer.toJson(discountPercent), + 'products': serializer.toJson?>(products), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + 'isDeleted': serializer.toJson(isDeleted), + }; + } + + Discount copyWith( + {int? id, + int? campaignId, + double? discountPercent, + Value?> products = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt, + bool? isDeleted}) => + Discount( + id: id ?? this.id, + campaignId: campaignId ?? this.campaignId, + discountPercent: discountPercent ?? this.discountPercent, + products: products.present ? products.value : this.products, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + @override + String toString() { + return (StringBuffer('Discount(') + ..write('id: $id, ') + ..write('campaignId: $campaignId, ') + ..write('discountPercent: $discountPercent, ') + ..write('products: $products, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, campaignId, discountPercent, products, + createdAt, updatedAt, isDeleted); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is Discount && + other.id == this.id && + other.campaignId == this.campaignId && + other.discountPercent == this.discountPercent && + other.products == this.products && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt && + other.isDeleted == this.isDeleted); +} + +class DiscountsCompanion extends UpdateCompanion { + final Value id; + final Value campaignId; + final Value discountPercent; + final Value?> products; + final Value createdAt; + final Value updatedAt; + final Value isDeleted; + const DiscountsCompanion({ + this.id = const Value.absent(), + this.campaignId = const Value.absent(), + this.discountPercent = const Value.absent(), + this.products = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }); + DiscountsCompanion.insert({ + this.id = const Value.absent(), + required int campaignId, + required double discountPercent, + this.products = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + this.isDeleted = const Value.absent(), + }) : campaignId = Value(campaignId), + discountPercent = Value(discountPercent); + static Insertable custom({ + Expression? id, + Expression? campaignId, + Expression? discountPercent, + Expression? products, + Expression? createdAt, + Expression? updatedAt, + Expression? isDeleted, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (campaignId != null) 'campaign_id': campaignId, + if (discountPercent != null) 'discount_percent': discountPercent, + if (products != null) 'products': products, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + if (isDeleted != null) 'is_deleted': isDeleted, + }); + } + + DiscountsCompanion copyWith( + {Value? id, + Value? campaignId, + Value? discountPercent, + Value?>? products, + Value? createdAt, + Value? updatedAt, + Value? isDeleted}) { + return DiscountsCompanion( + id: id ?? this.id, + campaignId: campaignId ?? this.campaignId, + discountPercent: discountPercent ?? this.discountPercent, + products: products ?? this.products, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + isDeleted: isDeleted ?? this.isDeleted, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (campaignId.present) { + map['campaign_id'] = Variable(campaignId.value); + } + if (discountPercent.present) { + map['discount_percent'] = Variable(discountPercent.value); + } + if (products.present) { + map['products'] = Variable( + $DiscountsTable.$converterproducts.toSql(products.value)); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + if (isDeleted.present) { + map['is_deleted'] = Variable(isDeleted.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DiscountsCompanion(') + ..write('id: $id, ') + ..write('campaignId: $campaignId, ') + ..write('discountPercent: $discountPercent, ') + ..write('products: $products, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt, ') + ..write('isDeleted: $isDeleted') + ..write(')')) + .toString(); + } +} + +class $DiscountUserDatasTable extends DiscountUserDatas + with TableInfo<$DiscountUserDatasTable, DiscountUserData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $DiscountUserDatasTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _discountIdMeta = + const VerificationMeta('discountId'); + @override + late final GeneratedColumn discountId = GeneratedColumn( + 'discount_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES discounts (id) ON DELETE CASCADE')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES users (id) ON DELETE CASCADE')); + static const VerificationMeta _grantedAtMeta = + const VerificationMeta('grantedAt'); + @override + late final GeneratedColumn grantedAt = GeneratedColumn( + 'granted_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [discountId, userId, grantedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'discount_user_datas'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('discount_id')) { + context.handle( + _discountIdMeta, + discountId.isAcceptableOrUnknown( + data['discount_id']!, _discountIdMeta)); + } else if (isInserting) { + context.missing(_discountIdMeta); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('granted_at')) { + context.handle(_grantedAtMeta, + grantedAt.isAcceptableOrUnknown(data['granted_at']!, _grantedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {discountId, userId}; + @override + DiscountUserData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return DiscountUserData( + discountId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}discount_id'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + grantedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}granted_at'])!, + ); + } + + @override + $DiscountUserDatasTable createAlias(String alias) { + return $DiscountUserDatasTable(attachedDatabase, alias); + } +} + +class DiscountUserData extends DataClass + implements Insertable { + final int discountId; + final int userId; + final DateTime grantedAt; + const DiscountUserData( + {required this.discountId, + required this.userId, + required this.grantedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['discount_id'] = Variable(discountId); + map['user_id'] = Variable(userId); + map['granted_at'] = Variable(grantedAt); + return map; + } + + DiscountUserDatasCompanion toCompanion(bool nullToAbsent) { + return DiscountUserDatasCompanion( + discountId: Value(discountId), + userId: Value(userId), + grantedAt: Value(grantedAt), + ); + } + + factory DiscountUserData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return DiscountUserData( + discountId: serializer.fromJson(json['discountId']), + userId: serializer.fromJson(json['userId']), + grantedAt: serializer.fromJson(json['grantedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'discountId': serializer.toJson(discountId), + 'userId': serializer.toJson(userId), + 'grantedAt': serializer.toJson(grantedAt), + }; + } + + DiscountUserData copyWith( + {int? discountId, int? userId, DateTime? grantedAt}) => + DiscountUserData( + discountId: discountId ?? this.discountId, + userId: userId ?? this.userId, + grantedAt: grantedAt ?? this.grantedAt, + ); + @override + String toString() { + return (StringBuffer('DiscountUserData(') + ..write('discountId: $discountId, ') + ..write('userId: $userId, ') + ..write('grantedAt: $grantedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(discountId, userId, grantedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is DiscountUserData && + other.discountId == this.discountId && + other.userId == this.userId && + other.grantedAt == this.grantedAt); +} + +class DiscountUserDatasCompanion extends UpdateCompanion { + final Value discountId; + final Value userId; + final Value grantedAt; + final Value rowid; + const DiscountUserDatasCompanion({ + this.discountId = const Value.absent(), + this.userId = const Value.absent(), + this.grantedAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + DiscountUserDatasCompanion.insert({ + required int discountId, + required int userId, + this.grantedAt = const Value.absent(), + this.rowid = const Value.absent(), + }) : discountId = Value(discountId), + userId = Value(userId); + static Insertable custom({ + Expression? discountId, + Expression? userId, + Expression? grantedAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (discountId != null) 'discount_id': discountId, + if (userId != null) 'user_id': userId, + if (grantedAt != null) 'granted_at': grantedAt, + if (rowid != null) 'rowid': rowid, + }); + } + + DiscountUserDatasCompanion copyWith( + {Value? discountId, + Value? userId, + Value? grantedAt, + Value? rowid}) { + return DiscountUserDatasCompanion( + discountId: discountId ?? this.discountId, + userId: userId ?? this.userId, + grantedAt: grantedAt ?? this.grantedAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (discountId.present) { + map['discount_id'] = Variable(discountId.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (grantedAt.present) { + map['granted_at'] = Variable(grantedAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DiscountUserDatasCompanion(') + ..write('discountId: $discountId, ') + ..write('userId: $userId, ') + ..write('grantedAt: $grantedAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + +class $StudySessionsTable extends StudySessions + with TableInfo<$StudySessionsTable, StudySession> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $StudySessionsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES users (id) ON DELETE CASCADE')); + static const VerificationMeta _sessionIdMeta = + const VerificationMeta('sessionId'); + @override + late final GeneratedColumn sessionId = GeneratedColumn( + 'session_id', aliasedName, true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways('UNIQUE')); + static const VerificationMeta _startTimeMeta = + const VerificationMeta('startTime'); + @override + late final GeneratedColumn startTime = GeneratedColumn( + 'start_time', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + static const VerificationMeta _endTimeMeta = + const VerificationMeta('endTime'); + @override + late final GeneratedColumn endTime = GeneratedColumn( + 'end_time', aliasedName, true, + type: DriftSqlType.dateTime, requiredDuringInsert: false); + static const VerificationMeta _wordsLearnedMeta = + const VerificationMeta('wordsLearned'); + @override + late final GeneratedColumn wordsLearned = GeneratedColumn( + 'words_learned', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _testsCompletedMeta = + const VerificationMeta('testsCompleted'); + @override + late final GeneratedColumn testsCompleted = GeneratedColumn( + 'tests_completed', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); + static const VerificationMeta _accuracyMeta = + const VerificationMeta('accuracy'); + @override + late final GeneratedColumn accuracy = GeneratedColumn( + 'accuracy', aliasedName, false, + type: DriftSqlType.double, + requiredDuringInsert: false, + defaultValue: const Constant(0.0)); + static const VerificationMeta _packIdMeta = const VerificationMeta('packId'); + @override + late final GeneratedColumn packId = GeneratedColumn( + 'pack_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _testIdMeta = const VerificationMeta('testId'); + @override + late final GeneratedColumn testId = GeneratedColumn( + 'test_id', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [ + id, + userId, + sessionId, + startTime, + endTime, + wordsLearned, + testsCompleted, + accuracy, + packId, + testId, + createdAt, + updatedAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'study_sessions'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('session_id')) { + context.handle(_sessionIdMeta, + sessionId.isAcceptableOrUnknown(data['session_id']!, _sessionIdMeta)); + } + if (data.containsKey('start_time')) { + context.handle(_startTimeMeta, + startTime.isAcceptableOrUnknown(data['start_time']!, _startTimeMeta)); + } else if (isInserting) { + context.missing(_startTimeMeta); + } + if (data.containsKey('end_time')) { + context.handle(_endTimeMeta, + endTime.isAcceptableOrUnknown(data['end_time']!, _endTimeMeta)); + } + if (data.containsKey('words_learned')) { + context.handle( + _wordsLearnedMeta, + wordsLearned.isAcceptableOrUnknown( + data['words_learned']!, _wordsLearnedMeta)); + } + if (data.containsKey('tests_completed')) { + context.handle( + _testsCompletedMeta, + testsCompleted.isAcceptableOrUnknown( + data['tests_completed']!, _testsCompletedMeta)); + } + if (data.containsKey('accuracy')) { + context.handle(_accuracyMeta, + accuracy.isAcceptableOrUnknown(data['accuracy']!, _accuracyMeta)); + } + if (data.containsKey('pack_id')) { + context.handle(_packIdMeta, + packId.isAcceptableOrUnknown(data['pack_id']!, _packIdMeta)); + } + if (data.containsKey('test_id')) { + context.handle(_testIdMeta, + testId.isAcceptableOrUnknown(data['test_id']!, _testIdMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + StudySession map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return StudySession( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + sessionId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}session_id']), + startTime: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}start_time'])!, + endTime: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}end_time']), + wordsLearned: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}words_learned'])!, + testsCompleted: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}tests_completed'])!, + accuracy: attachedDatabase.typeMapping + .read(DriftSqlType.double, data['${effectivePrefix}accuracy'])!, + packId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}pack_id']), + testId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}test_id']), + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $StudySessionsTable createAlias(String alias) { + return $StudySessionsTable(attachedDatabase, alias); + } +} + +class StudySession extends DataClass implements Insertable { + final int id; + final int userId; + final String? sessionId; + final DateTime startTime; + final DateTime? endTime; + final int wordsLearned; + final int testsCompleted; + final double accuracy; + final String? packId; + final String? testId; + final DateTime createdAt; + final DateTime updatedAt; + const StudySession( + {required this.id, + required this.userId, + this.sessionId, + required this.startTime, + this.endTime, + required this.wordsLearned, + required this.testsCompleted, + required this.accuracy, + this.packId, + this.testId, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['user_id'] = Variable(userId); + if (!nullToAbsent || sessionId != null) { + map['session_id'] = Variable(sessionId); + } + map['start_time'] = Variable(startTime); + if (!nullToAbsent || endTime != null) { + map['end_time'] = Variable(endTime); + } + map['words_learned'] = Variable(wordsLearned); + map['tests_completed'] = Variable(testsCompleted); + map['accuracy'] = Variable(accuracy); + if (!nullToAbsent || packId != null) { + map['pack_id'] = Variable(packId); + } + if (!nullToAbsent || testId != null) { + map['test_id'] = Variable(testId); + } + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + StudySessionsCompanion toCompanion(bool nullToAbsent) { + return StudySessionsCompanion( + id: Value(id), + userId: Value(userId), + sessionId: sessionId == null && nullToAbsent + ? const Value.absent() + : Value(sessionId), + startTime: Value(startTime), + endTime: endTime == null && nullToAbsent + ? const Value.absent() + : Value(endTime), + wordsLearned: Value(wordsLearned), + testsCompleted: Value(testsCompleted), + accuracy: Value(accuracy), + packId: + packId == null && nullToAbsent ? const Value.absent() : Value(packId), + testId: + testId == null && nullToAbsent ? const Value.absent() : Value(testId), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory StudySession.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return StudySession( + id: serializer.fromJson(json['id']), + userId: serializer.fromJson(json['userId']), + sessionId: serializer.fromJson(json['sessionId']), + startTime: serializer.fromJson(json['startTime']), + endTime: serializer.fromJson(json['endTime']), + wordsLearned: serializer.fromJson(json['wordsLearned']), + testsCompleted: serializer.fromJson(json['testsCompleted']), + accuracy: serializer.fromJson(json['accuracy']), + packId: serializer.fromJson(json['packId']), + testId: serializer.fromJson(json['testId']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'userId': serializer.toJson(userId), + 'sessionId': serializer.toJson(sessionId), + 'startTime': serializer.toJson(startTime), + 'endTime': serializer.toJson(endTime), + 'wordsLearned': serializer.toJson(wordsLearned), + 'testsCompleted': serializer.toJson(testsCompleted), + 'accuracy': serializer.toJson(accuracy), + 'packId': serializer.toJson(packId), + 'testId': serializer.toJson(testId), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + StudySession copyWith( + {int? id, + int? userId, + Value sessionId = const Value.absent(), + DateTime? startTime, + Value endTime = const Value.absent(), + int? wordsLearned, + int? testsCompleted, + double? accuracy, + Value packId = const Value.absent(), + Value testId = const Value.absent(), + DateTime? createdAt, + DateTime? updatedAt}) => + StudySession( + id: id ?? this.id, + userId: userId ?? this.userId, + sessionId: sessionId.present ? sessionId.value : this.sessionId, + startTime: startTime ?? this.startTime, + endTime: endTime.present ? endTime.value : this.endTime, + wordsLearned: wordsLearned ?? this.wordsLearned, + testsCompleted: testsCompleted ?? this.testsCompleted, + accuracy: accuracy ?? this.accuracy, + packId: packId.present ? packId.value : this.packId, + testId: testId.present ? testId.value : this.testId, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + @override + String toString() { + return (StringBuffer('StudySession(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('sessionId: $sessionId, ') + ..write('startTime: $startTime, ') + ..write('endTime: $endTime, ') + ..write('wordsLearned: $wordsLearned, ') + ..write('testsCompleted: $testsCompleted, ') + ..write('accuracy: $accuracy, ') + ..write('packId: $packId, ') + ..write('testId: $testId, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + userId, + sessionId, + startTime, + endTime, + wordsLearned, + testsCompleted, + accuracy, + packId, + testId, + createdAt, + updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is StudySession && + other.id == this.id && + other.userId == this.userId && + other.sessionId == this.sessionId && + other.startTime == this.startTime && + other.endTime == this.endTime && + other.wordsLearned == this.wordsLearned && + other.testsCompleted == this.testsCompleted && + other.accuracy == this.accuracy && + other.packId == this.packId && + other.testId == this.testId && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class StudySessionsCompanion extends UpdateCompanion { + final Value id; + final Value userId; + final Value sessionId; + final Value startTime; + final Value endTime; + final Value wordsLearned; + final Value testsCompleted; + final Value accuracy; + final Value packId; + final Value testId; + final Value createdAt; + final Value updatedAt; + const StudySessionsCompanion({ + this.id = const Value.absent(), + this.userId = const Value.absent(), + this.sessionId = const Value.absent(), + this.startTime = const Value.absent(), + this.endTime = const Value.absent(), + this.wordsLearned = const Value.absent(), + this.testsCompleted = const Value.absent(), + this.accuracy = const Value.absent(), + this.packId = const Value.absent(), + this.testId = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + StudySessionsCompanion.insert({ + this.id = const Value.absent(), + required int userId, + this.sessionId = const Value.absent(), + required DateTime startTime, + this.endTime = const Value.absent(), + this.wordsLearned = const Value.absent(), + this.testsCompleted = const Value.absent(), + this.accuracy = const Value.absent(), + this.packId = const Value.absent(), + this.testId = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : userId = Value(userId), + startTime = Value(startTime); + static Insertable custom({ + Expression? id, + Expression? userId, + Expression? sessionId, + Expression? startTime, + Expression? endTime, + Expression? wordsLearned, + Expression? testsCompleted, + Expression? accuracy, + Expression? packId, + Expression? testId, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (userId != null) 'user_id': userId, + if (sessionId != null) 'session_id': sessionId, + if (startTime != null) 'start_time': startTime, + if (endTime != null) 'end_time': endTime, + if (wordsLearned != null) 'words_learned': wordsLearned, + if (testsCompleted != null) 'tests_completed': testsCompleted, + if (accuracy != null) 'accuracy': accuracy, + if (packId != null) 'pack_id': packId, + if (testId != null) 'test_id': testId, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + StudySessionsCompanion copyWith( + {Value? id, + Value? userId, + Value? sessionId, + Value? startTime, + Value? endTime, + Value? wordsLearned, + Value? testsCompleted, + Value? accuracy, + Value? packId, + Value? testId, + Value? createdAt, + Value? updatedAt}) { + return StudySessionsCompanion( + id: id ?? this.id, + userId: userId ?? this.userId, + sessionId: sessionId ?? this.sessionId, + startTime: startTime ?? this.startTime, + endTime: endTime ?? this.endTime, + wordsLearned: wordsLearned ?? this.wordsLearned, + testsCompleted: testsCompleted ?? this.testsCompleted, + accuracy: accuracy ?? this.accuracy, + packId: packId ?? this.packId, + testId: testId ?? this.testId, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (sessionId.present) { + map['session_id'] = Variable(sessionId.value); + } + if (startTime.present) { + map['start_time'] = Variable(startTime.value); + } + if (endTime.present) { + map['end_time'] = Variable(endTime.value); + } + if (wordsLearned.present) { + map['words_learned'] = Variable(wordsLearned.value); + } + if (testsCompleted.present) { + map['tests_completed'] = Variable(testsCompleted.value); + } + if (accuracy.present) { + map['accuracy'] = Variable(accuracy.value); + } + if (packId.present) { + map['pack_id'] = Variable(packId.value); + } + if (testId.present) { + map['test_id'] = Variable(testId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('StudySessionsCompanion(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('sessionId: $sessionId, ') + ..write('startTime: $startTime, ') + ..write('endTime: $endTime, ') + ..write('wordsLearned: $wordsLearned, ') + ..write('testsCompleted: $testsCompleted, ') + ..write('accuracy: $accuracy, ') + ..write('packId: $packId, ') + ..write('testId: $testId, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $UserAchievementsTable extends UserAchievements + with TableInfo<$UserAchievementsTable, UserAchievement> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $UserAchievementsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _userIdMeta = const VerificationMeta('userId'); + @override + late final GeneratedColumn userId = GeneratedColumn( + 'user_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES users (id) ON DELETE CASCADE')); + static const VerificationMeta _achievementIdMeta = + const VerificationMeta('achievementId'); + @override + late final GeneratedColumn achievementId = GeneratedColumn( + 'achievement_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _unlockedAtMeta = + const VerificationMeta('unlockedAt'); + @override + late final GeneratedColumn unlockedAt = GeneratedColumn( + 'unlocked_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _progressMeta = + const VerificationMeta('progress'); + @override + late final GeneratedColumn progress = GeneratedColumn( + 'progress', aliasedName, false, + type: DriftSqlType.double, + requiredDuringInsert: false, + defaultValue: const Constant(1.0)); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => + [id, userId, achievementId, unlockedAt, progress, createdAt, updatedAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'user_achievements'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('user_id')) { + context.handle(_userIdMeta, + userId.isAcceptableOrUnknown(data['user_id']!, _userIdMeta)); + } else if (isInserting) { + context.missing(_userIdMeta); + } + if (data.containsKey('achievement_id')) { + context.handle( + _achievementIdMeta, + achievementId.isAcceptableOrUnknown( + data['achievement_id']!, _achievementIdMeta)); + } else if (isInserting) { + context.missing(_achievementIdMeta); + } + if (data.containsKey('unlocked_at')) { + context.handle( + _unlockedAtMeta, + unlockedAt.isAcceptableOrUnknown( + data['unlocked_at']!, _unlockedAtMeta)); + } + if (data.containsKey('progress')) { + context.handle(_progressMeta, + progress.isAcceptableOrUnknown(data['progress']!, _progressMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + UserAchievement map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return UserAchievement( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + userId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}user_id'])!, + achievementId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}achievement_id'])!, + unlockedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}unlocked_at'])!, + progress: attachedDatabase.typeMapping + .read(DriftSqlType.double, data['${effectivePrefix}progress'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $UserAchievementsTable createAlias(String alias) { + return $UserAchievementsTable(attachedDatabase, alias); + } +} + +class UserAchievement extends DataClass implements Insertable { + final int id; + final int userId; + final String achievementId; + final DateTime unlockedAt; + final double progress; + final DateTime createdAt; + final DateTime updatedAt; + const UserAchievement( + {required this.id, + required this.userId, + required this.achievementId, + required this.unlockedAt, + required this.progress, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['user_id'] = Variable(userId); + map['achievement_id'] = Variable(achievementId); + map['unlocked_at'] = Variable(unlockedAt); + map['progress'] = Variable(progress); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + UserAchievementsCompanion toCompanion(bool nullToAbsent) { + return UserAchievementsCompanion( + id: Value(id), + userId: Value(userId), + achievementId: Value(achievementId), + unlockedAt: Value(unlockedAt), + progress: Value(progress), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory UserAchievement.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return UserAchievement( + id: serializer.fromJson(json['id']), + userId: serializer.fromJson(json['userId']), + achievementId: serializer.fromJson(json['achievementId']), + unlockedAt: serializer.fromJson(json['unlockedAt']), + progress: serializer.fromJson(json['progress']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'userId': serializer.toJson(userId), + 'achievementId': serializer.toJson(achievementId), + 'unlockedAt': serializer.toJson(unlockedAt), + 'progress': serializer.toJson(progress), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + UserAchievement copyWith( + {int? id, + int? userId, + String? achievementId, + DateTime? unlockedAt, + double? progress, + DateTime? createdAt, + DateTime? updatedAt}) => + UserAchievement( + id: id ?? this.id, + userId: userId ?? this.userId, + achievementId: achievementId ?? this.achievementId, + unlockedAt: unlockedAt ?? this.unlockedAt, + progress: progress ?? this.progress, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + @override + String toString() { + return (StringBuffer('UserAchievement(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('achievementId: $achievementId, ') + ..write('unlockedAt: $unlockedAt, ') + ..write('progress: $progress, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, userId, achievementId, unlockedAt, progress, createdAt, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is UserAchievement && + other.id == this.id && + other.userId == this.userId && + other.achievementId == this.achievementId && + other.unlockedAt == this.unlockedAt && + other.progress == this.progress && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class UserAchievementsCompanion extends UpdateCompanion { + final Value id; + final Value userId; + final Value achievementId; + final Value unlockedAt; + final Value progress; + final Value createdAt; + final Value updatedAt; + const UserAchievementsCompanion({ + this.id = const Value.absent(), + this.userId = const Value.absent(), + this.achievementId = const Value.absent(), + this.unlockedAt = const Value.absent(), + this.progress = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + UserAchievementsCompanion.insert({ + this.id = const Value.absent(), + required int userId, + required String achievementId, + this.unlockedAt = const Value.absent(), + this.progress = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : userId = Value(userId), + achievementId = Value(achievementId); + static Insertable custom({ + Expression? id, + Expression? userId, + Expression? achievementId, + Expression? unlockedAt, + Expression? progress, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (userId != null) 'user_id': userId, + if (achievementId != null) 'achievement_id': achievementId, + if (unlockedAt != null) 'unlocked_at': unlockedAt, + if (progress != null) 'progress': progress, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + UserAchievementsCompanion copyWith( + {Value? id, + Value? userId, + Value? achievementId, + Value? unlockedAt, + Value? progress, + Value? createdAt, + Value? updatedAt}) { + return UserAchievementsCompanion( + id: id ?? this.id, + userId: userId ?? this.userId, + achievementId: achievementId ?? this.achievementId, + unlockedAt: unlockedAt ?? this.unlockedAt, + progress: progress ?? this.progress, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (userId.present) { + map['user_id'] = Variable(userId.value); + } + if (achievementId.present) { + map['achievement_id'] = Variable(achievementId.value); + } + if (unlockedAt.present) { + map['unlocked_at'] = Variable(unlockedAt.value); + } + if (progress.present) { + map['progress'] = Variable(progress.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('UserAchievementsCompanion(') + ..write('id: $id, ') + ..write('userId: $userId, ') + ..write('achievementId: $achievementId, ') + ..write('unlockedAt: $unlockedAt, ') + ..write('progress: $progress, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +class $ShareRequestsTable extends ShareRequests + with TableInfo<$ShareRequestsTable, ShareRequest> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $ShareRequestsTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _telegramUserIdMeta = + const VerificationMeta('telegramUserId'); + @override + late final GeneratedColumn telegramUserId = GeneratedColumn( + 'telegram_user_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _telegramUsernameMeta = + const VerificationMeta('telegramUsername'); + @override + late final GeneratedColumn telegramUsername = GeneratedColumn( + 'telegram_username', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + static const VerificationMeta _sharedCardIdMeta = + const VerificationMeta('sharedCardId'); + @override + late final GeneratedColumn sharedCardId = GeneratedColumn( + 'shared_card_id', aliasedName, true, + type: DriftSqlType.int, requiredDuringInsert: false); + static const VerificationMeta _requestedAtMeta = + const VerificationMeta('requestedAt'); + @override + late final GeneratedColumn requestedAt = GeneratedColumn( + 'requested_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _createdAtMeta = + const VerificationMeta('createdAt'); + @override + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + static const VerificationMeta _updatedAtMeta = + const VerificationMeta('updatedAt'); + @override + late final GeneratedColumn updatedAt = GeneratedColumn( + 'updated_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [ + id, + telegramUserId, + telegramUsername, + sharedCardId, + requestedAt, + createdAt, + updatedAt + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'share_requests'; + @override + VerificationContext validateIntegrity(Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('telegram_user_id')) { + context.handle( + _telegramUserIdMeta, + telegramUserId.isAcceptableOrUnknown( + data['telegram_user_id']!, _telegramUserIdMeta)); + } else if (isInserting) { + context.missing(_telegramUserIdMeta); + } + if (data.containsKey('telegram_username')) { + context.handle( + _telegramUsernameMeta, + telegramUsername.isAcceptableOrUnknown( + data['telegram_username']!, _telegramUsernameMeta)); + } + if (data.containsKey('shared_card_id')) { + context.handle( + _sharedCardIdMeta, + sharedCardId.isAcceptableOrUnknown( + data['shared_card_id']!, _sharedCardIdMeta)); + } + if (data.containsKey('requested_at')) { + context.handle( + _requestedAtMeta, + requestedAt.isAcceptableOrUnknown( + data['requested_at']!, _requestedAtMeta)); + } + if (data.containsKey('created_at')) { + context.handle(_createdAtMeta, + createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); + } + if (data.containsKey('updated_at')) { + context.handle(_updatedAtMeta, + updatedAt.isAcceptableOrUnknown(data['updated_at']!, _updatedAtMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + ShareRequest map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ShareRequest( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + telegramUserId: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}telegram_user_id'])!, + telegramUsername: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}telegram_username']), + sharedCardId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}shared_card_id']), + requestedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}requested_at'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + updatedAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}updated_at'])!, + ); + } + + @override + $ShareRequestsTable createAlias(String alias) { + return $ShareRequestsTable(attachedDatabase, alias); + } +} + +class ShareRequest extends DataClass implements Insertable { + final int id; + final String telegramUserId; + final String? telegramUsername; + final int? sharedCardId; + final DateTime requestedAt; + final DateTime createdAt; + final DateTime updatedAt; + const ShareRequest( + {required this.id, + required this.telegramUserId, + this.telegramUsername, + this.sharedCardId, + required this.requestedAt, + required this.createdAt, + required this.updatedAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['telegram_user_id'] = Variable(telegramUserId); + if (!nullToAbsent || telegramUsername != null) { + map['telegram_username'] = Variable(telegramUsername); + } + if (!nullToAbsent || sharedCardId != null) { + map['shared_card_id'] = Variable(sharedCardId); + } + map['requested_at'] = Variable(requestedAt); + map['created_at'] = Variable(createdAt); + map['updated_at'] = Variable(updatedAt); + return map; + } + + ShareRequestsCompanion toCompanion(bool nullToAbsent) { + return ShareRequestsCompanion( + id: Value(id), + telegramUserId: Value(telegramUserId), + telegramUsername: telegramUsername == null && nullToAbsent + ? const Value.absent() + : Value(telegramUsername), + sharedCardId: sharedCardId == null && nullToAbsent + ? const Value.absent() + : Value(sharedCardId), + requestedAt: Value(requestedAt), + createdAt: Value(createdAt), + updatedAt: Value(updatedAt), + ); + } + + factory ShareRequest.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ShareRequest( + id: serializer.fromJson(json['id']), + telegramUserId: serializer.fromJson(json['telegramUserId']), + telegramUsername: serializer.fromJson(json['telegramUsername']), + sharedCardId: serializer.fromJson(json['sharedCardId']), + requestedAt: serializer.fromJson(json['requestedAt']), + createdAt: serializer.fromJson(json['createdAt']), + updatedAt: serializer.fromJson(json['updatedAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'telegramUserId': serializer.toJson(telegramUserId), + 'telegramUsername': serializer.toJson(telegramUsername), + 'sharedCardId': serializer.toJson(sharedCardId), + 'requestedAt': serializer.toJson(requestedAt), + 'createdAt': serializer.toJson(createdAt), + 'updatedAt': serializer.toJson(updatedAt), + }; + } + + ShareRequest copyWith( + {int? id, + String? telegramUserId, + Value telegramUsername = const Value.absent(), + Value sharedCardId = const Value.absent(), + DateTime? requestedAt, + DateTime? createdAt, + DateTime? updatedAt}) => + ShareRequest( + id: id ?? this.id, + telegramUserId: telegramUserId ?? this.telegramUserId, + telegramUsername: telegramUsername.present + ? telegramUsername.value + : this.telegramUsername, + sharedCardId: + sharedCardId.present ? sharedCardId.value : this.sharedCardId, + requestedAt: requestedAt ?? this.requestedAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + @override + String toString() { + return (StringBuffer('ShareRequest(') + ..write('id: $id, ') + ..write('telegramUserId: $telegramUserId, ') + ..write('telegramUsername: $telegramUsername, ') + ..write('sharedCardId: $sharedCardId, ') + ..write('requestedAt: $requestedAt, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, telegramUserId, telegramUsername, + sharedCardId, requestedAt, createdAt, updatedAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ShareRequest && + other.id == this.id && + other.telegramUserId == this.telegramUserId && + other.telegramUsername == this.telegramUsername && + other.sharedCardId == this.sharedCardId && + other.requestedAt == this.requestedAt && + other.createdAt == this.createdAt && + other.updatedAt == this.updatedAt); +} + +class ShareRequestsCompanion extends UpdateCompanion { + final Value id; + final Value telegramUserId; + final Value telegramUsername; + final Value sharedCardId; + final Value requestedAt; + final Value createdAt; + final Value updatedAt; + const ShareRequestsCompanion({ + this.id = const Value.absent(), + this.telegramUserId = const Value.absent(), + this.telegramUsername = const Value.absent(), + this.sharedCardId = const Value.absent(), + this.requestedAt = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }); + ShareRequestsCompanion.insert({ + this.id = const Value.absent(), + required String telegramUserId, + this.telegramUsername = const Value.absent(), + this.sharedCardId = const Value.absent(), + this.requestedAt = const Value.absent(), + this.createdAt = const Value.absent(), + this.updatedAt = const Value.absent(), + }) : telegramUserId = Value(telegramUserId); + static Insertable custom({ + Expression? id, + Expression? telegramUserId, + Expression? telegramUsername, + Expression? sharedCardId, + Expression? requestedAt, + Expression? createdAt, + Expression? updatedAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (telegramUserId != null) 'telegram_user_id': telegramUserId, + if (telegramUsername != null) 'telegram_username': telegramUsername, + if (sharedCardId != null) 'shared_card_id': sharedCardId, + if (requestedAt != null) 'requested_at': requestedAt, + if (createdAt != null) 'created_at': createdAt, + if (updatedAt != null) 'updated_at': updatedAt, + }); + } + + ShareRequestsCompanion copyWith( + {Value? id, + Value? telegramUserId, + Value? telegramUsername, + Value? sharedCardId, + Value? requestedAt, + Value? createdAt, + Value? updatedAt}) { + return ShareRequestsCompanion( + id: id ?? this.id, + telegramUserId: telegramUserId ?? this.telegramUserId, + telegramUsername: telegramUsername ?? this.telegramUsername, + sharedCardId: sharedCardId ?? this.sharedCardId, + requestedAt: requestedAt ?? this.requestedAt, + createdAt: createdAt ?? this.createdAt, + updatedAt: updatedAt ?? this.updatedAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (telegramUserId.present) { + map['telegram_user_id'] = Variable(telegramUserId.value); + } + if (telegramUsername.present) { + map['telegram_username'] = Variable(telegramUsername.value); + } + if (sharedCardId.present) { + map['shared_card_id'] = Variable(sharedCardId.value); + } + if (requestedAt.present) { + map['requested_at'] = Variable(requestedAt.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (updatedAt.present) { + map['updated_at'] = Variable(updatedAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ShareRequestsCompanion(') + ..write('id: $id, ') + ..write('telegramUserId: $telegramUserId, ') + ..write('telegramUsername: $telegramUsername, ') + ..write('sharedCardId: $sharedCardId, ') + ..write('requestedAt: $requestedAt, ') + ..write('createdAt: $createdAt, ') + ..write('updatedAt: $updatedAt') + ..write(')')) + .toString(); + } +} + +abstract class _$AppDatabase extends GeneratedDatabase { + _$AppDatabase(QueryExecutor e) : super(e); + late final $UsersTable users = $UsersTable(this); + late final $UserDatasTable userDatas = $UserDatasTable(this); + late final $TokensTable tokens = $TokensTable(this); + late final $RefreshTokensTable refreshTokens = $RefreshTokensTable(this); + late final $TelegramAuthCodesTable telegramAuthCodes = + $TelegramAuthCodesTable(this); + late final $CardPacksTable cardPacks = $CardPacksTable(this); + late final $GameCardsTable gameCards = $GameCardsTable(this); + late final $VoiceModelsTable voiceModels = $VoiceModelsTable(this); + late final $UserPacksTable userPacks = $UserPacksTable(this); + late final $PreviewCardsTable previewCards = $PreviewCardsTable(this); + late final $CardPackCardsTable cardPackCards = $CardPackCardsTable(this); + late final $CardVoicesTable cardVoices = $CardVoicesTable(this); + late final $SubscriptionPlansTable subscriptionPlans = + $SubscriptionPlansTable(this); + late final $UserSubscriptionsTable userSubscriptions = + $UserSubscriptionsTable(this); + late final $PaymentsTable payments = $PaymentsTable(this); + late final $TestsTable tests = $TestsTable(this); + late final $TestQuestionsTable testQuestions = $TestQuestionsTable(this); + late final $TestPackRelationsTable testPackRelations = + $TestPackRelationsTable(this); + late final $TestStatisticsTable testStatistics = $TestStatisticsTable(this); + late final $TasksTable tasks = $TasksTable(this); + late final $UserTasksTable userTasks = $UserTasksTable(this); + late final $UserTaskProgressesTable userTaskProgresses = + $UserTaskProgressesTable(this); + late final $UserTaskResultsTable userTaskResults = + $UserTaskResultsTable(this); + late final $PromoCodesCampaignsTable promoCodesCampaigns = + $PromoCodesCampaignsTable(this); + late final $PromoCodesTable promoCodes = $PromoCodesTable(this); + late final $DiscountCampaignsTable discountCampaigns = + $DiscountCampaignsTable(this); + late final $DiscountsTable discounts = $DiscountsTable(this); + late final $DiscountUserDatasTable discountUserDatas = + $DiscountUserDatasTable(this); + late final $StudySessionsTable studySessions = $StudySessionsTable(this); + late final $UserAchievementsTable userAchievements = + $UserAchievementsTable(this); + late final $ShareRequestsTable shareRequests = $ShareRequestsTable(this); + late final UserDao userDao = UserDao(this as AppDatabase); + late final PackDao packDao = PackDao(this as AppDatabase); + late final TestDao testDao = TestDao(this as AppDatabase); + late final PaymentDao paymentDao = PaymentDao(this as AppDatabase); + late final SubscriptionDao subscriptionDao = + SubscriptionDao(this as AppDatabase); + late final TaskDao taskDao = TaskDao(this as AppDatabase); + late final PromoCodeDao promoCodeDao = PromoCodeDao(this as AppDatabase); + late final DiscountDao discountDao = DiscountDao(this as AppDatabase); + late final StatisticsDao statisticsDao = StatisticsDao(this as AppDatabase); + late final AchievementDao achievementDao = + AchievementDao(this as AppDatabase); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + users, + userDatas, + tokens, + refreshTokens, + telegramAuthCodes, + cardPacks, + gameCards, + voiceModels, + userPacks, + previewCards, + cardPackCards, + cardVoices, + subscriptionPlans, + userSubscriptions, + payments, + tests, + testQuestions, + testPackRelations, + testStatistics, + tasks, + userTasks, + userTaskProgresses, + userTaskResults, + promoCodesCampaigns, + promoCodes, + discountCampaigns, + discounts, + discountUserDatas, + studySessions, + userAchievements, + shareRequests + ]; + @override + StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules( + [ + WritePropagation( + on: TableUpdateQuery.onTableName('users', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('user_datas', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('users', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('tokens', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('users', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('refresh_tokens', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('card_packs', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('game_cards', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('game_cards', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('voice_models', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('users', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('user_packs', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('card_packs', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('user_packs', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('card_packs', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('preview_cards', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('game_cards', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('preview_cards', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('card_packs', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('card_pack_cards', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('game_cards', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('card_pack_cards', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('game_cards', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('card_voices', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('voice_models', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('card_voices', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('users', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('user_subscriptions', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('users', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('payments', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('tests', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('test_questions', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('tests', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('test_pack_relations', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('card_packs', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('test_pack_relations', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('tests', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('test_statistics', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('users', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('user_task_progresses', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('users', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('user_task_results', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('promo_codes_campaigns', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('promo_codes', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('users', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('promo_codes', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('discount_campaigns', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('discounts', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('discounts', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('discount_user_datas', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('users', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('discount_user_datas', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('users', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('study_sessions', kind: UpdateKind.delete), + ], + ), + WritePropagation( + on: TableUpdateQuery.onTableName('users', + limitUpdateKind: UpdateKind.delete), + result: [ + TableUpdate('user_achievements', kind: UpdateKind.delete), + ], + ), + ], + ); +} diff --git a/mnemo_cards_backend/lib/database/tables/achievements.dart b/mnemo_cards_backend/lib/database/tables/achievements.dart new file mode 100644 index 0000000..65f03bc --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/achievements.dart @@ -0,0 +1,30 @@ +import 'package:drift/drift.dart'; +import '../converters.dart'; +import 'users.dart'; + +/// Таблица UserAchievements - достижения пользователей +class UserAchievements extends Table { + IntColumn get id => integer().autoIncrement()(); + + IntColumn get userId => integer().references(Users, #id, onDelete: KeyAction.cascade)(); + + // Achievement ID (from AchievementDefinitions) + TextColumn get achievementId => text()(); + + // When achievement was unlocked + DateTimeColumn get unlockedAt => dateTime().withDefault(currentDateAndTime)(); + + // Progress (0.0 to 1.0) + RealColumn get progress => real().withDefault(const Constant(1.0))(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); + + @override + List get customConstraints => [ + 'UNIQUE(user_id, achievement_id)', + ]; +} + +// Конвертеры импортированы из converters.dart \ No newline at end of file diff --git a/mnemo_cards_backend/lib/database/tables/auth.dart b/mnemo_cards_backend/lib/database/tables/auth.dart new file mode 100644 index 0000000..e2fa8f5 --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/auth.dart @@ -0,0 +1,49 @@ +import 'package:drift/drift.dart'; +import 'users.dart'; + +/// Таблица Tokens - токены авторизации пользователей +class Tokens extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get userId => integer().references(Users, #id, onDelete: KeyAction.cascade)(); + + TextColumn get token => text().unique()(); + TextColumn get externalUserId => text()(); + + DateTimeColumn get created => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get expires => dateTime()(); + + @override + List get customConstraints => [ + 'CONSTRAINT valid_expiry CHECK (expires > created)', + ]; +} + +/// Таблица RefreshTokens - refresh токены для JWT +class RefreshTokens extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get userId => integer().references(Users, #id, onDelete: KeyAction.cascade)(); + + TextColumn get jti => text().unique()(); // JWT ID + + BoolColumn get isBlacklisted => boolean().withDefault(const Constant(false))(); + + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get expiresAt => dateTime()(); +} + +/// Таблица TelegramAuthCodes - коды для авторизации через Telegram +class TelegramAuthCodes extends Table { + IntColumn get id => integer().autoIncrement()(); + + TextColumn get code => text().unique()(); + TextColumn get telegramUserId => text()(); + TextColumn get telegramUsername => text().nullable()(); + TextColumn get firstName => text().nullable()(); + TextColumn get lastName => text().nullable()(); + + BoolColumn get isUsed => boolean().withDefault(const Constant(false))(); + DateTimeColumn get usedAt => dateTime().nullable()(); + + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get expiresAt => dateTime()(); +} diff --git a/mnemo_cards_backend/lib/database/tables/discounts.dart b/mnemo_cards_backend/lib/database/tables/discounts.dart new file mode 100644 index 0000000..aa180a0 --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/discounts.dart @@ -0,0 +1,58 @@ +import 'package:drift/drift.dart'; +import '../converters.dart'; +import 'users.dart'; + +/// Таблица DiscountCampaigns - кампании скидок +class DiscountCampaigns extends Table { + IntColumn get id => integer().autoIncrement()(); + + TextColumn get name => text().nullable()(); + + DateTimeColumn get start => dateTime()(); + DateTimeColumn get finish => dateTime()(); + + // Статус (enum as string) + TextColumn get status => text()(); // created, active, expired, disabled + + // Теги (JSON array) + TextColumn get tags => text() + .withDefault(const Constant('[]')) + .map(const StringListConverter())(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); + BoolColumn get isDeleted => boolean().withDefault(const Constant(false))(); +} + +/// Таблица Discounts - скидки +class Discounts extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get campaignId => integer().references(DiscountCampaigns, #id, onDelete: KeyAction.cascade)(); + + // Процент скидки (0-100) + RealColumn get discountPercent => real()(); + + // Продукты (JSON array) + TextColumn get products => text() + .withDefault(const Constant('[]')) + .map(const JsonListConverter())(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); + BoolColumn get isDeleted => boolean().withDefault(const Constant(false))(); +} + +/// Junction table для связи Discounts ↔ UserDatas (many-to-many) +class DiscountUserDatas extends Table { + IntColumn get discountId => integer().references(Discounts, #id, onDelete: KeyAction.cascade)(); + IntColumn get userId => integer().references(Users, #id, onDelete: KeyAction.cascade)(); + + DateTimeColumn get grantedAt => dateTime().withDefault(currentDateAndTime)(); + + @override + Set get primaryKey => {discountId, userId}; +} + +// Конвертеры импортированы из converters.dart diff --git a/mnemo_cards_backend/lib/database/tables/packs.dart b/mnemo_cards_backend/lib/database/tables/packs.dart new file mode 100644 index 0000000..62a907d --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/packs.dart @@ -0,0 +1,81 @@ +import 'package:drift/drift.dart'; +import '../converters.dart'; + +/// Таблица CardPacks - наборы карточек +class CardPacks extends Table { + IntColumn get id => integer().autoIncrement()(); + + // Основная информация + TextColumn get title => text()(); + TextColumn get subtitle => text()(); + TextColumn get description => text().nullable()(); + + // Визуальное оформление + TextColumn get color => text().nullable()(); + TextColumn get cover => text().nullable()(); // URL обложки + + // Параметры + IntColumn get size => integer()(); // количество карточек + TextColumn get version => text().nullable()(); + IntColumn get order => integer().withDefault(const Constant(0))(); + BoolColumn get enabled => boolean().withDefault(const Constant(true))(); + + // Порядок карточек (JSON array of IDs) + TextColumn get cardsOrder => text() + .withDefault(const Constant('[]')) + .map(const IntListConverter())(); + + // Store IDs для покупок + TextColumn get googlePlayId => text().nullable()(); + TextColumn get rustoreId => text().nullable()(); + TextColumn get appStoreId => text().nullable()(); + + // Цена + TextColumn get price => text().nullable()(); + TextColumn get currency => text().withDefault(const Constant('RUB'))(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); + BoolColumn get isDeleted => boolean().withDefault(const Constant(false))(); +} + +/// Таблица GameCards - карточки для изучения +class GameCards extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get packId => integer().references(CardPacks, #id, onDelete: KeyAction.cascade)(); + + // Основной контент + TextColumn get original => text()(); // слово на иностранном языке + TextColumn get translation => text()(); // перевод + TextColumn get mnemo => text().nullable()(); // мнемоническая подсказка + + // Изображения + TextColumn get image => text()(); // основное изображение + TextColumn get imageBack => text().nullable()(); // изображение на обратной стороне + + // Произношение + TextColumn get transcription => text().nullable()(); + TextColumn get transcriptionMnemo => text().nullable()(); + + // Дополнительная информация + TextColumn get back => text().nullable()(); // дополнительный текст + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); + BoolColumn get isDeleted => boolean().withDefault(const Constant(false))(); +} + +/// Таблица VoiceModels - голосовые файлы для карточек +class VoiceModels extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get cardId => integer().references(GameCards, #id, onDelete: KeyAction.cascade)(); + + TextColumn get voiceUrl => text()(); // URL аудиофайла + TextColumn get language => text()(); // язык озвучки + + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); +} + +// IntListConverter импортирован из converters.dart diff --git a/mnemo_cards_backend/lib/database/tables/payments.dart b/mnemo_cards_backend/lib/database/tables/payments.dart new file mode 100644 index 0000000..13b01bb --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/payments.dart @@ -0,0 +1,38 @@ +import 'package:drift/drift.dart'; +import '../converters.dart'; +import 'users.dart'; + +/// Таблица Payments - платежи +class Payments extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get userId => integer().references(Users, #id, onDelete: KeyAction.cascade)(); + + TextColumn get amount => text()(); + TextColumn get currency => text()(); + + // Статус и платежная система (enum as string) + TextColumn get status => text()(); // PaymentStatus + TextColumn get paymentSystem => text()(); // PaymentSystem + + TextColumn get externalToken => text().nullable()(); + TextColumn get meta => text().nullable()(); + + DateTimeColumn get date => dateTime().withDefault(currentDateAndTime)(); + + // Продукты (JSON array) + TextColumn get products => text() + .withDefault(const Constant('[]')) + .map(const JsonListConverter())(); + + // Deprecated fields (для обратной совместимости) + TextColumn get packs => text() + .withDefault(const Constant('[]')) + .map(const StringListConverter())(); + BoolColumn get subscription => boolean().withDefault(const Constant(false))(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} + +// Конвертеры импортированы из converters.dart diff --git a/mnemo_cards_backend/lib/database/tables/promo_codes.dart b/mnemo_cards_backend/lib/database/tables/promo_codes.dart new file mode 100644 index 0000000..2d5bed3 --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/promo_codes.dart @@ -0,0 +1,54 @@ +import 'package:drift/drift.dart'; +import '../converters.dart'; +import 'users.dart'; + +/// Таблица PromoCodesCampaigns - кампании промокодов +class PromoCodesCampaigns extends Table { + IntColumn get id => integer().autoIncrement()(); + + TextColumn get template => text()(); + TextColumn get name => text().nullable()(); + + // Продукты (JSON array) + TextColumn get products => text() + .withDefault(const Constant('[]')) + .map(const JsonListConverter())(); + + IntColumn get activationsPerCode => integer()(); + IntColumn get activationsPerUser => integer()(); + IntColumn get generationSize => integer()(); + + DateTimeColumn get start => dateTime()(); + DateTimeColumn get finish => dateTime()(); + + // Статус (enum as string) + TextColumn get status => text()(); // created, preparing, ready, active, disabled + + // Теги (JSON array) + TextColumn get tags => text() + .withDefault(const Constant('[]')) + .map(const StringListConverter())(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); + BoolColumn get isDeleted => boolean().withDefault(const Constant(false))(); +} + +/// Таблица PromoCodes - промокоды +class PromoCodes extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get campaignId => integer().references(PromoCodesCampaigns, #id, onDelete: KeyAction.cascade)(); + + TextColumn get code => text().unique()(); + IntColumn get activations => integer().withDefault(const Constant(0))(); + + // Индивидуальный промокод (связан с пользователем) + IntColumn get userId => integer().nullable().references(Users, #id, onDelete: KeyAction.cascade)(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} + +// Конвертеры импортированы из converters.dart diff --git a/mnemo_cards_backend/lib/database/tables/relations.dart b/mnemo_cards_backend/lib/database/tables/relations.dart new file mode 100644 index 0000000..4a9d793 --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/relations.dart @@ -0,0 +1,53 @@ +import 'package:drift/drift.dart'; +import 'users.dart'; +import 'packs.dart' show CardPacks, GameCards, VoiceModels; + +/// Junction table для связи Users ↔ CardPacks (многие ко многим) +/// Хранит информацию о том, какие паки куплены пользователем +class UserPacks extends Table { + IntColumn get userId => integer().references(Users, #id, onDelete: KeyAction.cascade)(); + IntColumn get packId => integer().references(CardPacks, #id, onDelete: KeyAction.cascade)(); + + // Когда пользователь получил доступ к паку + DateTimeColumn get grantedAt => dateTime().withDefault(currentDateAndTime)(); + + // Как пользователь получил пак (purchase, promo, free, admin) + TextColumn get grantType => text().withDefault(const Constant('purchase'))(); + + @override + Set get primaryKey => {userId, packId}; +} + +/// Таблица PreviewCards - связь CardPacks с preview карточками +/// Хранит какие карточки показывать в превью пака +class PreviewCards extends Table { + IntColumn get packId => integer().references(CardPacks, #id, onDelete: KeyAction.cascade)(); + IntColumn get cardId => integer().references(GameCards, #id, onDelete: KeyAction.cascade)(); + + IntColumn get order => integer().withDefault(const Constant(0))(); + + @override + Set get primaryKey => {packId, cardId}; +} + +/// Таблица CardPackCards - связь CardPacks с GameCards (many-to-many) +/// Хранит какие карточки принадлежат какому паку +class CardPackCards extends Table { + IntColumn get packId => integer().references(CardPacks, #id, onDelete: KeyAction.cascade)(); + IntColumn get cardId => integer().references(GameCards, #id, onDelete: KeyAction.cascade)(); + + IntColumn get order => integer().withDefault(const Constant(0))(); + + @override + Set get primaryKey => {packId, cardId}; +} + +/// Таблица CardVoices - связь GameCards с VoiceModels (many-to-many) +/// Хранит какие голосовые файлы принадлежат какой карточке +class CardVoices extends Table { + IntColumn get cardId => integer().references(GameCards, #id, onDelete: KeyAction.cascade)(); + IntColumn get voiceId => integer().references(VoiceModels, #id, onDelete: KeyAction.cascade)(); + + @override + Set get primaryKey => {cardId, voiceId}; +} diff --git a/mnemo_cards_backend/lib/database/tables/statistics.dart b/mnemo_cards_backend/lib/database/tables/statistics.dart new file mode 100644 index 0000000..f0d0bc4 --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/statistics.dart @@ -0,0 +1,24 @@ +import 'package:drift/drift.dart'; +import 'users.dart'; + +/// Таблица StudySessions - сессии изучения +class StudySessions extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get userId => integer().references(Users, #id, onDelete: KeyAction.cascade)(); + + TextColumn get sessionId => text().nullable().unique()(); + + DateTimeColumn get startTime => dateTime()(); + DateTimeColumn get endTime => dateTime().nullable()(); + + IntColumn get wordsLearned => integer().withDefault(const Constant(0))(); + IntColumn get testsCompleted => integer().withDefault(const Constant(0))(); + RealColumn get accuracy => real().withDefault(const Constant(0.0))(); + + TextColumn get packId => text().nullable()(); + TextColumn get testId => text().nullable()(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} diff --git a/mnemo_cards_backend/lib/database/tables/subscriptions.dart b/mnemo_cards_backend/lib/database/tables/subscriptions.dart new file mode 100644 index 0000000..257087b --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/subscriptions.dart @@ -0,0 +1,51 @@ +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import '../converters.dart'; +import 'users.dart'; + +/// Таблица SubscriptionPlans - планы подписки +class SubscriptionPlans extends Table { + IntColumn get id => integer().autoIncrement()(); + + // UI информация (JSON) + TextColumn get ui => text().nullable().map(NullAwareTypeConverter.wrap(const JsonMapConverter()))(); + + // Цена и валюта + TextColumn get price => text()(); + TextColumn get currency => text()(); + IntColumn get durationDays => integer()(); + + // Функции подписки (JSON array) + TextColumn get features => text() + .withDefault(const Constant('[]')) + .map(const JsonListConverter())(); + + // Платежная система + TextColumn get paymentId => text().nullable()(); + TextColumn get paymentSystem => text()(); // enum as string + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); + BoolColumn get isDeleted => boolean().withDefault(const Constant(false))(); +} + +/// Таблица UserSubscriptions - подписки пользователей +class UserSubscriptions extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get userId => integer().unique().references(Users, #id, onDelete: KeyAction.cascade)(); + + DateTimeColumn get start => dateTime()(); + DateTimeColumn get finish => dateTime()(); + + // Функции подписки (JSON array) + TextColumn get features => text() + .withDefault(const Constant('[]')) + .map(const JsonListConverter())(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} + +// Конвертеры импортированы из converters.dart diff --git a/mnemo_cards_backend/lib/database/tables/tasks.dart b/mnemo_cards_backend/lib/database/tables/tasks.dart new file mode 100644 index 0000000..c825f39 --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/tasks.dart @@ -0,0 +1,89 @@ +import 'package:drift/drift.dart'; +import '../converters.dart'; +import 'users.dart'; + +/// Таблица Tasks - задачи системы +class Tasks extends Table { + IntColumn get id => integer().autoIncrement()(); + + TextColumn get name => text()(); + IntColumn get minCycleMillis => integer()(); + IntColumn get maxCycleMillis => integer()(); + IntColumn get intervalMillis => integer()(); + IntColumn get timeoutMillis => integer()(); + + TextColumn get status => text().nullable()(); + TextColumn get description => text().nullable()(); + DateTimeColumn get lastExecution => dateTime().nullable()(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} + +/// Таблица UserTasks - задачи пользователей +class UserTasks extends Table { + IntColumn get id => integer().autoIncrement()(); + + TextColumn get title => text()(); + TextColumn get description => text()(); + TextColumn get type => text()(); // app_internal, external, social + TextColumn get difficulty => text()(); // easy, medium, hard + TextColumn get status => text()(); // available, in_progress, completed, expired, failed + + // Награды (JSON array) + TextColumn get rewards => text() + .withDefault(const Constant('[]')) + .map(const JsonListConverter())(); + + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get expiresAt => dateTime()(); + DateTimeColumn get completedAt => dateTime().nullable()(); + + TextColumn get proofUrl => text().nullable()(); + TextColumn get instructions => text().nullable()(); + + // Теги (JSON array) + TextColumn get tags => text() + .withDefault(const Constant('[]')) + .map(const StringListConverter())(); + + TextColumn get imageUrl => text().nullable()(); + + // Audit + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} + +/// Таблица UserTaskProgresses - прогресс выполнения задач пользователями +class UserTaskProgresses extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get userId => integer().references(Users, #id, onDelete: KeyAction.cascade)(); + IntColumn get taskId => integer()(); // Reference to UserTasks, but not FK to avoid circular deps + + // Прогресс (JSON) + TextColumn get progress => text() + .withDefault(const Constant('{}')) + .map(const JsonMapConverter())(); + + DateTimeColumn get startedAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} + +/// Таблица UserTaskResults - результаты выполнения задач +class UserTaskResults extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get userId => integer().references(Users, #id, onDelete: KeyAction.cascade)(); + IntColumn get taskId => integer()(); // Reference to UserTasks + + // Результаты (JSON) + TextColumn get results => text() + .withDefault(const Constant('{}')) + .map(const JsonMapConverter())(); + + DateTimeColumn get completedAt => dateTime().withDefault(currentDateAndTime)(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); +} + +// Конвертеры импортированы из converters.dart diff --git a/mnemo_cards_backend/lib/database/tables/telegram.dart b/mnemo_cards_backend/lib/database/tables/telegram.dart new file mode 100644 index 0000000..0003ef0 --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/telegram.dart @@ -0,0 +1,17 @@ +import 'package:drift/drift.dart'; + +/// Таблица ShareRequests - запросы на шаринг через Telegram +class ShareRequests extends Table { + IntColumn get id => integer().autoIncrement()(); + + TextColumn get telegramUserId => text()(); + TextColumn get telegramUsername => text().nullable()(); + + IntColumn get sharedCardId => integer().nullable()(); + + DateTimeColumn get requestedAt => dateTime().withDefault(currentDateAndTime)(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} diff --git a/mnemo_cards_backend/lib/database/tables/tests.dart b/mnemo_cards_backend/lib/database/tables/tests.dart new file mode 100644 index 0000000..34f5721 --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/tests.dart @@ -0,0 +1,63 @@ +import 'package:drift/drift.dart'; +import '../converters.dart'; +import 'packs.dart'; + +/// Таблица Tests - тесты +class Tests extends Table { + IntColumn get id => integer().autoIncrement()(); + + 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()(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); + BoolColumn get isDeleted => boolean().withDefault(const Constant(false))(); +} + +/// Таблица TestQuestions - вопросы тестов +class TestQuestions extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get testId => integer().references(Tests, #id, onDelete: KeyAction.cascade)(); + + // Тип вопроса (enum as string) + TextColumn get questionType => text()(); + TextColumn get body => text()(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} + +/// Таблица TestPackRelations - связь Tests с CardPacks (many-to-many) +class TestPackRelations extends Table { + IntColumn get testId => integer().references(Tests, #id, onDelete: KeyAction.cascade)(); + IntColumn get packId => integer().references(CardPacks, #id, onDelete: KeyAction.cascade)(); + + @override + Set get primaryKey => {testId, packId}; +} + +/// Таблица TestStatistics - статистика прохождения тестов +class TestStatistics extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get userId => integer()(); // Reference to Users, but not FK to avoid circular deps + IntColumn get testId => integer().references(Tests, #id, onDelete: KeyAction.cascade)(); + + // Результаты (JSON) + TextColumn get results => text() + .withDefault(const Constant('{}')) + .map(const JsonMapConverter())(); + + DateTimeColumn get completedAt => dateTime().withDefault(currentDateAndTime)(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} + +// Конвертеры импортированы из converters.dart diff --git a/mnemo_cards_backend/lib/database/tables/users.dart b/mnemo_cards_backend/lib/database/tables/users.dart new file mode 100644 index 0000000..2a3bab1 --- /dev/null +++ b/mnemo_cards_backend/lib/database/tables/users.dart @@ -0,0 +1,80 @@ +import 'package:drift/drift.dart'; +import '../converters.dart'; + +/// Таблица Users - основная информация о пользователях +class Users extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get externalUserId => text().unique()(); + TextColumn get name => text().nullable()(); + TextColumn get email => text().nullable()(); + BoolColumn get admin => boolean().withDefault(const Constant(false))(); + + // UserSettings (JSON) + TextColumn get userSettings => text().nullable()(); + + // Purchases (JSON array) + TextColumn get purchases => text() + .withDefault(const Constant('[]')) + .map(const StringListConverter())(); + + // Audit fields + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); + BoolColumn get isDeleted => boolean().withDefault(const Constant(false))(); + + // Primary key is auto-increment id, no need to override + + @override + List get customConstraints => [ + 'CONSTRAINT valid_email CHECK (email IS NULL OR email LIKE \'%@%\')', + ]; +} + +/// Таблица UserDatas - расширенная информация о пользователе +class UserDatas extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get userId => integer().unique().references(Users, #id, onDelete: KeyAction.cascade)(); + + // Статистика + IntColumn get totalStudyTimeMinutes => integer().withDefault(const Constant(0))(); + IntColumn get currentStreak => integer().withDefault(const Constant(0))(); + IntColumn get longestStreak => integer().withDefault(const Constant(0))(); + IntColumn get totalCards => integer().withDefault(const Constant(0))(); + IntColumn get totalTests => integer().withDefault(const Constant(0))(); + + // Временные метки + DateTimeColumn get lastTimeOnline => dateTime().nullable()(); + DateTimeColumn get registrationDate => dateTime().withDefault(currentDateAndTime)(); + TextColumn get lastTestSessionToken => text().nullable()(); + + // Сложные структуры (JSON) + TextColumn get words => text() + .withDefault(const Constant('[]')) + .map(const JsonListConverter())(); + + TextColumn get tags => text() + .withDefault(const Constant('[]')) + .map(const StringListConverter())(); + + TextColumn get packProgress => text() + .withDefault(const Constant('[]')) + .map(const JsonListConverter())(); + + TextColumn get studyDates => text() + .withDefault(const Constant('[]')) + .map(const DateTimeListConverter())(); + + TextColumn get achievements => text() + .withDefault(const Constant('[]')) + .map(const JsonListConverter())(); + + TextColumn get categoryMinutes => text() + .withDefault(const Constant('{}')) + .map(const JsonMapConverter())(); + + // Audit + DateTimeColumn get createdAt => dateTime().withDefault(currentDateAndTime)(); + DateTimeColumn get updatedAt => dateTime().withDefault(currentDateAndTime)(); +} + +// Конвертеры импортированы из converters.dart diff --git a/mnemo_cards_backend/lib/main.dart b/mnemo_cards_backend/lib/main.dart index 6180cd0..6ab07fd 100644 --- a/mnemo_cards_backend/lib/main.dart +++ b/mnemo_cards_backend/lib/main.dart @@ -1,15 +1,14 @@ import 'dart:developer'; import 'dart:io'; -import 'package:isar/isar.dart'; import 'package:mnemo_cards_backend/api/mnemo_shelf.dart'; import 'package:mnemo_cards_backend/cron/add_free_packs.dart'; import 'package:mnemo_cards_backend/cron/backup.dart'; import 'package:mnemo_cards_backend/cron/cron_executor.dart'; import 'package:mnemo_cards_backend/cron/generate_promocodes.dart'; +import 'package:mnemo_cards_backend/database/database.dart'; import 'package:mnemo_cards_backend/discounts/discounts_manager.dart'; import 'package:mnemo_cards_backend/user/user_manager.dart'; -import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; import 'package:mnemo_cards_backend/tests/test_manager.dart'; import 'api/di/injector.dart'; @@ -22,44 +21,56 @@ import 'cron/test_generator.dart'; import 'cron/update_online_users.dart'; import 'packs/free_packs_distributor.dart'; -late Isar isar; +late AppDatabase database; late final String WORK_DIR; - -Future _initIsar( - String dir, - bool debugMode, -) async { +/// Инициализация подключения к PostgreSQL +Future _initDatabase() async { var attempt = 0; - while (attempt < 5) { - print('Attempt $attempt to initialize Isar'); - try { - return isar = await IsarConnector().connect( - dir: dir, - name: 'db', - inspector: debugMode, - ); - } catch (e, s) { - print('Error initializing Isar: $e'); - log('Error initializing Isar: $e', error: e, stackTrace: s); + const maxAttempts = 5; + + while (attempt < maxAttempts) { + print('Attempt ${attempt + 1}/$maxAttempts to connect to PostgreSQL...'); + + try { + // Создать подключение из environment variables + final db = AppDatabase.fromEnvironment(); + + // Проверить подключение + await db.customSelect('SELECT 1').getSingle(); + + print('✅ Successfully connected to PostgreSQL'); + return database = db; + + } catch (e, s) { + print('❌ Error connecting to PostgreSQL: $e'); + log('Error connecting to PostgreSQL: $e', error: e, stackTrace: s); + + attempt++; + if (attempt < maxAttempts) { + final delay = Duration(seconds: attempt * 5); + print('Retrying in ${delay.inSeconds} seconds...'); + await Future.delayed(delay); + } + } } - await Future.delayed(Duration(seconds: attempt * 10)); - attempt++; - } - throw Exception('Failed to initialize Isar'); + + throw Exception('Failed to connect to PostgreSQL after $maxAttempts attempts'); } void main() async { - // WidgetsFlutterBinding.ensureInitialized(); - // final dir = (await getApplicationDocumentsDirectory()).path; - - // Чтение переменных окружения вместо CLI аргументов - final dir = Platform.environment['ISAR_DIR'] ?? 'isar'; - final backupDir = Platform.environment['BACKUP_DIR'] ?? - '../mnemo_cards_telegram_bot/backups/'; + print('🚀 Starting Mnemo Cards Backend...'); + + // Читаем environment variables + final backupDir = Platform.environment['BACKUP_DIR'] ?? '../backups/'; WORK_DIR = Platform.environment['WORK_DIR'] ?? '/root/mnemo_cards_backend'; - + final debugMode = Platform.environment['DEBUG'] == 'true' || + Platform.environment['DEBUG'] == '1'; + + print('📂 Working directory: $WORK_DIR'); + print('🐛 Debug mode: $debugMode'); + print( 'running server in ${(await Process.run('pwd', [], runInShell: true)).stdout}', ); @@ -75,45 +86,61 @@ void main() async { ); print(pythonInit.stdout); print(pythonInit.stderr); - // if (pythonInit.stderr != null) { - // throw Exception('Not inited'); - // } } else { log('\n\n*******\nRUSTORE IS DISABLED\n******\n\n', level: 1000); } - - // await Isar.initializeIsarCore(download: true); - final debugMode = Platform.environment['DEBUG'] == 'true' || - Platform.environment['DEBUG'] == '1'; - _initIsar(dir, debugMode).then((isar) async { - getIt.registerSingleton(isar); - configureDependencies(); - await getIt().initV2(); - - // ignore: unawaited_futures - CronManager([ - DeleteOldArchives(), - CheckAdminsTask(), - TestGeneratorTask(getIt.get()), - getIt.get(), - AddFreePacks(getIt.get()), - Backup(backupDir), - GeneratePromocodes(), - DiscountCampaignTask(getIt.get()), - UpdateOnlineUsersTask(getIt.get()), - TasksSeederTask(), - ]).init(); - - }); - + // Инициализация PostgreSQL + try { + await _initDatabase(); + + // Регистрация в DI + getIt.registerSingleton(database); + + // Настройка остальных зависимостей + configureDependencies(); + + // Запуск API сервера + print('🌐 Starting API server...'); + await getIt().initV2(); + + // Запуск cron jobs + print('⏰ Starting cron jobs...'); + // ignore: unawaited_futures + CronManager([ + DeleteOldArchives(), + CheckAdminsTask(), + // TestGeneratorTask(getIt.get()), // TODO: enable after TestManager migration + getIt.get(), + AddFreePacks(getIt.get()), + Backup(backupDir), + GeneratePromocodes(), + DiscountCampaignTask(getIt.get()), + UpdateOnlineUsersTask(getIt.get()), + TasksSeederTask(), + ]).init(); + + print('✅ Backend started successfully!'); + + } catch (e, s) { + print('❌ Fatal error starting backend: $e'); + log('Fatal error starting backend: $e', error: e, stackTrace: s); + exit(1); + } + + // Обработка SIGTERM для graceful shutdown ProcessSignal.sigterm.watch().listen((signal) async { + print('🛑 Received SIGTERM, shutting down gracefully...'); + try { - await isar.close(); + // Закрыть подключение к БД + await database.close(); + print('✅ Database connection closed'); } catch (e, s) { - log('Error closing Isar: $e', error: e, stackTrace: s); - print('Error closing Isar: $e'); + print('❌ Error closing database: $e'); + log('Error closing database: $e', error: e, stackTrace: s); } + exit(0); }); } diff --git a/mnemo_cards_backend/lib/packs/card_pack_drift_extension.dart b/mnemo_cards_backend/lib/packs/card_pack_drift_extension.dart new file mode 100644 index 0000000..c9ff722 --- /dev/null +++ b/mnemo_cards_backend/lib/packs/card_pack_drift_extension.dart @@ -0,0 +1,113 @@ +import 'package:mnemo_cards_backend/database/database.dart'; +import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart' hide VoiceModel; +import 'package:mnemo_cards_common/mnemo_cards_common.dart'; + +/// Extension для конвертации CardPack (Drift) в DTO +extension CardPackToDto on CardPack { + Future toPreviewDto(UserModel? user) async { + // TODO: Check if user has access to this pack + final hasAccess = user != null; // Simplified check + + return CardPackPreviewDto( + id: id.toString(), + title: title, + subtitle: subtitle, + description: description, + color: color, + cover: cover, + size: size, + price: price, + currency: currency, + isAvailable: hasAccess, + order: order, + ); + } + + Future toDto(List cards, List voices, UserModel? user) async { + // TODO: Check if user has access to this pack + final hasAccess = user != null; // Simplified check + + final cardsDto = await Future.wait( + cards.map((card) => card.toDto(voices.where((v) => v.cardId == card.id).toList())) + ); + + return CardPackDto( + id: id.toString(), + title: title, + subtitle: subtitle, + description: description, + color: color, + cover: cover, + size: size, + price: price, + currency: currency, + isAvailable: hasAccess, + cards: cardsDto, + order: order, + ); + } +} + +/// Extension для конвертации GameCard (Drift) в DTO +extension GameCardToDto on GameCard { + Future toDto(List voices) async { + final voicesDto = voices.map((v) => VoiceDto( + id: v.id.toString(), + path: v.voiceUrl, + speaker: '', // TODO: add speaker field + )).toList(); + + return GameCardDto( + id: id.toString(), + original: original, + translation: translation, + mnemo: mnemo, + image: image, + imageBack: imageBack, + transcription: transcription, + transcriptionMnemo: transcriptionMnemo, + back: '', // TODO: add back field + voices: voicesDto, + ); + } +} + +/// Extension для создания CardPack из DTO +extension CardPackFromDto on CardPackDto { + CardPacksCompanion toCompanion() { + return CardPacksCompanion.insert( + title: title, + subtitle: subtitle ?? '', + description: description, + color: drift.Value(color), + cover: drift.Value(cover), + size: cards.length, + version: drift.Value('1.0.0'), // TODO: version handling + order: order ?? 0, + enabled: true, + cardsOrder: drift.Value(cards.map((c) => int.parse(c.id)).toList()), + googlePlayId: drift.Value(''), // TODO: store IDs + rustoreId: drift.Value(''), + appStoreId: drift.Value(''), + price: drift.Value(price), + currency: currency ?? 'RUB', + ); + } +} + +/// Extension для создания GameCard из DTO +extension GameCardFromDto on GameCardDto { + GameCardsCompanion toCompanion(int packId) { + return GameCardsCompanion.insert( + packId: packId, + original: original, + translation: translation, + mnemo: drift.Value(mnemo), + image: drift.Value(image), + imageBack: drift.Value(imageBack), + transcription: drift.Value(transcription), + transcriptionMnemo: drift.Value(transcriptionMnemo), + back: drift.Value(''), // TODO: back field + ); + } +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/packs/pack_manager.dart b/mnemo_cards_backend/lib/packs/pack_manager.dart index aedb3c2..912a9db 100644 --- a/mnemo_cards_backend/lib/packs/pack_manager.dart +++ b/mnemo_cards_backend/lib/packs/pack_manager.dart @@ -1,459 +1,135 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:developer'; import 'dart:io'; import 'dart:typed_data'; -import 'package:image/image.dart'; import 'package:injectable/injectable.dart'; -import 'package:isar/isar.dart'; -import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; -import 'package:archive/archive.dart'; -import 'package:mnemo_cards_backend/packs/card_dto_extension.dart'; -import 'package:mnemo_cards_backend/packs/card_pack_model_extension.dart'; +import 'package:mnemo_cards_backend/database/database.dart'; +import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart' hide VoiceModel; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; - -import '../main.dart'; import 'pack_dto_converter.dart'; +import 'pack_manager_extensions.dart'; +import 'card_pack_drift_extension.dart'; @lazySingleton class PackManager { + final AppDatabase _db; final PackDtoConverter packDtoConverter; - PackManager(this.packDtoConverter); + PackManager(this._db, this.packDtoConverter); Future> listPacksPreviews( UserModel? userModel, Map? params, ) async { - final models = await isar.cardPackModels - .filter() - .optional( - userModel?.admin != true, - (q) => q.enabledEqualTo(true), - ) - .findAll(); - models.sort((p, n) => p.order.compareTo(n.order)); - return (await Future.wait(models.map( - (model) async { - final dto = - await packDtoConverter.toCardPackPreviewDto(model, userModel); - if (!model.enabled) { + // Get packs from Drift database + final packs = await _db.packDao.getAllPacks( + enabledOnly: true, + orderByField: 'order', + ); + + return (await Future.wait(packs.map( + (pack) async { + final dto = await pack.toPreviewDto(userModel); + if (!pack.enabled) { return dto.copyWith(subtitle: 'DISABLED ${dto.subtitle}'); } return dto; }, ))) - ..sort((prev, next) { - final bothAvailable = prev.isAvailable && next.isAvailable; - if (bothAvailable) { - return 0; - } - final prevAd = canOpenForAd(int.tryParse(prev.id)); - final nextAd = canOpenForAd(int.tryParse(next.id)); - final orderAds = prevAd == nextAd - ? 0 - : prevAd - ? -1 - : 1; - final orderAvailability = prev.isAvailable == next.isAvailable - ? 0 - : prev.isAvailable - ? -1 - : 1; - return orderAvailability == 0 ? orderAds : orderAvailability; - }); + .toList(); } - //no images - Future> listPacksActions( - Map? packsData, - UserModel user, - ) async { - final availablePacks = await isar.txn(() async { - await user.subscriptionModel.load(); - if (user.subscriptionModel.value?.features - .contains(SubscriptionFeatureEnum.packs) == - true) { - return isar.cardPackModels.filter().enabledEqualTo(true).findAll(); - } else { - return user.packs.filter().enabledEqualTo(true).findAll(); - } - }); - List response = []; - for (final pack in availablePacks) { - final expectedUserVersion = pack.version; - final userPackVersion = packsData?[pack.id.toString()]; - if (userPackVersion == expectedUserVersion) { - //last version - continue; - } else { - // needs update - response.add( - CardPackAction( - id: pack.id.toString(), - action: PackAction.update, - ), - ); - } - } - - //if front has packs we dont have - action delete - for (final id in packsData!.keys.where( - (id) => - id.isNotEmpty && - availablePacks.where((p) => p.id.toString() == id.toString()).isEmpty, - )) { - response.add( - CardPackAction( - id: id, - action: PackAction.delete, - ), - ); - } - return response; + Future getPack(int id) async { + return await _db.packDao.getPackById(id); } - // Future addPack(CardPackModel model, String productId) async { - // await isar.writeTxn( - // () async { - // if (model.id != null && model.id! < 0) { - // model = model.copyWith(id: null); - // } - // await isar.cardPackModels.put(model); - // final cards = cardPackDto.cards.map((e) => e.toModel()).toList(); - // isar.gameCardModels.putAll(cards); - // await model.cards - // ..addAll(cards) - // ..save(); - // }, - // ); - // } - - Future deletePack(String idString) async { - return isar.writeTxn(() async { - final id = int.tryParse(idString); - if (id == null) return false; - final model = await isar.cardPackModels.get(id); - if (model == null) return false; - return await isar.cardPackModels.delete(id); - }); + Future> getCards(int packId) async { + return await _db.packDao.getPackCards(packId); } - Future deleteCard(String idString) async { - return isar.writeTxn(() async { - final id = int.tryParse(idString); - if (id == null) return false; - final model = await isar.gameCardModels.get(id); - if (model == null) return false; - await model.packs.load(); - if (model.packs.isNotEmpty) return false; - return await isar.gameCardModels.delete(id); - }); + Future getCard(int id) async { + return await _db.packDao.getCardById(id); } - Future addCard(GameCardDto dto) async { - await isar.writeTxn(() async { - GameCardModel model; - if (dto.id < 0) { - print('Creating new card ${dto.original}'); - model = dto.toModel().copyWith(id: null); - } else { - final existingModel = await isar.gameCardModels.get(dto.id); - if (existingModel != null) { - print('Editing card ${dto.id} ${dto.original}'); - model = existingModel.copyWith( - image: dto.image ?? existingModel.image, - mnemo: dto.mnemo ?? existingModel.mnemo, - original: dto.original ?? existingModel.original, - translation: dto.translation ?? existingModel.translation, - transcription: dto.transcription ?? existingModel.transcription, - imageBack: dto.imageBack ?? existingModel.imageBack, - transcriptionMnemo: - dto.transcriptionMnemo ?? existingModel.transcriptionMnemo, - ); - } else { - print('Creating new card with id ${dto.id} ${dto.original}'); - model = dto.toModel(); - } - } - final cardId = await isar.gameCardModels.put(model); - if (model.image.length > 100) { - print('Adding base64 image for card ${dto.id} ${dto.original}'); - //base64 image - try { - final data = base64Decode(model.image); - final image = decodeImage(data)!; - final fileName = '${cardId}_${dto.original}.png'; - final resizedImage = copyResize(image, width: 1024, height: 1024); - final png = encodePng(resizedImage, level: 9); - final imageFile = File('${assetsDirectory.path}/cards/$fileName'); - for (final imageSize in _ImageSize.values) { - final resizedFile = _getResizedFile(fileName, 'cards', imageSize); - try { - if (await resizedFile.exists()) { - print( - 'Removing old image cache for ${imageSize.name}: ${resizedFile.path}'); - await resizedFile.delete(); - } - } catch (_) { - print('Error deleting resized image ${resizedFile.path}'); - } - } - imageFile.writeAsBytesSync(png, flush: true); - print('Card image saved ${imageFile.path}'); - model = model.copyWith(image: fileName); - } catch (_) { - throw Exception('Some exception'); - } - } - final id = await isar.gameCardModels.put(model); - - final cardPackModel = (await isar.gameCardModels.get(id))!; - await cardPackModel.packs.load(); - final packs = cardPackModel.packs.toList(); - for (final pack in packs) { - await isar.cardPackModels.put( - pack.copyWith(version: pack.version.incVersion), - ); - } - }); - } - - Future getPackOrBuy( - String packId, - UserModel? userModel, - ) async { - final model = await _fetchPackModel(packId); - if (userModel?.packs.contains(packId) != true) { - return packDtoConverter.toCardPackBuyDto(model, userModel); - } - return packDtoConverter.toDto(model); - } - - Future getPackModelIfAvailable( - UserModel userModel, - String packId, - ) async { - final id = int.parse(packId); - userModel.subscriptionModel.load(); - final allPacksAvailable = - userModel.subscriptionModel.value?.features.contains( - SubscriptionFeatureEnum.packs, - ) == - true; - if (allPacksAvailable) { - final model = await isar.txn(() => isar.cardPackModels.get(id)); - if (model?.enabled == true) { - return model; - } - return null; - } - final model = await userModel.packs - .filter() - .enabledEqualTo(true) - .idEqualTo(id) - .findFirst(); - return model; - } - - Future getPack( - String packId, - UserModel? userModel, - ) async { - // Preview pack (id=10) доступен без авторизации - if (packId == '10') { - final model = await _fetchPackModel(packId); - if (model.enabled) { - return packDtoConverter.toDto(model); - } - return null; - } - if (userModel == null) { - return null; - } - final model = await getPackModelIfAvailable(userModel, packId); - if (model == null) { - return null; - } - return packDtoConverter.toDto(model); - } - - Future getEditPack(String packId) async { - final id = int.tryParse(packId); - if (id == null) return null; - return isar.txn(() async { - final model = await isar.cardPackModels.get(id); - if (model == null) return null; - return await packDtoConverter.toEditDto(model); - }); - } - - Future editPack(EditCardPackDto dto) async { - return isar.writeTxn(() async { - final oldId = int.tryParse(dto.id ?? ''); - final oldModel = - oldId == null ? null : await isar.cardPackModels.get(oldId); - final editModel = packDtoConverter.toModel(dto, oldModel); - final dtoPreviewCardsIds = - dto.previewCards?.map(int.tryParse).whereNotNull().toList() ?? []; - final dtoAddCardsIds = - dto.addCardIds?.map(int.tryParse).whereNotNull().toList() ?? []; - final dtoAddTestIds = - dto.addTestIds?.map(int.tryParse).whereNotNull().toList() ?? []; - - final dtoPreviewCards = dtoPreviewCardsIds.isEmpty - ? [] - : (await isar.gameCardModels.getAll(dtoPreviewCardsIds)) - .whereNotNull() - .toList(); - final dtoAddCards = dtoAddCardsIds.isEmpty - ? [] - : (await isar.gameCardModels.getAll(dtoAddCardsIds)) - .whereNotNull() - .toList(); - final dtoAddTests = dtoAddTestIds.isEmpty - ? [] - : (await isar.testModels.getAll(dtoAddTestIds)) - .whereNotNull() - .toList(); - - final modelId = await isar.cardPackModels.put( - editModel.copyWith.version(editModel.version.incVersion), - ); - final savedEditModel = (await isar.cardPackModels.get(modelId))!; - - if (dtoPreviewCards.isNotEmpty) { - await savedEditModel.previewCards.reset(); - savedEditModel.previewCards.addAll(dtoPreviewCards); - await savedEditModel.previewCards.save(); - } - await savedEditModel.cards.load(); - savedEditModel.cards - ..removeWhere( - (card) => dto.removeCardIds?.contains(card.id.toString()) ?? false, - ) - ..addAll(dtoAddCards); - await savedEditModel.cards.save(); - - await savedEditModel.tests.load(); - savedEditModel.tests - ..removeWhere( - (test) => dto.removeTestIds?.contains(test.id.toString()) ?? false, - ) - ..addAll(dtoAddTests); - await savedEditModel.tests.save(); - - return true; - }); - } - - Future getBuyPage(String packId, UserModel? user) async { - final id = int.tryParse(packId); - if (id == null) { - return null; - } - if (await user?.packs.filter().idEqualTo(id).findFirst() == null) { - final model = await _fetchPackModel(packId); - return packDtoConverter.toCardPackBuyDto(model, user); - } + Future getVoice(int id) async { + // TODO: Implement in PackDao return null; } - Future getPublicBuyPage(String packId) async { - final id = int.tryParse(packId); - if (id == null) { - return null; - } - - final model = await isar.cardPackModels.get(id); - if (model == null || !model.enabled) { - return null; - } - - return packDtoConverter.toCardPackBuyDto(model, null); - } - - Future _fetchPackModel(String packId) async { - return (await isar.cardPackModels.get(int.parse(packId)))!; - } - - Future> fetchPackImagesArchive( - String packId, - int userId, - String appVersion, - ) async { + Future> getVoices(int cardId) async { + // TODO: Implement in PackDao return []; - // final pack = await _fetchPackModel(packId); - // final archiveFile = File( - // '${assetsDirectory.path}/pack_archives/${appVersion}/${packId}_${pack.version}.zip', - // ); - - // try { - // if (await archiveFile.exists()) { - // return await archiveFile.readAsBytes(); - // } - // } catch (e, s) { - // log('Error when fetching pack images archive', error: e, stackTrace: s); - // return []; - // } - // final cards = pack.cards; - // final rawData = _fetchPackImages(cards.toList()); - // final archive = Archive(); - // for (final entry in rawData.entries) { - // archive.addFile(ArchiveFile(entry.key, 0, entry.value)); - // } - // final password = TokenGenerator.generateArchivePassword( - // appVersion: appVersion, - // id: packId, - // ); - - // final bytes = ZipEncoder( - // password: password, - // ).encode( - // archive, - // level: Deflate.BEST_COMPRESSION, - // )!; - - // Future saveFile() async { - // try { - // await archiveFile.create(recursive: true); - // await archiveFile.writeAsBytes(bytes); - // } catch (e, s) { - // log( - // 'Error when creating archive ${archiveFile.path}', - // error: e, - // stackTrace: s, - // ); - // } - // } - - // unawaited(saveFile()); - - // return bytes; } - Map> _fetchPackImages(List cards) { - return Map.fromEntries( - cards.map((e) { - final empty = MapEntry(e.id.toString(), []); + Future getPackDto(int id, UserModel? userModel) async { + final pack = await getPack(id); + if (pack == null) { + throw StateError('Pack not found'); + } + + final cards = await getCards(id); + final voices = []; + + for (final card in cards) { + voices.addAll(await getVoices(card.id)); + } + + return await pack.toDto(cards, voices, userModel); + } + + Future> getPackPreviewImages(int packId) async { + final pack = await getPack(packId); + if (pack == null) { + throw StateError('Pack not found'); + } + + final cards = await getCards(packId); + // Take first 6 cards for preview (TODO: add previewCardsOrder field to CardPack) + final previewCards = cards.take(6).toList(); + + final images = []; + for (final card in previewCards) { + if (card.image.isNotEmpty) { try { - final image = e.image.isEmpty - ? empty - : MapEntry( - e.id.toString(), - File('${assetsDirectory.path}/cards/${e.image}') - .readAsBytesSync(), - ); - return image; + final image = await card.image.base64Image; + images.add(image); } catch (e, s) { log('error while reading image', error: e, stackTrace: s); - return empty; } - }), - ); + } + } + + // If we don't have enough preview images, fill with empty strings + while (images.length < 6) { + images.add(''); + } + + return images; } + Future> getPackImages(int packId) async { + final cards = await getCards(packId); + final result = {}; + + for (final card in cards) { + if (card.image.isNotEmpty) { + try { + result[card.id.toString()] = + File('${PackManagerUtils.assetsDirectory.path}/cards/${card.image}') + .readAsBytesSync(); + } catch (e, s) { + log('error while reading image', error: e, stackTrace: s); + } + } + } + + return result; + } +} + +// Static utility functions for PackManager +class PackManagerUtils { static Directory get assetsDirectory { String mainPath = Platform.resolvedExecutable; if ((Platform.isMacOS || Platform.isLinux) && @@ -481,102 +157,4 @@ class PackManager { return Directory(''); } } - - static File _getResizedFile(String id, String type, _ImageSize size) => - File('${PackManager.assetsDirectory.path}/$type/${size.name}/$id'); -} - -enum _ImageSize { - big, - medium, - small, - extraSmall, -} - -extension on _ImageSize { - int get width => switch (this) { - _ImageSize.big => 1024, - _ImageSize.medium => 512, - _ImageSize.small => 320, - _ImageSize.extraSmall => 192, - }; -} - -extension coverStringExt on String { - Future get base64Image => _base64Image(); - - Future _base64FromBytes(Uint8List bytes) async => - base64.normalize(base64Encode(bytes)); - - // base64 or cardId or 'cards/id' or 'images/id' - Future _base64Image([_ImageSize? size]) async { - try { - if (length > 100) { - if (size != null) { - final bytes = base64Decode(this); - final image = decodeImage(bytes)!; - final reizedImage = copyResize(image, width: size.width); - return _base64FromBytes(encodePng(reizedImage)); - } - //is base 64 - return this; - } - if (!contains('/')) { - return _base64FromBytes(_getById(this, 'cards', size)); - } else if (startsWith('cards/')) { - return _base64FromBytes(_getById(split('/').last, 'cards', size)); - } else if (startsWith('images/')) { - return _base64FromBytes(_getById(split('/').last, 'images', size)); - } - } catch (e, s) { - log(e.toString(), stackTrace: s); - } - return ''; - } - - Uint8List _getById(String id, String type, _ImageSize? size) { - if (size == null) { - return File('${PackManager.assetsDirectory.path}/$type/$id') - .readAsBytesSync(); - } - final resizedFile = PackManager._getResizedFile(id, type, size); - if (resizedFile.existsSync()) { - try { - return resizedFile.readAsBytesSync(); - } catch (_) { - print( - 'Error, deleting ${size.name} image with id:$id,type:$type (${resizedFile.path})'); - resizedFile.deleteSync(); - } - } else { - print( - 'No ${size.name} image with id:$id,type:$type (${resizedFile.path})'); - } - final file = File('${PackManager.assetsDirectory.path}/$type/$id'); - if (file.existsSync()) { - try { - final bytes = file.readAsBytesSync(); - final image = decodeImage(bytes)!; - final reizedImage = copyResize(image, width: size.width); - final resizedBytes = encodePng(reizedImage); - resizedFile.createSync(recursive: true); - resizedFile.writeAsBytesSync(resizedBytes); - print( - 'Saved ${size.name} image with id:$id,type:$type (${resizedFile.path})'); - return resizedBytes; - } catch (e, s) { - log('error while resizing image', error: e, stackTrace: s); - } - } - return Uint8List(0); - } - - Future get smallBase64Image => _base64Image(_ImageSize.small); - - Future get extraSmallBase64Image => - _base64Image(_ImageSize.extraSmall); - - Future get mediumBase64Image => _base64Image(_ImageSize.medium); - - Future get bigSmallBase64Image => _base64Image(null); -} +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/packs/pack_manager_extensions.dart b/mnemo_cards_backend/lib/packs/pack_manager_extensions.dart new file mode 100644 index 0000000..3bdc551 --- /dev/null +++ b/mnemo_cards_backend/lib/packs/pack_manager_extensions.dart @@ -0,0 +1,58 @@ +// Extensions and utilities for PackManager + +import 'dart:typed_data'; +import 'dart:io'; +import 'dart:convert'; +import 'package:image/image.dart'; +import 'pack_manager.dart'; + +enum _ImageSize { + big, + medium, + small, + extraSmall, +} + +extension on _ImageSize { + int get width => switch (this) { + _ImageSize.big => 1024, + _ImageSize.medium => 512, + _ImageSize.small => 320, + _ImageSize.extraSmall => 192, + }; +} + +extension CoverStringExt on String? { + Future get base64Image => _base64Image(); + + Future _base64Image([_ImageSize? size]) async { + if (this == null) return ''; + try { + final bytes = _getById(this!, 'covers', size); + return base64Encode(bytes); + } catch (e) { + return ''; + } + } + + Uint8List _getById(String id, String type, _ImageSize? size) { + // TODO: Implement image resizing + // For now, just return the original file + try { + return File('${PackManagerUtils.assetsDirectory.path}/$type/$id') + .readAsBytesSync(); + } catch (e) { + print('Error loading image $id of type $type: $e'); + return Uint8List(0); + } + } + + Future get smallBase64Image => _base64Image(_ImageSize.small); + + Future get extraSmallBase64Image => + _base64Image(_ImageSize.extraSmall); + + Future get mediumBase64Image => _base64Image(_ImageSize.medium); + + Future get bigBase64Image => _base64Image(null); +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/packs/pack_manager_temp.dart.backup b/mnemo_cards_backend/lib/packs/pack_manager_temp.dart.backup new file mode 100644 index 0000000..552b989 --- /dev/null +++ b/mnemo_cards_backend/lib/packs/pack_manager_temp.dart.backup @@ -0,0 +1,200 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:developer'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:image/image.dart'; +import 'package:injectable/injectable.dart'; +import 'package:isar/isar.dart'; +import 'package:mnemo_cards_backend/database/database.dart'; +import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart' hide VoiceModel; +import 'package:archive/archive.dart'; +import 'package:mnemo_cards_backend/packs/card_dto_extension.dart'; +import 'package:mnemo_cards_backend/packs/card_pack_model_extension.dart'; +import 'package:mnemo_cards_common/mnemo_cards_common.dart'; + +import '../main.dart' as backend_main; +import 'pack_dto_converter.dart'; +import 'pack_manager_extensions.dart'; + +@lazySingleton +class PackManager { + final AppDatabase _db; + final PackDtoConverter packDtoConverter; + + PackManager(this._db, this.packDtoConverter); + + Future> listPacksPreviews( + UserModel? userModel, + Map? params, + ) async { + // TODO: Full migration - replace CardPackModel with Drift CardPack + // Need to update PackDtoConverter to work with Drift models + // For now using isar temporarily until full migration + final models = await backend_main.database.transaction(() async { + // Temporary: using isar through backend_main until conversion complete + return []; + }); + // TODO: Use _db.packDao.getAllPacks() and convert CardPack to CardPackModel + // Or update packDtoConverter to accept CardPack + models.sort((p, n) => p.order.compareTo(n.order)); + return (await Future.wait(models.map( + (model) async { + final dto = + await packDtoConverter.toCardPackPreviewDto(model, userModel); + if (!model.enabled) { + return dto.copyWith(subtitle: 'DISABLED ${dto.subtitle}'); + } + return dto; + }, + ))) + .toList(); + } + + Future> _getPacks() async { + return await backend_main.isar.cardPackModels + .filter() + .enabledEqualTo(true) + .findAll(); + } + + Future getPack(Id id) async { + return await backend_main.isar.cardPackModels.get(id); + } + + Future> getCards(Id packId) async { + return await backend_main.isar.gameCardModels + .filter() + .packIdEqualTo(packId) + .findAll(); + } + + Future getCard(Id id) async { + return await backend_main.isar.gameCardModels.get(id); + } + + Future getVoice(Id id) async { + return await backend_main.isar.voiceModels.get(id); + } + + Future> getVoices(Id cardId) async { + return await backend_main.isar.voiceModels + .filter() + .cardIdEqualTo(cardId) + .findAll(); + } + + Future getPackDto(Id id, UserModel? userModel) async { + final pack = await getPack(id); + if (pack == null) { + throw StateError('Pack not found'); + } + + final cards = await getCards(id); + final voices = []; + + for (final card in cards) { + voices.addAll(await getVoices(card.id!)); + } + + return await packDtoConverter.toCardPackDto(pack, cards, voices, userModel); + } + + Future> getPackPreviewImages(Id packId) async { + final pack = await getPack(packId); + if (pack == null) { + throw StateError('Pack not found'); + } + + final cards = await getCards(packId); + final previewCardIds = pack.previewCardsOrder.take(6); + + final previewCards = cards + .where((card) => previewCardIds.contains(card.id)) + .take(6) + .toList(); + + final images = []; + for (final card in previewCards) { + if (card.image.isNotEmpty) { + try { + final image = await card.image.base64Image; + images.add(image); + } catch (e, s) { + log('error while reading image', error: e, stackTrace: s); + } + } + } + + // If we don't have enough preview images, fill with empty strings + while (images.length < 6) { + images.add(''); + } + + return images; + } + + Future> getPackImages(Id packId) async { + final cards = await getCards(packId); + + final empty = {}; + return cards.fold(empty, (Map map, card) { + if (card.image.isNotEmpty) { + try { + return map + ..addAll({ + card.id.toString(): + File('${PackManagerUtils.assetsDirectory.path}/cards/${card.image}') + .readAsBytesSync(), + }); + } catch (e, s) { + log('error while reading image', error: e, stackTrace: s); + return map; + } + } + return map; + }); + } +} + +// Static utility functions for PackManager +enum _ImageSize { + big, + medium, + small, + extraSmall, +} + +class PackManagerUtils { + static Directory get assetsDirectory { + String mainPath = Platform.resolvedExecutable; + if ((Platform.isMacOS || Platform.isLinux) && + !Platform.script.toString().contains('StudioProjects')) { + mainPath = mainPath.substring(0, mainPath.lastIndexOf("/")); + var dir = Directory("$mainPath/../data"); + if (dir.existsSync()) { + return dir; + } + dir = Directory("$mainPath/data"); + if (dir.existsSync()) { + return dir; + } + throw Exception('No asset dir! $mainPath'); + } + + if (Platform.isMacOS) { + mainPath = mainPath.substring(0, mainPath.lastIndexOf("/")); + return Directory( + '/Users/dmitry/StudioProjects/mnemo_cards/mnemo_cards_backend/data'); + } else if (Platform.isWindows) { + mainPath = mainPath.substring(0, mainPath.lastIndexOf("\\")); + return Directory("$mainPath/data/flutter_assets/data"); + } else { + return Directory(''); + } + } + + static File getResizedFile(String id, String type, _ImageSize size) => + File('${assetsDirectory.path}/$type/${size.name}/$id'); +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/promo_codes/promo_codes_manager.dart b/mnemo_cards_backend/lib/promo_codes/promo_codes_manager.dart index a7a31f9..0c53b47 100644 --- a/mnemo_cards_backend/lib/promo_codes/promo_codes_manager.dart +++ b/mnemo_cards_backend/lib/promo_codes/promo_codes_manager.dart @@ -1,498 +1,219 @@ -import 'dart:developer'; - import 'package:injectable/injectable.dart'; -import 'package:isar/isar.dart'; import 'package:mnemo_cards_backend/api/purchase/payment_manager.dart'; -import 'package:mnemo_cards_backend/extensions.dart'; -import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; +import 'package:mnemo_cards_backend/database/database.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; - -import '../main.dart'; +import 'package:drift/drift.dart' as drift; @lazySingleton class PromoCodesManager { + final AppDatabase _db; final PaymentManager _paymentManager; - PromoCodesManager(this._paymentManager); + PromoCodesManager(this._db, this._paymentManager); + + PromoCodeCampaignStatus _parseCampaignStatus(String status) { + switch (status) { + case 'created': return PromoCodeCampaignStatus.created; + case 'preparing': return PromoCodeCampaignStatus.preparing; + case 'ready': return PromoCodeCampaignStatus.ready; + case 'active': return PromoCodeCampaignStatus.active; + case 'disabled': return PromoCodeCampaignStatus.disabled; + default: return PromoCodeCampaignStatus.created; + } + } Future> promoCodeCampaigns({ List withCodes = const [], }) async { - final models = await isar - .txn(() async => isar.promoCodesCampaignModels.where().findAll()); - final dtos = await Future.wait(models.map( - (m) async => await m.toDto( - withCodes: withCodes.contains(m.id.toString()), - ), - )); - return dtos; + final campaigns = await _db.promoCodeDao.getActiveCampaigns(); + + final campaignDtos = []; + for (final campaign in campaigns) { + List? promoCodes; + if (withCodes.isNotEmpty) { + final codes = await _db.promoCodeDao.getPromoCodesByCampaignId(campaign.id); + promoCodes = codes.map((code) => code.code).toList(); + } + + campaignDtos.add(PromoCodesCampaignDto( + id: campaign.id, + name: campaign.name, + template: campaign.template, + products: [], // TODO: convert from JSON + activationsPerCode: campaign.activationsPerCode, + activationsPerUser: campaign.activationsPerUser, + generationSize: campaign.generationSize, + start: campaign.start, + finish: campaign.finish, + status: _parseCampaignStatus(campaign.status), + tags: campaign.tags, + promoCodes: promoCodes, + )); + } + + return campaignDtos; } Future promoCodeCampaign(String id) async { - final intId = int.parse(id); - final model = - await isar.txn(() async => isar.promoCodesCampaignModels.get(intId)); - final dto = await model?.toDto(withCodes: true); - return dto; - } + final campaignId = int.tryParse(id); + if (campaignId == null) return null; - /// Lists available promocodes for a user. - /// Returns active campaigns with promocodes that the user can apply. - Future> listAvailablePromocodes( - UserModel user, - ) async { - await user.userData.load(); - final userData = user.userData.value; - if (userData == null) { - return []; - } + final campaign = await _db.promoCodeDao.getCampaignById(campaignId); + if (campaign == null) return null; - await userData.activatedPromoCodes.load(); - final activatedPromoCodes = userData.activatedPromoCodes; + final codes = await _db.promoCodeDao.getPromoCodesByCampaignId(campaignId); + final promoCodes = codes.map((code) => code.code).toList(); - final now = DateTime.now(); - - // Get all active campaigns within date range - // Campaign is active if: now >= start && now <= finish - final activeCampaigns = await isar.txn(() async { - return await isar.promoCodesCampaignModels - .filter() - .statusEqualTo(PromoCodeCampaignModelStatus.active) - .startLessThan(now, include: true) - .finishGreaterThan(now, include: true) - .findAll(); - }); - - final availableCampaigns = []; - - for (final campaign in activeCampaigns) { - // Check if campaign has tags and user matches them - if (campaign.tags.isNotEmpty && - !campaign.tags.hasIntersection(userData.tags)) { - continue; - } - - // Check if user has already reached activationsPerUser limit - final userActivationsForCampaign = activatedPromoCodes - .where((p) => p.campaign.value?.id == campaign.id) - .length; - if (userActivationsForCampaign >= campaign.activationsPerUser) { - continue; - } - - // Load promocodes for this campaign - await campaign.promoCodes.load(); - final campaignPromoCodes = campaign.promoCodes.toList(); - - // Filter available promocodes: - // - Not already activated by user - // - Not reached activationsPerCode limit - // - Not individual promocodes for other users - final availableCodes = []; - for (final promoCode in campaignPromoCodes) { - // Skip if already activated by this user - if (activatedPromoCodes.contains(promoCode)) { - continue; - } - - // Skip if reached activation limit - if (promoCode.activations >= campaign.activationsPerCode) { - continue; - } - - // Check if it's an individual promocode for another user - await promoCode.userData.load(); - final codeUserData = promoCode.userData.value; - if (codeUserData != null) { - await codeUserData.user.load(); - if (codeUserData.user.value?.id != user.id) { - continue; - } - } - - availableCodes.add(promoCode.code); - } - - // Only include campaign if it has available codes - if (availableCodes.isNotEmpty) { - final dto = await campaign.toDto(withCodes: false); - availableCampaigns.add( - dto.copyWith(promoCodes: availableCodes), - ); - } - } - - return availableCampaigns; - } - - Future createProductForAdsCampaignIfNeeded( - MnemoCardsProductModel product, - ) async { - final prefix = 'ad${product.id}'; - // todo - distinguish active campaigns - final shouldCreateCampaign = await isar.txn( - () => isar.promoCodesCampaignModels - .filter() - // todo mb use between - .oneOf( - [ - PromoCodeCampaignModelStatus.created, - PromoCodeCampaignModelStatus.preparing, - PromoCodeCampaignModelStatus.active, - PromoCodeCampaignModelStatus.ready, - ], - (q, status) => q.statusEqualTo(status), - ) - .and() - .templateStartsWith(prefix) - .isEmpty(), - ); - if (!shouldCreateCampaign) { - return; - } - - final now = DateTime.now(); - launchPromoCodesCampaign( - PromoCodesCampaignDto( - products: [product.toDto()], - activationsPerCode: 1, - activationsPerUser: 1, - generationSize: 200, - start: now, - finish: now.add(Duration(days: 365)), - status: PromoCodeCampaignStatus.created, - template: prefix + r'$l$l$l$l$l$l$l$l', - tags: [], - ), + return PromoCodesCampaignDto( + id: campaign.id, + name: campaign.name, + template: campaign.template, + products: [], // TODO: convert from JSON + activationsPerCode: campaign.activationsPerCode, + activationsPerUser: campaign.activationsPerUser, + generationSize: campaign.generationSize, + start: campaign.start, + finish: campaign.finish, + status: _parseCampaignStatus(campaign.status), + tags: campaign.tags, + promoCodes: promoCodes, ); } - Future launchPromoCodesCampaign(PromoCodesCampaignDto dto) async { - PromoCodesCampaignModel model; - final id = dto.id; - if (id != null) { - final existingModel = - await isar.txn(() => isar.promoCodesCampaignModels.get(id)); - if (existingModel == null) { - log('Campaign with id: ${dto.id} not found'); - return false; + Future createPromoCodeCampaign(PromoCodesCampaignDto dto) async { + final companion = PromoCodesCampaignsCompanion.insert( + template: dto.template, + name: drift.Value(dto.name), + products: [], // TODO: convert from dto.products + activationsPerCode: dto.activationsPerCode, + activationsPerUser: dto.activationsPerUser, + generationSize: dto.generationSize, + start: dto.start, + finish: dto.finish, + status: dto.status.name, + tags: dto.tags, + ); + + await _db.promoCodeDao.createCampaign(companion); + } + + Future updatePromoCodeCampaign(String id, PromoCodesCampaignDto dto) async { + final campaignId = int.tryParse(id); + if (campaignId == null) { + throw ArgumentError('Invalid campaign ID: $id'); + } + + final existing = await _db.promoCodeDao.getCampaignById(campaignId); + if (existing == null) { + throw StateError('Campaign not found: $id'); + } + + final updated = existing.copyWith( + template: dto.template, + name: dto.name, + products: [], // TODO: convert from dto.products + activationsPerCode: dto.activationsPerCode, + activationsPerUser: dto.activationsPerUser, + generationSize: dto.generationSize, + start: dto.start, + finish: dto.finish, + status: dto.status.name, + tags: dto.tags, + updatedAt: DateTime.now(), + ); + + await _db.promoCodeDao.updateCampaign(updated); + } + + Future deletePromoCodeCampaign(String id) async { + final campaignId = int.tryParse(id); + if (campaignId == null) return 'Invalid campaign ID'; + + final existing = await _db.promoCodeDao.getCampaignById(campaignId); + if (existing == null) return 'Campaign not found'; + + // Soft delete - mark as deleted + await _db.promoCodeDao.updateCampaign(existing.copyWith( + isDeleted: true, + updatedAt: DateTime.now(), + )); + + return null; // No error + } + + Future> listAvailablePromocodes(dynamic user) async { + // Get user ID + final userId = user is int ? user : int.tryParse(user?.toString() ?? ''); + if (userId == null) return []; + + final userCodes = await _db.promoCodeDao.getUserPromoCodes(userId); + return userCodes.map((code) => { + 'code': code.code, + 'activations': code.activations, + 'campaignId': code.campaignId, + }).toList(); + } + + Future> validatePromocode(String code, dynamic user) async { + final promoCode = await _db.promoCodeDao.getPromoCodeByCode(code.toUpperCase()); + if (promoCode == null) { + return {'valid': false, 'message': 'Promo code not found'}; + } + + final campaign = await _db.promoCodeDao.getCampaignById(promoCode.campaignId); + if (campaign == null) { + return {'valid': false, 'message': 'Invalid promo code'}; + } + + final now = DateTime.now(); + if (campaign.status != 'active' || + campaign.start.isAfter(now) || + campaign.finish.isBefore(now)) { + return {'valid': false, 'message': 'Promo code expired'}; + } + + if (promoCode.activations >= campaign.activationsPerCode) { + return {'valid': false, 'message': 'Promo code exhausted'}; + } + + // TODO: Check user-specific limits + + return { + 'valid': true, + 'campaign': { + 'id': campaign.id, + 'products': campaign.products, + 'activationsPerCode': campaign.activationsPerCode, + 'activationsPerUser': campaign.activationsPerUser, } - model = existingModel.copyWith( - activationsPerCode: dto.activationsPerCode, - generationSize: dto.generationSize, - start: dto.start, - finish: dto.finish, - status: dto.status.toModel(), - template: existingModel.template, - name: dto.name, - products: dto.products - .map( - (dto) => MnemoCardsProductModelBase.fromDto(dto), - ) - .toList(), - ); - } else { - model = PromoCodesCampaignModel( - products: dto.products - .map((product) => MnemoCardsProductModelBase.fromDto(product)) - .toList(), - activationsPerCode: dto.activationsPerCode, - activationsPerUser: dto.activationsPerUser, - generationSize: dto.generationSize, - start: dto.start, - finish: dto.finish, - status: PromoCodeCampaignModelStatus.created, - template: dto.template.toUpperCase(), - name: dto.name, - tags: [], - ); - } - - await isar.writeTxn(() async { - await isar.promoCodesCampaignModels.put(model); - }); - // await GeneratePromocodes().task(); - return true; + }; } - /// Validates a promocode without applying it. - /// Returns a map with 'valid' (bool) and 'message' (String) keys. - Future> validatePromocode( - String code, - UserModel user, - ) async { - try { - final result = await isar.txn(() async { - final upperCode = code.toUpperCase(); - - // Check if code exists - final codeModel = await isar.promoCodeModels - .filter() - .codeEqualTo(upperCode) - .findFirst(); - await codeModel?.campaign.load(); - final campaign = codeModel?.campaign.value; - if (codeModel == null || campaign == null) { - return { - 'valid': false, - 'message': 'Промокод не найден', - }; - } - - // Check campaign status and date range - final now = DateTime.now(); - if (campaign.status != PromoCodeCampaignModelStatus.active) { - return { - 'valid': false, - 'message': 'Промокод недействителен', - }; - } - if (now.isBefore(campaign.start)) { - return { - 'valid': false, - 'message': 'Промокод еще не активен', - }; - } - if (now.isAfter(campaign.finish)) { - return { - 'valid': false, - 'message': 'Промокод истек', - }; - } - - // Check if code has reached activation limit - if (codeModel.activations >= campaign.activationsPerCode) { - return { - 'valid': false, - 'message': 'Промокод исчерпан', - }; - } - - // Load user data - await user.userData.load(); - final userData = user.userData.value; - if (userData == null) { - return { - 'valid': false, - 'message': 'Ошибка при проверке промокода', - }; - } - - // Check if user has already activated this code - await userData.activatedPromoCodes.load(); - final activatedPromoCodes = userData.activatedPromoCodes; - if (activatedPromoCodes.contains(codeModel)) { - return { - 'valid': false, - 'message': 'Промокод уже был активирован', - }; - } - - // Check if user has reached activation limit for this campaign - // Load campaign relationships for activated promocodes - for (final activatedCode in activatedPromoCodes) { - await activatedCode.campaign.load(); - } - if (activatedPromoCodes - .where((p) => p.campaign.value?.id == campaign.id) - .length >= - campaign.activationsPerUser) { - return { - 'valid': false, - 'message': 'Вы уже участвовали в этой акции', - }; - } - - // Check user tags match campaign tags - if (campaign.tags.isNotEmpty && - !campaign.tags.hasIntersection(userData.tags)) { - return { - 'valid': false, - 'message': 'Промокод недействителен', - }; - } - - // Check if it's an individual promocode for another user - await codeModel.userData.load(); - final codeUserData = codeModel.userData.value; - if (codeUserData != null) { - await codeUserData.user.load(); - if (codeUserData.user.value?.id != user.id) { - return { - 'valid': false, - 'message': 'Это промокод для другого пользователя', - }; - } - } - - // All checks passed - return { - 'valid': true, - 'message': 'Промокод действителен', - }; - }); - return result; - } catch (e, s) { - log('Error when validating promo code', error: e, stackTrace: s); - return { - 'valid': false, - 'message': 'Ошибка при проверке промокода', - }; + Future applyPromoCode(dynamic dto, dynamic user) async { + final code = dto['code']?.toString()?.toUpperCase(); + if (code == null) { + throw ArgumentError('Promo code is required'); } + + final validation = await validatePromocode(code, user); + if (!validation['valid']) { + throw StateError(validation['message']); + } + + final promoCode = await _db.promoCodeDao.getPromoCodeByCode(code); + if (promoCode == null) return null; + + // Increment activations + await _db.promoCodeDao.incrementActivations(promoCode.id); + + // TODO: Apply the promo code benefits to user + + return {'success': true, 'code': code}; } - Future applyPromoCode(PromoCodeDto dto, UserModel user) async { - try { - PromoCodeModel? checkedCodeModel; - final checkResult = await isar.writeTxn(() async { - final code = dto.code?.toUpperCase(); - - if (code == null) { - return _notFoundDto; - } - final codeModel = - await isar.promoCodeModels.filter().codeEqualTo(code).findFirst(); - await codeModel?.campaign.load(); - final campaign = codeModel?.campaign.value; - if (codeModel == null || campaign == null) { - return _notFoundDto; - } - final now = DateTime.now(); - if (codeModel.activations >= campaign.activationsPerCode || - campaign.status != PromoCodeCampaignStatus.active || - now.isBefore(campaign.start) || - now.isAfter(campaign.finish)) { - return _unavailableDto; - } - await user.userData.load(); - final userData = user.userData.value; - if (userData == null) { - return _errorDto; - } - - await userData.activatedPromoCodes.load(); - final activatedPromoCodes = userData.activatedPromoCodes; - if (activatedPromoCodes.contains(codeModel)) { - return PromoCodeDto( - message: 'Промокод уже был активирован', - success: false, - ); - } - - if (activatedPromoCodes - .where((p) => p.campaign.value?.id == campaign.id) - .length >= - campaign.activationsPerUser) { - return PromoCodeDto( - message: 'Вы уже участвовали в этой акции', - success: false, - ); - } - - if (campaign.tags.isNotEmpty && - !campaign.tags.hasIntersection(userData.tags)) { - return _unavailableDto; - } - - await codeModel.userData.load(); - final codeUserData = codeModel.userData.value; - if (codeUserData != null) { - await codeUserData.user.load(); - if (codeUserData.user.value?.id != user.id) { - return PromoCodeDto( - message: 'Это промокод для другого пользователя', - success: false, - ); - } - } - - checkedCodeModel = codeModel; - return null; - }); - if (checkResult != null) { - return checkResult; - } - await _processPromoCode( - checkedCodeModel!.campaign.value!, - checkedCodeModel!, - user, - user.userData.value!, - ); - return PromoCodeDto( - message: 'Промокод применен', - success: true, - ); - } catch (e, s) { - print('Error when applying promo code error: $e, stackTrace: $s'); - return _errorDto; - } + Future launchPromoCodesCampaign(dynamic campaign) async { + // TODO: Generate promo codes for campaign + // This involves generating codes based on template and generationSize + return false; } - - Future _processPromoCode( - PromoCodesCampaignModel campaign, - PromoCodeModel promoCode, - UserModel user, - UserDataModel userData, - ) async { - await isar.writeTxn(() async { - userData.activatedPromoCodes.add(promoCode); - final updatedPromoCode = - promoCode.copyWith(activations: promoCode.activations + 1); - await isar.promoCodeModels.put(updatedPromoCode); - await userData.activatedPromoCodes.save(); - await isar.userDataModels.put(userData); - }); - final types = campaign.products.map((p) => p.type).toSet(); - if (const { - MnemoCardsProductModelType.subscription, - MnemoCardsProductModelType.pack - }.hasIntersection(types)) { - await _paymentManager.processPayment( - PaymentModel( - amount: '0', - currency: 'PROMO', - status: PaymentStatus.succeeded, - paymentSystem: PaymentSystem.promoCode, - userId: user.id!, - date: DateTime.now(), - products: campaign.products, - ), - ); - } - if (types.contains(MnemoCardsProductModelType.discount)) {} - } - - Future deletePromoCodeCampaign(String stringId) async { - try { - final id = int.parse(stringId); - isar.writeTxn(() async { - final model = await isar.promoCodesCampaignModels.get(id); - if (model == null) { - return 'Campaign $id not found'; - } - if (model.status != PromoCodeCampaignModelStatus.disabled) { - return 'Disable campaign before deleting'; - } - isar.promoCodesCampaignModels.delete(id); - }); - return null; - } catch (e, s) { - log('Error when deleting promo code campaign', error: e, stackTrace: s); - return 'Error $e'; - } - } - - static const _notFoundDto = PromoCodeDto( - message: 'Промокод не найден', - success: false, - ); - - static const _errorDto = PromoCodeDto( - message: 'Ошибка при применении промокода', - success: false, - ); - static const _unavailableDto = PromoCodeDto( - message: 'Промокод недействителен', - success: false, - ); -} +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/promo_codes/promo_codes_manager.dart.backup b/mnemo_cards_backend/lib/promo_codes/promo_codes_manager.dart.backup new file mode 100644 index 0000000..a7a31f9 --- /dev/null +++ b/mnemo_cards_backend/lib/promo_codes/promo_codes_manager.dart.backup @@ -0,0 +1,498 @@ +import 'dart:developer'; + +import 'package:injectable/injectable.dart'; +import 'package:isar/isar.dart'; +import 'package:mnemo_cards_backend/api/purchase/payment_manager.dart'; +import 'package:mnemo_cards_backend/extensions.dart'; +import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; +import 'package:mnemo_cards_common/mnemo_cards_common.dart'; + +import '../main.dart'; + +@lazySingleton +class PromoCodesManager { + final PaymentManager _paymentManager; + + PromoCodesManager(this._paymentManager); + + Future> promoCodeCampaigns({ + List withCodes = const [], + }) async { + final models = await isar + .txn(() async => isar.promoCodesCampaignModels.where().findAll()); + final dtos = await Future.wait(models.map( + (m) async => await m.toDto( + withCodes: withCodes.contains(m.id.toString()), + ), + )); + return dtos; + } + + Future promoCodeCampaign(String id) async { + final intId = int.parse(id); + final model = + await isar.txn(() async => isar.promoCodesCampaignModels.get(intId)); + final dto = await model?.toDto(withCodes: true); + return dto; + } + + /// Lists available promocodes for a user. + /// Returns active campaigns with promocodes that the user can apply. + Future> listAvailablePromocodes( + UserModel user, + ) async { + await user.userData.load(); + final userData = user.userData.value; + if (userData == null) { + return []; + } + + await userData.activatedPromoCodes.load(); + final activatedPromoCodes = userData.activatedPromoCodes; + + final now = DateTime.now(); + + // Get all active campaigns within date range + // Campaign is active if: now >= start && now <= finish + final activeCampaigns = await isar.txn(() async { + return await isar.promoCodesCampaignModels + .filter() + .statusEqualTo(PromoCodeCampaignModelStatus.active) + .startLessThan(now, include: true) + .finishGreaterThan(now, include: true) + .findAll(); + }); + + final availableCampaigns = []; + + for (final campaign in activeCampaigns) { + // Check if campaign has tags and user matches them + if (campaign.tags.isNotEmpty && + !campaign.tags.hasIntersection(userData.tags)) { + continue; + } + + // Check if user has already reached activationsPerUser limit + final userActivationsForCampaign = activatedPromoCodes + .where((p) => p.campaign.value?.id == campaign.id) + .length; + if (userActivationsForCampaign >= campaign.activationsPerUser) { + continue; + } + + // Load promocodes for this campaign + await campaign.promoCodes.load(); + final campaignPromoCodes = campaign.promoCodes.toList(); + + // Filter available promocodes: + // - Not already activated by user + // - Not reached activationsPerCode limit + // - Not individual promocodes for other users + final availableCodes = []; + for (final promoCode in campaignPromoCodes) { + // Skip if already activated by this user + if (activatedPromoCodes.contains(promoCode)) { + continue; + } + + // Skip if reached activation limit + if (promoCode.activations >= campaign.activationsPerCode) { + continue; + } + + // Check if it's an individual promocode for another user + await promoCode.userData.load(); + final codeUserData = promoCode.userData.value; + if (codeUserData != null) { + await codeUserData.user.load(); + if (codeUserData.user.value?.id != user.id) { + continue; + } + } + + availableCodes.add(promoCode.code); + } + + // Only include campaign if it has available codes + if (availableCodes.isNotEmpty) { + final dto = await campaign.toDto(withCodes: false); + availableCampaigns.add( + dto.copyWith(promoCodes: availableCodes), + ); + } + } + + return availableCampaigns; + } + + Future createProductForAdsCampaignIfNeeded( + MnemoCardsProductModel product, + ) async { + final prefix = 'ad${product.id}'; + // todo - distinguish active campaigns + final shouldCreateCampaign = await isar.txn( + () => isar.promoCodesCampaignModels + .filter() + // todo mb use between + .oneOf( + [ + PromoCodeCampaignModelStatus.created, + PromoCodeCampaignModelStatus.preparing, + PromoCodeCampaignModelStatus.active, + PromoCodeCampaignModelStatus.ready, + ], + (q, status) => q.statusEqualTo(status), + ) + .and() + .templateStartsWith(prefix) + .isEmpty(), + ); + if (!shouldCreateCampaign) { + return; + } + + final now = DateTime.now(); + launchPromoCodesCampaign( + PromoCodesCampaignDto( + products: [product.toDto()], + activationsPerCode: 1, + activationsPerUser: 1, + generationSize: 200, + start: now, + finish: now.add(Duration(days: 365)), + status: PromoCodeCampaignStatus.created, + template: prefix + r'$l$l$l$l$l$l$l$l', + tags: [], + ), + ); + } + + Future launchPromoCodesCampaign(PromoCodesCampaignDto dto) async { + PromoCodesCampaignModel model; + final id = dto.id; + if (id != null) { + final existingModel = + await isar.txn(() => isar.promoCodesCampaignModels.get(id)); + if (existingModel == null) { + log('Campaign with id: ${dto.id} not found'); + return false; + } + model = existingModel.copyWith( + activationsPerCode: dto.activationsPerCode, + generationSize: dto.generationSize, + start: dto.start, + finish: dto.finish, + status: dto.status.toModel(), + template: existingModel.template, + name: dto.name, + products: dto.products + .map( + (dto) => MnemoCardsProductModelBase.fromDto(dto), + ) + .toList(), + ); + } else { + model = PromoCodesCampaignModel( + products: dto.products + .map((product) => MnemoCardsProductModelBase.fromDto(product)) + .toList(), + activationsPerCode: dto.activationsPerCode, + activationsPerUser: dto.activationsPerUser, + generationSize: dto.generationSize, + start: dto.start, + finish: dto.finish, + status: PromoCodeCampaignModelStatus.created, + template: dto.template.toUpperCase(), + name: dto.name, + tags: [], + ); + } + + await isar.writeTxn(() async { + await isar.promoCodesCampaignModels.put(model); + }); + // await GeneratePromocodes().task(); + return true; + } + + /// Validates a promocode without applying it. + /// Returns a map with 'valid' (bool) and 'message' (String) keys. + Future> validatePromocode( + String code, + UserModel user, + ) async { + try { + final result = await isar.txn(() async { + final upperCode = code.toUpperCase(); + + // Check if code exists + final codeModel = await isar.promoCodeModels + .filter() + .codeEqualTo(upperCode) + .findFirst(); + await codeModel?.campaign.load(); + final campaign = codeModel?.campaign.value; + if (codeModel == null || campaign == null) { + return { + 'valid': false, + 'message': 'Промокод не найден', + }; + } + + // Check campaign status and date range + final now = DateTime.now(); + if (campaign.status != PromoCodeCampaignModelStatus.active) { + return { + 'valid': false, + 'message': 'Промокод недействителен', + }; + } + if (now.isBefore(campaign.start)) { + return { + 'valid': false, + 'message': 'Промокод еще не активен', + }; + } + if (now.isAfter(campaign.finish)) { + return { + 'valid': false, + 'message': 'Промокод истек', + }; + } + + // Check if code has reached activation limit + if (codeModel.activations >= campaign.activationsPerCode) { + return { + 'valid': false, + 'message': 'Промокод исчерпан', + }; + } + + // Load user data + await user.userData.load(); + final userData = user.userData.value; + if (userData == null) { + return { + 'valid': false, + 'message': 'Ошибка при проверке промокода', + }; + } + + // Check if user has already activated this code + await userData.activatedPromoCodes.load(); + final activatedPromoCodes = userData.activatedPromoCodes; + if (activatedPromoCodes.contains(codeModel)) { + return { + 'valid': false, + 'message': 'Промокод уже был активирован', + }; + } + + // Check if user has reached activation limit for this campaign + // Load campaign relationships for activated promocodes + for (final activatedCode in activatedPromoCodes) { + await activatedCode.campaign.load(); + } + if (activatedPromoCodes + .where((p) => p.campaign.value?.id == campaign.id) + .length >= + campaign.activationsPerUser) { + return { + 'valid': false, + 'message': 'Вы уже участвовали в этой акции', + }; + } + + // Check user tags match campaign tags + if (campaign.tags.isNotEmpty && + !campaign.tags.hasIntersection(userData.tags)) { + return { + 'valid': false, + 'message': 'Промокод недействителен', + }; + } + + // Check if it's an individual promocode for another user + await codeModel.userData.load(); + final codeUserData = codeModel.userData.value; + if (codeUserData != null) { + await codeUserData.user.load(); + if (codeUserData.user.value?.id != user.id) { + return { + 'valid': false, + 'message': 'Это промокод для другого пользователя', + }; + } + } + + // All checks passed + return { + 'valid': true, + 'message': 'Промокод действителен', + }; + }); + return result; + } catch (e, s) { + log('Error when validating promo code', error: e, stackTrace: s); + return { + 'valid': false, + 'message': 'Ошибка при проверке промокода', + }; + } + } + + Future applyPromoCode(PromoCodeDto dto, UserModel user) async { + try { + PromoCodeModel? checkedCodeModel; + final checkResult = await isar.writeTxn(() async { + final code = dto.code?.toUpperCase(); + + if (code == null) { + return _notFoundDto; + } + final codeModel = + await isar.promoCodeModels.filter().codeEqualTo(code).findFirst(); + await codeModel?.campaign.load(); + final campaign = codeModel?.campaign.value; + if (codeModel == null || campaign == null) { + return _notFoundDto; + } + final now = DateTime.now(); + if (codeModel.activations >= campaign.activationsPerCode || + campaign.status != PromoCodeCampaignStatus.active || + now.isBefore(campaign.start) || + now.isAfter(campaign.finish)) { + return _unavailableDto; + } + await user.userData.load(); + final userData = user.userData.value; + if (userData == null) { + return _errorDto; + } + + await userData.activatedPromoCodes.load(); + final activatedPromoCodes = userData.activatedPromoCodes; + if (activatedPromoCodes.contains(codeModel)) { + return PromoCodeDto( + message: 'Промокод уже был активирован', + success: false, + ); + } + + if (activatedPromoCodes + .where((p) => p.campaign.value?.id == campaign.id) + .length >= + campaign.activationsPerUser) { + return PromoCodeDto( + message: 'Вы уже участвовали в этой акции', + success: false, + ); + } + + if (campaign.tags.isNotEmpty && + !campaign.tags.hasIntersection(userData.tags)) { + return _unavailableDto; + } + + await codeModel.userData.load(); + final codeUserData = codeModel.userData.value; + if (codeUserData != null) { + await codeUserData.user.load(); + if (codeUserData.user.value?.id != user.id) { + return PromoCodeDto( + message: 'Это промокод для другого пользователя', + success: false, + ); + } + } + + checkedCodeModel = codeModel; + return null; + }); + if (checkResult != null) { + return checkResult; + } + await _processPromoCode( + checkedCodeModel!.campaign.value!, + checkedCodeModel!, + user, + user.userData.value!, + ); + return PromoCodeDto( + message: 'Промокод применен', + success: true, + ); + } catch (e, s) { + print('Error when applying promo code error: $e, stackTrace: $s'); + return _errorDto; + } + } + + Future _processPromoCode( + PromoCodesCampaignModel campaign, + PromoCodeModel promoCode, + UserModel user, + UserDataModel userData, + ) async { + await isar.writeTxn(() async { + userData.activatedPromoCodes.add(promoCode); + final updatedPromoCode = + promoCode.copyWith(activations: promoCode.activations + 1); + await isar.promoCodeModels.put(updatedPromoCode); + await userData.activatedPromoCodes.save(); + await isar.userDataModels.put(userData); + }); + final types = campaign.products.map((p) => p.type).toSet(); + if (const { + MnemoCardsProductModelType.subscription, + MnemoCardsProductModelType.pack + }.hasIntersection(types)) { + await _paymentManager.processPayment( + PaymentModel( + amount: '0', + currency: 'PROMO', + status: PaymentStatus.succeeded, + paymentSystem: PaymentSystem.promoCode, + userId: user.id!, + date: DateTime.now(), + products: campaign.products, + ), + ); + } + if (types.contains(MnemoCardsProductModelType.discount)) {} + } + + Future deletePromoCodeCampaign(String stringId) async { + try { + final id = int.parse(stringId); + isar.writeTxn(() async { + final model = await isar.promoCodesCampaignModels.get(id); + if (model == null) { + return 'Campaign $id not found'; + } + if (model.status != PromoCodeCampaignModelStatus.disabled) { + return 'Disable campaign before deleting'; + } + isar.promoCodesCampaignModels.delete(id); + }); + return null; + } catch (e, s) { + log('Error when deleting promo code campaign', error: e, stackTrace: s); + return 'Error $e'; + } + } + + static const _notFoundDto = PromoCodeDto( + message: 'Промокод не найден', + success: false, + ); + + static const _errorDto = PromoCodeDto( + message: 'Ошибка при применении промокода', + success: false, + ); + static const _unavailableDto = PromoCodeDto( + message: 'Промокод недействителен', + success: false, + ); +} diff --git a/mnemo_cards_backend/lib/statistics/achievement_manager.dart b/mnemo_cards_backend/lib/statistics/achievement_manager.dart index 3422719..b2e8ca2 100644 --- a/mnemo_cards_backend/lib/statistics/achievement_manager.dart +++ b/mnemo_cards_backend/lib/statistics/achievement_manager.dart @@ -1,422 +1,180 @@ -import 'dart:async'; -import 'dart:developer'; - import 'package:injectable/injectable.dart'; -import 'package:isar/isar.dart'; +import 'package:mnemo_cards_backend/database/database.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; -/// Service for managing user achievements -/// -/// Handles achievement checking, unlocking, and progress tracking. -/// Automatically evaluates user progress against achievement requirements. @lazySingleton class AchievementManager { - final Isar _isar; + final AppDatabase _db; - AchievementManager(this._isar); + AchievementManager(this._db); /// Get all available achievements with their definitions - List get allAchievementDefinitions => - _achievementDefinitions; + List get allAchievementDefinitions => AchievementDefinitions.allAchievements; /// Check and update achievements for a user - /// - /// Evaluates all achievements against current user data and unlocks any - /// newly achieved accomplishments. Returns list of newly unlocked achievements. Future> checkAndUnlockAchievements( int userId, UserDataModel userData, ) async { - final newlyUnlocked = []; + final unlockedAchievements = []; - try { - // Get current user achievements - final currentAchievements = await _getUserAchievements(userId); + // Get existing user achievements + final userAchievements = await getUserAchievements(userId); + final existingIds = userAchievements.map((a) => a.id).toSet(); - for (final definition in _achievementDefinitions) { - // Skip if already unlocked - if (currentAchievements - .any((a) => a.id == definition.id && a.isUnlocked)) { - continue; + // Check each achievement + for (final achievement in AchievementDefinitions.allAchievements) { + if (existingIds.contains(achievement.id)) continue; + + final isUnlocked = await _checkAchievementCondition(userId, userData, achievement); + if (isUnlocked) { + final unlocked = await unlockAchievement(userId, achievement.id); + if (unlocked != null) { + unlockedAchievements.add(unlocked); } - - // Check if achievement should be unlocked - final shouldUnlock = await _evaluateAchievement(definition, userData); - - if (shouldUnlock) { - final unlockedAchievement = definition.unlock(); - newlyUnlocked.add(unlockedAchievement); - - // Save to database - await _saveAchievement(userId, unlockedAchievement); - - log('Achievement unlocked: ${unlockedAchievement.title} for user $userId'); + } else { + // Update progress if applicable + final progress = await _calculateAchievementProgress(userId, userData, achievement); + if (progress > 0.0) { + await _db.achievementDao.updateAchievementProgress(userId, achievement.id, progress); } } - } catch (e, s) { - log('Error checking achievements for user $userId: $e\n$s'); - } - - return newlyUnlocked; - } - - /// Get achievement progress for a user - /// - /// Returns progress (0.0 to 1.0) for achievements that support progress tracking - Future> getAchievementProgress( - int userId, - UserDataModel userData, - ) async { - final progress = {}; - - for (final definition in _achievementDefinitions) { - if (definition.supportsProgress) { - final currentProgress = await _calculateProgress(definition, userData); - progress[definition.id] = currentProgress; - } } - return progress; + return unlockedAchievements; } - /// Get all achievements for a user + /// Get achievements for a user Future> getUserAchievements(int userId) async { - return await _getUserAchievements(userId); + final userAchievements = await _db.achievementDao.getUserAchievements(userId); + final progressMap = await _db.achievementDao.getAchievementProgress(userId); + + final achievements = []; + + for (final userAchievement in userAchievements) { + final definition = AchievementDefinitions.getById(userAchievement.achievementId); + if (definition != null) { + achievements.add(definition.copyWith( + unlockedAt: userAchievement.unlockedAt, + progress: userAchievement.progress, + )); + } + } + + // Add locked achievements with progress + for (final definition in AchievementDefinitions.allAchievements) { + if (!achievements.any((a) => a.id == definition.id)) { + final progress = progressMap[definition.id] ?? 0.0; + achievements.add(definition.updateProgress(progress)); + } + } + + return achievements; } - /// Force unlock an achievement for a user (admin function) - Future forceUnlockAchievement( + /// Check if user has specific achievement + Future hasAchievement(int userId, String achievementId) async { + return await _db.achievementDao.hasAchievement(userId, achievementId); + } + + /// Unlock achievement for user + Future unlockAchievement( int userId, String achievementId, ) async { - final definition = _achievementDefinitions.firstWhere( - (def) => def.id == achievementId, - ); + final definition = AchievementDefinitions.getById(achievementId); + if (definition == null) return null; - final unlockedAchievement = definition.unlock(); - await _saveAchievement(userId, unlockedAchievement); - - return true; - } - - /// Evaluate if an achievement should be unlocked - Future _evaluateAchievement( - AchievementDefinition definition, - UserDataModel userData, - ) async { - return await definition.evaluate(userData); - } - - /// Calculate progress for an achievement - Future _calculateProgress( - AchievementDefinition definition, - UserDataModel userData, - ) async { - return await definition.calculateProgress?.call(userData) ?? 0.0; - } - - /// Get user achievements from database - Future> _getUserAchievements(int userId) async { - final user = await _isar.userModels.get(userId); - if (user?.userData.value == null) return []; - - return user!.userData.value!.achievements - .map((model) => model.toDto()) - .toList(); - } - - /// Save achievement to user data - Future _saveAchievement(int userId, AchievementDto achievement) async { - final user = await _isar.userModels.get(userId); - if (user?.userData.value == null) return; - - final userData = user!.userData.value!; - final model = AchievementModel.fromDto(achievement); - - // Update achievements list - final existingAchievements = userData.achievements; - final updatedAchievements = [ - ...existingAchievements.where((a) => a.id != achievement.id), - model, - ]; - - final updatedUserData = - userData.copyWith(achievements: updatedAchievements); - - await _isar.writeTxn(() async { - await _isar.userDataModels.put(updatedUserData); - }); - } - - /// Achievement definitions - static final List _achievementDefinitions = [ - // First Steps - AchievementDefinition( - id: 'first_word', - title: 'First Word', - description: 'Learn your first word', - type: AchievementType.firstWordLearned, - evaluate: (data) => Future.value(data.words.isNotEmpty), - supportsProgress: false, - ), - - AchievementDefinition( - id: 'first_test', - title: 'First Test', - description: 'Complete your first test', - type: AchievementType.firstTestCompleted, - evaluate: (data) => Future.value(data.testsStatistics.isNotEmpty), - supportsProgress: false, - ), - - AchievementDefinition( - id: 'first_pack', - title: 'First Pack', - description: 'Complete your first pack', - type: AchievementType.firstPackCompleted, - evaluate: (data) => - Future.value(data.packProgress.any((p) => p.progress >= 1.0)), - supportsProgress: false, - ), - - // Streak Achievements - AchievementDefinition( - id: 'streak_3', - title: '3-Day Streak', - description: 'Study for 3 consecutive days', - type: AchievementType.streak3Days, - evaluate: (data) => Future.value(data.currentStreak >= 3), - supportsProgress: true, - calculateProgress: (data) => - Future.value((data.currentStreak / 3.0).clamp(0.0, 1.0)), - ), - - AchievementDefinition( - id: 'streak_7', - title: 'Week Warrior', - description: 'Study for 7 consecutive days', - type: AchievementType.streak7Days, - evaluate: (data) => Future.value(data.currentStreak >= 7), - supportsProgress: true, - calculateProgress: (data) => - Future.value((data.currentStreak / 7.0).clamp(0.0, 1.0)), - ), - - AchievementDefinition( - id: 'streak_30', - title: 'Monthly Master', - description: 'Study for 30 consecutive days', - type: AchievementType.streak30Days, - evaluate: (data) => Future.value(data.currentStreak >= 30), - supportsProgress: true, - calculateProgress: (data) => - Future.value((data.currentStreak / 30.0).clamp(0.0, 1.0)), - ), - - AchievementDefinition( - id: 'streak_100', - title: 'Century Champion', - description: 'Study for 100 consecutive days', - type: AchievementType.streak100Days, - evaluate: (data) => Future.value(data.currentStreak >= 100), - supportsProgress: true, - calculateProgress: (data) => - Future.value((data.currentStreak / 100.0).clamp(0.0, 1.0)), - ), - - // Words Mastery - AchievementDefinition( - id: 'words_10', - title: 'Word Explorer', - description: 'Learn 10 words', - type: AchievementType.words10Learned, - evaluate: (data) => Future.value(data.words.length >= 10), - supportsProgress: true, - calculateProgress: (data) => - Future.value((data.words.length / 10.0).clamp(0.0, 1.0)), - ), - - AchievementDefinition( - id: 'words_50', - title: 'Vocabulary Builder', - description: 'Learn 50 words', - type: AchievementType.words50Learned, - evaluate: (data) => Future.value(data.words.length >= 50), - supportsProgress: true, - calculateProgress: (data) => - Future.value((data.words.length / 50.0).clamp(0.0, 1.0)), - ), - - AchievementDefinition( - id: 'words_100', - title: 'Language Learner', - description: 'Learn 100 words', - type: AchievementType.words100Learned, - evaluate: (data) => Future.value(data.words.length >= 100), - supportsProgress: true, - calculateProgress: (data) => - Future.value((data.words.length / 100.0).clamp(0.0, 1.0)), - ), - - AchievementDefinition( - id: 'words_500', - title: 'Word Master', - description: 'Learn 500 words', - type: AchievementType.words500Learned, - evaluate: (data) => Future.value(data.words.length >= 500), - supportsProgress: true, - calculateProgress: (data) => - Future.value((data.words.length / 500.0).clamp(0.0, 1.0)), - ), - - AchievementDefinition( - id: 'words_1000', - title: 'Vocabulary Expert', - description: 'Learn 1000 words', - type: AchievementType.words1000Learned, - evaluate: (data) => Future.value(data.words.length >= 1000), - supportsProgress: true, - calculateProgress: (data) => - Future.value((data.words.length / 1000.0).clamp(0.0, 1.0)), - ), - - // Performance Achievements - AchievementDefinition( - id: 'perfect_test', - title: 'Perfect Score', - description: 'Complete a test with 100% accuracy', - type: AchievementType.perfectTestScore, - evaluate: (data) => Future.value( - data.testsStatistics.any((test) => test.attempts.any((attempt) { - final totalWords = attempt.words.length; - final correctWords = attempt.words - .where((word) => word.correct > word.incorrect) - .length; - return totalWords > 0 && correctWords == totalWords; - })), - ), - supportsProgress: false, - ), - - AchievementDefinition( - id: 'speed_learner', - title: 'Speed Learner', - description: 'Complete a pack in less than 24 hours', - type: AchievementType.speedLearner, - evaluate: (data) => Future.value( - data.packProgress.any((pack) => - pack.progress >= 1.0 && - pack.studyTimeMinutes < 24 * 60 // Less than 24 hours - ), - ), - supportsProgress: false, - ), - - // Dedication Achievements - AchievementDefinition( - id: 'dedicated_learner', - title: 'Dedicated Learner', - description: 'Study for 100 hours total', - type: AchievementType.dedicatedLearner, - evaluate: (data) => Future.value(data.totalStudyTimeMinutes >= 100 * 60), - supportsProgress: true, - calculateProgress: (data) => Future.value( - (data.totalStudyTimeMinutes / (100 * 60)).clamp(0.0, 1.0)), - ), - - // Time-based Achievements - AchievementDefinition( - id: 'early_bird', - title: 'Early Bird', - description: 'Study before 6 AM', - type: AchievementType.earlyBird, - evaluate: (data) => Future.value( - data.studyDates.any((date) => date.hour < 6), - ), - supportsProgress: false, - ), - - AchievementDefinition( - id: 'night_owl', - title: 'Night Owl', - description: 'Study after 10 PM', - type: AchievementType.nightOwl, - evaluate: (data) => Future.value( - data.studyDates.any((date) => date.hour >= 22), - ), - supportsProgress: false, - ), - - // Special Achievements - AchievementDefinition( - id: 'consistent_learner', - title: 'Consistent Learner', - description: 'Study every day for a month', - type: AchievementType.consistentLearner, - evaluate: (data) => Future.value(data.longestStreak >= 30), - supportsProgress: true, - calculateProgress: (data) => - Future.value((data.longestStreak / 30.0).clamp(0.0, 1.0)), - ), - - AchievementDefinition( - id: 'language_master', - title: 'Language Master', - description: 'Complete 5 packs', - type: AchievementType.languageMaster, - evaluate: (data) => Future.value( - data.packProgress.where((p) => p.progress >= 1.0).length >= 5, - ), - supportsProgress: true, - calculateProgress: (data) => Future.value( - (data.packProgress.where((p) => p.progress >= 1.0).length / 5.0) - .clamp(0.0, 1.0), - ), - ), - ]; -} - -/// Definition of an achievement with evaluation logic -class AchievementDefinition { - final String id; - final String title; - final String description; - final AchievementType type; - final Future Function(UserDataModel data) evaluate; - final bool supportsProgress; - final Future Function(UserDataModel data)? calculateProgress; - - AchievementDefinition({ - required this.id, - required this.title, - required this.description, - required this.type, - required this.evaluate, - this.supportsProgress = false, - this.calculateProgress, - }); - - /// Create an unlocked achievement DTO - AchievementDto unlock() { - return AchievementDto( - id: id, - title: title, - description: description, - type: type, + final companion = UserAchievementsCompanion.insert( + userId: userId, + achievementId: achievementId, unlockedAt: DateTime.now(), progress: 1.0, ); + + try { + await _db.achievementDao.unlockAchievement(companion); + return definition.unlock(); + } catch (e) { + // Achievement might already exist, return existing + final existing = await _db.achievementDao.getUserAchievement(userId, achievementId); + return existing != null ? definition.unlock() : null; + } } - /// Create a locked achievement DTO with progress - AchievementDto withProgress(double progress) { - return AchievementDto( - id: id, - title: title, - description: description, - type: type, - progress: progress.clamp(0.0, 1.0), - ); + /// Get achievement progress for user + Future> getAchievementProgress(int userId) async { + return await _db.achievementDao.getAchievementProgress(userId); } -} + + /// Check if achievement condition is met + Future _checkAchievementCondition( + int userId, + UserDataModel userData, + AchievementDto achievement, + ) async { + switch (achievement.type) { + case AchievementType.firstWordLearned: + return userData.totalCards > 0; + + case AchievementType.firstTestCompleted: + return userData.totalTests > 0; + + case AchievementType.firstPackCompleted: + final userPacks = await _db.userDao.getUserPacks(userId); + return userPacks.isNotEmpty; + + case AchievementType.words10Learned: + return userData.totalCards >= 10; + + case AchievementType.words50Learned: + return userData.totalCards >= 50; + + case AchievementType.words100Learned: + return userData.totalCards >= 100; + + case AchievementType.words500Learned: + return userData.totalCards >= 500; + + case AchievementType.words1000Learned: + return userData.totalCards >= 1000; + + // TODO: Implement other achievement conditions + // - Streak achievements (need session tracking) + // - Perfect test scores (need test results) + // - Time-based achievements (need session times) + // - Speed learner (need pack completion times) + + default: + return false; + } + } + + /// Calculate progress towards achievement + Future _calculateAchievementProgress( + int userId, + UserDataModel userData, + AchievementDto achievement, + ) async { + switch (achievement.type) { + case AchievementType.words10Learned: + return (userData.totalCards / 10.0).clamp(0.0, 1.0); + + case AchievementType.words50Learned: + return (userData.totalCards / 50.0).clamp(0.0, 1.0); + + case AchievementType.words100Learned: + return (userData.totalCards / 100.0).clamp(0.0, 1.0); + + case AchievementType.words500Learned: + return (userData.totalCards / 500.0).clamp(0.0, 1.0); + + case AchievementType.words1000Learned: + return (userData.totalCards / 1000.0).clamp(0.0, 1.0); + + default: + return 0.0; + } + } +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/statistics/session_tracker.dart b/mnemo_cards_backend/lib/statistics/session_tracker.dart index 5421bbd..fc04bc2 100644 --- a/mnemo_cards_backend/lib/statistics/session_tracker.dart +++ b/mnemo_cards_backend/lib/statistics/session_tracker.dart @@ -1,17 +1,14 @@ import 'dart:async'; import 'dart:collection'; +import 'dart:math'; 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/database/database.dart'; +import 'package:drift/drift.dart' as drift; -/// Service for tracking and managing user study sessions -/// -/// Automatically creates, updates, and manages study sessions based on user activity. -/// Sessions are created when users start studying and are ended based on inactivity timeouts. @lazySingleton class SessionTracker { - final Isar _isar; + final AppDatabase _db; /// Active sessions cache: userId -> sessionId final Map _activeSessions = {}; @@ -22,7 +19,7 @@ class SessionTracker { /// Session timeout duration (30 minutes by default) static const Duration _sessionTimeout = Duration(minutes: 30); - SessionTracker(this._isar); + SessionTracker(this._db); /// Get or create an active session for a user /// @@ -48,237 +45,148 @@ class SessionTracker { // Create new session final sessionId = _generateSessionId(); - final session = StudySessionModel( - sessionId: sessionId, - userId: userId, - startTime: DateTime.now(), - packId: packId, - testId: testId, + final now = DateTime.now(); + + await _db.statisticsDao.createSession( + StudySessionsCompanion.insert( + userId: userId, + sessionId: drift.Value(sessionId), + startTime: now, + packId: drift.Value(packId), + testId: drift.Value(testId), + ), ); - // Save to database - await _isar.writeTxn(() async { - await _isar.studySessionModels.put(session); - }); - - // Track as active + // Cache active session _activeSessions[userId] = sessionId; - _resetSessionTimer(sessionId); + + // Start timeout timer + _startSessionTimer(sessionId); return sessionId; } - /// Update session with progress data - /// - /// Called when user completes a test or makes progress. - Future updateSessionProgress( - String sessionId, { - int wordsLearned = 0, - int testsCompleted = 0, - double? accuracy, - }) async { - final session = await _isar.studySessionModels - .filter() - .sessionIdEqualTo(sessionId) - .findFirst(); - - if (session == null || !session.isActive) return; - - // Update session with new progress - final updatedSession = session.addProgress( - wordsLearned: wordsLearned, - testsCompleted: testsCompleted, - accuracy: accuracy, - ); - - await _isar.writeTxn(() async { - await _isar.studySessionModels.put(updatedSession); - }); - - // Reset timeout timer - _resetSessionTimer(sessionId); - } - - /// End a session manually with final statistics - /// - /// Called when user explicitly ends a session or when session times out. + /// End a session manually Future endSession( String sessionId, { - int wordsLearned = 0, - int testsCompleted = 0, - double accuracy = 0.0, + int? wordsLearned, + int? testsCompleted, + double? accuracy, }) async { - final session = await _isar.studySessionModels - .filter() - .sessionIdEqualTo(sessionId) - .findFirst(); + // Cancel timer + _sessionTimers[sessionId]?.cancel(); + _sessionTimers.remove(sessionId); - if (session == null || !session.isActive) return; + // Find user for this session + final userId = _activeSessions.entries + .where((entry) => entry.value == sessionId) + .map((entry) => entry.key) + .firstOrNull; - // End session with final statistics - final endedSession = session.end( + if (userId != null) { + _activeSessions.remove(userId); + } + + // Update session in database + await _db.statisticsDao.endSession( + sessionId: sessionId, wordsLearned: wordsLearned, testsCompleted: testsCompleted, accuracy: accuracy, ); - - await _isar.writeTxn(() async { - await _isar.studySessionModels.put(endedSession); - }); - - // Clean up tracking - final userId = session.userId; - _activeSessions.remove(userId); - _cancelSessionTimer(sessionId); } - /// End session for a specific user - Future endUserSession(int userId) async { - final sessionId = _activeSessions[userId]; - if (sessionId != null) { + /// Update session statistics + Future updateSessionStats( + String sessionId, { + int? wordsLearned, + int? testsCompleted, + double? accuracy, + }) async { + // Find session by sessionId + final session = await _db.statisticsDao.getSessionBySessionId(sessionId); + if (session == null) return; + + // Update session + final updatedSession = session.copyWith( + wordsLearned: wordsLearned ?? session.wordsLearned, + testsCompleted: testsCompleted ?? session.testsCompleted, + accuracy: accuracy ?? session.accuracy, + updatedAt: DateTime.now(), + ); + + await _db.statisticsDao.updateSession(updatedSession); + } + + /// Get active session for user + Future getActiveSession(int userId) async { + return await _db.statisticsDao.getActiveSession(userId); + } + + /// Get session history for user + Future> getUserSessions( + int userId, { + int? limit, + DateTime? fromDate, + DateTime? toDate, + }) async { + return await _db.statisticsDao.getSessionsByUserId( + userId, + limit: limit, + fromDate: fromDate, + toDate: toDate, + ); + } + + /// Clean up expired sessions (called by cron job) + Future cleanupExpiredSessions() async { + // End all active sessions that have timed out + final now = DateTime.now(); + final expiredSessions = []; + + for (final entry in _activeSessions.entries) { + final sessionId = entry.value; + final session = await _db.statisticsDao.getSessionBySessionId(sessionId); + + if (session != null && + session.endTime == null && + now.difference(session.startTime).inMinutes > _sessionTimeout.inMinutes) { + expiredSessions.add(sessionId); + } + } + + for (final sessionId in expiredSessions) { await endSession(sessionId); } } - /// Get active session for a user - Future getActiveSession(int userId) async { - final sessionId = _activeSessions[userId]; - if (sessionId == null) return null; - - return await _isar.studySessionModels - .filter() - .sessionIdEqualTo(sessionId) - .findFirst(); - } - - /// Get recent sessions for a user - Future> getRecentSessions( - int userId, { - int limit = 10, - }) async { - return await _isar.studySessionModels - .filter() - .userIdEqualTo(userId) - .sortByStartTimeDesc() - .limit(limit) - .findAll(); - } - - /// Clean up expired sessions - /// - /// Should be called periodically (e.g., via cron job) - Future cleanupExpiredSessions() async { - final cutoffTime = DateTime.now().subtract(_sessionTimeout); - - // Find sessions that should have expired but are still marked as active - final expiredSessions = await _isar.studySessionModels - .filter() - .endTimeIsNull() - .startTimeLessThan(cutoffTime) - .findAll(); - - for (final session in expiredSessions) { - await endSession(session.sessionId!); - } - } - - /// Get session statistics for a user over a time period - Future> getSessionStatistics( - int userId, { - DateTime? from, - DateTime? to, - }) async { - final query = _isar.studySessionModels.filter().userIdEqualTo(userId); - - if (from != null) { - query.startTimeGreaterThan(from); - } - if (to != null) { - query.startTimeLessThan(to); - } - - final sessions = await query.findAll(); - - if (sessions.isEmpty) { - return { - 'totalSessions': 0, - 'totalDurationMinutes': 0, - 'totalWordsLearned': 0, - 'totalTestsCompleted': 0, - 'averageAccuracy': 0.0, - 'averageSessionLength': 0.0, - }; - } - - final completedSessions = sessions.where((s) => !s.isActive).toList(); - final totalDuration = completedSessions.fold( - 0, - (sum, session) => sum + session.durationMinutes, - ); - final totalWordsLearned = completedSessions.fold( - 0, - (sum, session) => sum + session.wordsLearned, - ); - final totalTestsCompleted = completedSessions.fold( - 0, - (sum, session) => sum + session.testsCompleted, - ); - final averageAccuracy = completedSessions.isEmpty - ? 0.0 - : completedSessions.fold( - 0, - (sum, session) => sum + session.accuracy, - ) / - completedSessions.length; - - return { - 'totalSessions': sessions.length, - 'completedSessions': completedSessions.length, - 'totalDurationMinutes': totalDuration, - 'totalWordsLearned': totalWordsLearned, - 'totalTestsCompleted': totalTestsCompleted, - 'averageAccuracy': averageAccuracy, - 'averageSessionLength': completedSessions.isEmpty - ? 0.0 - : totalDuration / completedSessions.length, - }; - } - /// Generate a unique session ID String _generateSessionId() { - final timestamp = DateTime.now().millisecondsSinceEpoch; - final random = DateTime.now().microsecondsSinceEpoch % 10000; - return 'session_${timestamp}_$random'; + final random = Random(); + final bytes = List.generate(16, (i) => random.nextInt(256)); + return bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(); } - /// Reset the timeout timer for a session - void _resetSessionTimer(String sessionId) { - _cancelSessionTimer(sessionId); - - _sessionTimers[sessionId] = Timer(_sessionTimeout, () { - // Session timed out - end it - endSession(sessionId); + /// Start timeout timer for session + void _startSessionTimer(String sessionId) { + _sessionTimers[sessionId] = Timer(_sessionTimeout, () async { + await endSession(sessionId); }); } - /// Cancel the timeout timer for a session - void _cancelSessionTimer(String sessionId) { - final timer = _sessionTimers.remove(sessionId); - timer?.cancel(); + /// Reset timeout timer for session + void _resetSessionTimer(String sessionId) { + _sessionTimers[sessionId]?.cancel(); + _startSessionTimer(sessionId); } - /// Update session last activity time + /// Update session activity timestamp Future _updateSessionActivity(String sessionId) async { - // For now, we just reset the timer - // In the future, we could track last activity timestamps - } - - /// Dispose of all timers (for cleanup) - void dispose() { - for (final timer in _sessionTimers.values) { - timer.cancel(); + final session = await _db.statisticsDao.getSessionBySessionId(sessionId); + if (session != null) { + await _db.statisticsDao.updateSession( + session.copyWith(updatedAt: DateTime.now()), + ); } - _sessionTimers.clear(); - _activeSessions.clear(); } -} +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/tasks/task_manager.dart b/mnemo_cards_backend/lib/tasks/task_manager.dart new file mode 100644 index 0000000..8750117 --- /dev/null +++ b/mnemo_cards_backend/lib/tasks/task_manager.dart @@ -0,0 +1,106 @@ +import 'package:injectable/injectable.dart'; +import 'package:drift/drift.dart' as drift; +import 'package:mnemo_cards_backend/database/database.dart'; + +@lazySingleton +class TaskManager { + final AppDatabase _db; + + TaskManager(this._db); + + /// Получить все доступные задачи пользователя + Future> getUserTasks( + int userId, { + String? type, + String? difficulty, + String? status, + List? tags, + int? limit, + int? offset, + }) async { + // TODO: Implement with proper filtering + return await _db.taskDao.getUserTasks(userId); + } + + /// Получить задачу по ID + Future getUserTask(int userId, int taskId) async { + final tasks = await _db.taskDao.getUserTasks(userId); + return tasks.where((task) => task.id == taskId).firstOrNull; + } + + /// Начать выполнение задачи + Future startTask(int userId, int taskId) async { + await _db.transaction(() async { + // Проверить, что задача доступна + final task = await getUserTask(userId, taskId); + if (task == null || task.status != 'available') { + throw Exception('Task not available'); + } + + // Обновить статус задачи + await _db.taskDao.updateUserTask(task.copyWith(status: 'in_progress')); + + // Создать запись прогресса + await _db.taskDao.createTaskProgress( + UserTaskProgressesCompanion.insert( + userId: userId, + taskId: taskId, + progress: {'started': true}, + ), + ); + }); + } + + /// Завершить задачу + Future completeTask(int userId, int taskId) async { + await _db.transaction(() async { + // Проверить, что задача в процессе выполнения + final task = await getUserTask(userId, taskId); + if (task == null || task.status != 'in_progress') { + throw Exception('Task not in progress'); + } + + // Обновить статус задачи + final now = DateTime.now(); + await _db.taskDao.updateUserTask( + task.copyWith( + status: 'completed', + completedAt: drift.Value(now), + ), + ); + + // Создать запись результата + await _db.taskDao.createTaskResult( + UserTaskResultsCompanion.insert( + userId: userId, + taskId: taskId, + results: drift.Value({'completed': true, 'completedAt': now.toIso8601String()}), + ), + ); + + // TODO: Выдать награды пользователю + }); + } + + /// Получить прогресс выполнения задач пользователя + Future> getUserTaskProgress(int userId) async { + // TODO: Implement proper method in TaskDao + return []; // Placeholder + } + + /// Получить категории задач + Future> getTaskCategories() async { + // TODO: Extract unique categories from tasks + return ['app_internal', 'external', 'social']; + } + + /// Создать новую задачу для пользователя + Future createUserTask(UserTasksCompanion task) async { + return await _db.taskDao.createUserTask(task); + } + + /// Обновить задачу пользователя + Future updateUserTask(UserTask task) async { + return await _db.taskDao.updateUserTask(task); + } +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/tests/test_manager.dart b/mnemo_cards_backend/lib/tests/test_manager.dart index de81a96..d09676e 100644 --- a/mnemo_cards_backend/lib/tests/test_manager.dart +++ b/mnemo_cards_backend/lib/tests/test_manager.dart @@ -2,14 +2,14 @@ import 'dart:convert'; import 'dart:io'; import 'package:injectable/injectable.dart'; -import 'package:isar/isar.dart'; +import 'package:mnemo_cards_backend/database/database.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 'package:drift/drift.dart' as drift; -import '../main.dart'; import '../packs/pack_dto_converter.dart'; import 'generators/question_generators/input_buttons_question_generator.dart'; import 'generators/pack_test_generator.dart'; @@ -17,214 +17,228 @@ import 'generators/question_generators/simple_question_generator.dart'; @lazySingleton class TestManager { + final AppDatabase _db; final PackDtoConverter _packDtoConverter; List _customCreationTestData = []; - TestManager(this._packDtoConverter); + TestManager(this._db, this._packDtoConverter); Future _testStatisticsDto( - UserModel user, int testId) async { - return (await user.userData.value?.testsStatistics - .filter() - .test((q) => q.idEqualTo(testId)) - .findFirst()) - ?.toDto(); + int userId, int testId) async { + final statistics = await _db.testDao.getTestStatistics(userId, testId); + if (statistics == null) return null; + + // Convert TestStatistic to TestStatisticsDto + return TestStatisticsDto( + testId: statistics.testId.toString(), + results: statistics.results, + completedAt: statistics.completedAt.toIso8601String(), + ); } Future fetchTest(String id, UserModel user) async { - return (await isar.testModels.get(int.parse(id)))?.toDto( - statistics: await _testStatisticsDto(user, int.parse(id)), + final testId = int.tryParse(id); + if (testId == null) return null; + + final test = await _db.testDao.getTestById(testId); + if (test == null) return null; + + final questions = await _db.testDao.getTestQuestions(testId); + final statistics = await _testStatisticsDto(user.id!, testId); + + // Convert Test to TestDto + final questionsList = questions.map((q) { + // Parse question body based on type + final body = json.decode(q.body) as Map; + return AbstractTestQuestion.fromJson({ + 'questionType': q.questionType, + ...body, + }); + }).toList(); + + return TestDto( + id: testId.toString(), + name: test.name, + color: test.color, + cover: test.cover, + version: test.version ?? '1.0', + time: test.time, + timeSubtitle: test.timeSubtitle, + questions: questionsList, + statistics: statistics, ); } 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(); + final tests = await _db.testDao.getAllTests(); + + final testDtos = []; + for (final test in tests) { + final questions = await _db.testDao.getTestQuestions(test.id); + final statistics = await _testStatisticsDto(userModel.id!, test.id); + + final questionsList = questions.map((q) { + final body = json.decode(q.body) as Map; + return AbstractTestQuestion.fromJson({ + 'questionType': q.questionType, + ...body, + }); + }).toList(); + + testDtos.add(TestDto( + id: test.id.toString(), + name: test.name, + color: test.color, + cover: test.cover, + version: test.version ?? '1.0', + time: test.time, + timeSubtitle: test.timeSubtitle, + questions: questionsList, + statistics: statistics, + )); + } + + return testDtos; } 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; + final packId = model.id; + if (packId == null) return []; + + final tests = await _db.testDao.getTestsByPackId(packId); + + final testDtos = []; + for (final test in tests) { + final questions = await _db.testDao.getTestQuestions(test.id); + final statistics = await _testStatisticsDto(user.id!, test.id); + + final questionsList = questions.map((q) { + final body = json.decode(q.body) as Map; + return AbstractTestQuestion.fromJson({ + 'questionType': q.questionType, + ...body, + }); + }).toList(); + + testDtos.add(TestDto( + id: test.id.toString(), + name: test.name, + color: test.color, + cover: test.cover, + version: test.version ?? '1.0', + time: test.time, + timeSubtitle: test.timeSubtitle, + questions: questionsList, + statistics: statistics, + )); } - return (await Future.wait( - testModels.map( - (e) async => e.toDto( - statistics: await _testStatisticsDto(user, e.id!), - ), - ), - )) - .toList(); + + return testDtos; } Future _updateGeneratedTestsIfRequired(CardPackModel model) async { - final generated = model.tests.where((t) => t.version == 'generated'); - if (generated.isEmpty) { + final packId = model.id; + if (packId == null) return false; + + // Check if pack has any generated tests + final existingTests = await _db.testDao.getTestsByPackId(packId); + final hasGeneratedTests = existingTests.any((test) => + test.version?.contains('generated') ?? false); + + // Generate tests if none exist + if (!hasGeneratedTests) { 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; + // Load custom creation test data from database + // For now, keep it simple + _customCreationTestData = []; } 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, - }, - ), - ]; + final packId = model.id; + if (packId == null) return; - 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!); - } - } - }); + // Get pack data from database + final pack = await _db.packDao.getPackById(packId); + if (pack == null) return; + + // Get all cards for the pack + final cards = await _db.packDao.getPackCards(packId); + if (cards.isEmpty) return; + + // Create creation test data + final testDataItems = cards.map((card) { + return TestDataItem( + id: card.id.toString(), + original: card.original, + translation: card.translation, + mnemo: card.mnemo, + image: card.image, + back: card.back, + transcription: card.transcription, + ); + }).toList(); + + final creationTestData = CreationTestData( + items: testDataItems, + color: pack.color, + packId: packId.toString(), + ); + + // Generate test using PackTestGenerator + final generator = PackTestGenerator(creationTestData); + final testDto = await generator.generate( + name: '${pack.title} Test', + ratios: { + TestQuestionType.simple: 0.7, + TestQuestionType.input_buttons: 0.3, + }, + multiply: 1.0, + ); + + // Save test to database + await addTest(testDto); + + // Link test to pack + final testId = int.tryParse(testDto.id ?? ''); + if (testId != null) { + await _db.testDao.linkTestToPack(testId, packId); + } } 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 _db.transaction(() async { + // Create test + final testCompanion = TestsCompanion.insert( + name: testDto.name, + color: drift.Value(testDto.color), + cover: drift.Value(testDto.cover), + version: drift.Value(testDto.version), + time: drift.Value(testDto.time), + timeSubtitle: drift.Value(testDto.timeSubtitle), + ); + + final testId = await _db.testDao.createTest(testCompanion); + + // Create questions + for (final question in testDto.questions) { + final questionCompanion = TestQuestionsCompanion.insert( + testId: testId, + questionType: question.questionType.name, + body: json.encode(question.toJson()), ); - await isar.testQuestionModels.put(model); - questions.add(model); + + await _db.testDao.createTestQuestion(questionCompanion); } - final testModel = testDto.toEmptyModel(); - await isar.testModels.put(testModel); - await testModel.questions - ..addAll(questions) - ..save(); }); } -} +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/tests/test_manager.dart.backup b/mnemo_cards_backend/lib/tests/test_manager.dart.backup new file mode 100644 index 0000000..bc9182c --- /dev/null +++ b/mnemo_cards_backend/lib/tests/test_manager.dart.backup @@ -0,0 +1,211 @@ +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'; + +// TODO: This is a temporary stub implementation while migrating to Drift +@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 { + // TODO: Implement with Drift + throw UnimplementedError('TestManager not yet migrated to Drift'); + } + + Future> availableTests(UserModel userModel) async { + // TODO: Implement with Drift + return []; // Return empty list for now + } + + Future> fetchPackTests( + UserModel user, + CardPackModel model, + ) async { + // TODO: Implement with Drift + return []; // Return empty list for now + } + + 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(); + }); + } +} diff --git a/mnemo_cards_backend/lib/user/user_drift_extension.dart b/mnemo_cards_backend/lib/user/user_drift_extension.dart new file mode 100644 index 0000000..24cde69 --- /dev/null +++ b/mnemo_cards_backend/lib/user/user_drift_extension.dart @@ -0,0 +1,40 @@ +import 'package:isar/isar.dart'; +import 'package:drift/drift.dart' as drift; +import 'package:mnemo_cards_backend/database/database.dart'; +import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; + +/// Extension для конвертации User (Drift) в UserModel (Isar) +extension UserToUserModel on User { + Future toUserModel() async { + // Создаем UserModel на основе User из Drift + final userModel = UserModel( + id: id, + name: name, + email: email, + admin: admin, + // purchases и userSettings нужно загружать отдельно из UserDatas + purchases: [], // TODO: load from UserDatas + userSettings: null, // TODO: load from UserDatas + ); + + // TODO: Загрузить связанные данные (userData, packs, subscription) + // Пока оставляем пустыми для совместимости + + return userModel; + } +} + +/// Extension для конвертации UserModel в User (Drift) +extension UserModelToUser on UserModel { + UsersCompanion toUsersCompanion() { + return UsersCompanion( + id: id != null ? drift.Value(id as int) : const drift.Value.absent(), + // Note: UserModel не имеет externalUserId, purchases, userSettings + // Эти поля нужно брать из других источников или генерировать + externalUserId: const drift.Value.absent(), // TODO: generate or get from context + name: drift.Value(name), + email: drift.Value(email), + admin: drift.Value(admin), + ); + } +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/user/user_manager.dart b/mnemo_cards_backend/lib/user/user_manager.dart index 97cb23b..05d7f5b 100644 --- a/mnemo_cards_backend/lib/user/user_manager.dart +++ b/mnemo_cards_backend/lib/user/user_manager.dart @@ -1,7 +1,8 @@ import 'dart:developer'; import 'package:injectable/injectable.dart'; -import 'package:isar/isar.dart'; +import 'package:drift/drift.dart' as drift; +import 'package:mnemo_cards_backend/database/database.dart'; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; @@ -11,77 +12,76 @@ import '../statistics/session_tracker.dart'; import '../statistics/statistics_calculator.dart'; import '../statistics/achievement_manager.dart'; import 'secure.dart'; +import 'user_drift_extension.dart'; -Map _onlineUsers = {}; +Map _onlineUsers = {}; @lazySingleton class UserManager { + final AppDatabase _db; final FreePacksDistributor _freePacksDistributor; - final SessionTracker _sessionTracker; - final StatisticsCalculator _statisticsCalculator; - final AchievementManager _achievementManager; + // TODO: Re-enable when migrated to PostgreSQL + // final SessionTracker _sessionTracker; + // final StatisticsCalculator _statisticsCalculator; + // final AchievementManager _achievementManager; UserManager( + this._db, this._freePacksDistributor, - this._sessionTracker, - this._statisticsCalculator, - this._achievementManager, + // this._sessionTracker, + // this._statisticsCalculator, + // this._achievementManager, ); - Future fetchUser(Id id) async { - final user = await isar.userModels.get(id); - return user; + Future fetchUser(int id) async { + final user = await _db.userDao.getUserById(id); + if (user == null) return null; + return await user.toUserModel(); } - DateTime? lastOnline(Id id) => _onlineUsers[id]; + DateTime? lastOnline(int id) => _onlineUsers[id]; Future createOrGetAuthToken(UserModel user, String externalId) async { if (user.id == null) { throw Exception('Cant create token for empty id'); } final now = DateTime.now(); - final tokenModel = - await isar.tokenModels.filter().userIdEqualTo(user.id!).findFirst(); - if (tokenModel != null) { - if (tokenModel.expires.isAfter(now)) { - return tokenModel.token; + final token = await _db.userDao.getTokenByUserId(user.id!); + if (token != null) { + if (token.expires.isAfter(now)) { + return token.token; } - await isar.tokenModels.delete(tokenModel.id!); + await _db.userDao.deleteToken(token.id); } final userToken = Secure.token(); - await isar.tokenModels.put( - TokenModel( + await _db.userDao.createToken( + TokensCompanion.insert( token: userToken, externalUserId: externalId, userId: user.id!, - created: now, - expires: now.add(Duration(days: 360)), + expires: now.add(const Duration(days: 360)), ), ); return userToken; } Future getUserByToken(String authToken) async { - final tokenModel = - await isar.tokenModels.filter().tokenEqualTo(authToken).findFirst(); - if (tokenModel == null) { + final token = await _db.userDao.getTokenByValue(authToken); + if (token == null) { return null; } final now = DateTime.now(); - if (tokenModel.expires.isBefore(now)) { - isar.writeTxn(() => isar.tokenModels.delete(tokenModel.id!)); + if (token.expires.isBefore(now)) { + await _db.userDao.deleteToken(token.id); return null; } - final user = await isar.userModels.get(tokenModel.userId); - if (user == null) { - return null; - } else { - _onlineUsers[user.id] = now; + final user = await fetchUser(token.userId); + if (user != null) { + _onlineUsers[token.userId] = now; if (_onlineUsers.length > 300) { updateOnlineUsers(); } } - return user; } @@ -89,28 +89,23 @@ class UserManager { if (_onlineUsers.isEmpty) { return; } - return isar.writeTxn(() async { - final ids = _onlineUsers.keys.whereNotNull().toList(); - final users = (await isar.userModels.getAll(ids)).whereNotNull(); - await Future.wait(users.map((user) => user.userData.load())); - final updated = users - .map((user) => user.userData.value?.copyWith( - lastTimeOnline: _onlineUsers[user.id] ?? - user.userData.value?.lastTimeOnline, - )) - .whereNotNull() - .toList(); - await isar.userDataModels.putAll(updated); - _onlineUsers.clear(); - }); - } - Future> getTokensByUser(String userId) async { - final id = int.tryParse(userId); - if (id == null) return []; - return isar.txn( - () => isar.tokenModels.filter().userIdEqualTo(id).findAll(), - ); + final userIds = _onlineUsers.keys.whereNotNull().toList(); + + for (final userId in userIds) { + final lastOnline = _onlineUsers[userId]; + if (lastOnline != null) { + await _db.userDao.updateUserDataPartial( + UserDatasCompanion( + userId: drift.Value(userId), + lastTimeOnline: drift.Value(lastOnline), + updatedAt: drift.Value(DateTime.now()), + ), + ); + } + } + + _onlineUsers.clear(); } Future<(UserModel, String)> createOrGetUser({ @@ -118,369 +113,49 @@ class UserManager { required String email, String? name, }) async { - final tokenModel = await isar.tokenModels - .filter() - .externalUserIdEqualTo(externalId) - .findFirst(); - var user = tokenModel == null ? null : await fetchUser(tokenModel.userId); - if (user == null) { - print('Creating new user $name $email'); - final modelAndToken = await isar.writeTxn(() async { - final userModel = UserModel.empty.copyWith( - email: email, - name: name, - ); - - await isar.userModels.put(userModel); - print('User $name $email saved'); - await isar.tokenModels - .filter() - .externalUserIdEqualTo(externalId) - .deleteFirst(); - print('User tokens deleted $name $email'); - final token = await createOrGetAuthToken(userModel, externalId); - final userData = UserDataModel()..user.value = userModel; - await isar.userDataModels.put(userData); - userModel.userData.value = userData; - await userModel.userData.save(); - return (userModel, token); - }); - await _freePacksDistributor.giveFreePacksToUser(modelAndToken.$1); - return modelAndToken; - } else { - if (user.email == null) { - user = user.copyWith.email(email); - await isar.writeTxn(() => isar.userModels.put(user!)); - } - await isar.writeTxn(() async { - await user!.userData.load(); - if (user.userData.value == null) { - final userData = UserDataModel()..user.value = user; - await isar.userDataModels.put(userData); - user.userData.value = userData; - await user.userData.save(); - } - }); - return (user, tokenModel!.token); + // Check if user exists by externalId + final existingUser = await _db.userDao.getUserByExternalId(externalId); + if (existingUser != null) { + print('User found $name $email'); + final userModel = await existingUser.toUserModel(); + final token = await createOrGetAuthToken(userModel, externalId); + return (userModel, token); } - } - // Future addWordStatistics( - // UserModel user, AllWordsStatisticsDto wordStat) async { - // // final currentData = - // // user.userData?.decode(UserDataDto.fromJson) ?? UserDataDto(); - // // updateUserData( - // // user, - // // currentData.copyWith.allWordsStatistics( - // // wordStat.merge(currentData.allWordsStatistics), - // // ), - // // ); - // } + print('Creating new user $name $email'); - Future addTestStatistics( - UserModel user, - TestStatisticsDto testStat, - ) async { - UserDataModel currentData; - print('adding test stats'); - if (user.userData.value == null) { - print('new user data'); - currentData = UserDataModel(); - await isar.writeTxn(() async { - final id = await isar.userDataModels.put( - currentData..user.value = user, - ); - currentData = (await isar.userDataModels.get(id))!; - }); - print('user data saved'); - } else { - print('found user data'); - currentData = user.userData.value!; - } - if (testStat.sessionToken == currentData.lastTestSessionToken) { - print('old session token'); - return; - } - final testStatistics = await isar.txn( - () async => - await currentData.testsStatistics - .filter() - .test((q) => q.idEqualTo(testStat.testId)) - .findFirst() ?? - TestStatisticsModel(), + // Create new user + final userCompanion = UsersCompanion.insert( + externalUserId: externalId, + name: drift.Value(name), + email: drift.Value(email), ); - print('got tests stat for ${testStat.testId}'); + final userId = await _db.userDao.createUser(userCompanion); - final allWords = currentData.words.mergeWithDto(testStat.words.words); - print( - 'merged all words: ${allWords.length} (was ${currentData.words.length})'); - - return isar.writeTxn(() async { - try { - final test = await isar.testModels.get(testStat.testId); - // final testWords = updateWords(testStatistics.words, testStat.words.words); - final updatedTestStat = testStatistics.copyWith.attempts( - [ - ...testStatistics.attempts.takeLast(2), - TestAttempt( - words: testStat.words.words.map((e) => e.toModel()).toList(), - sessionToken: testStat.sessionToken, - ), - ], - )..test.value = test; - - await isar.testStatisticsModels.put(updatedTestStat); - await updatedTestStat.test.save(); - - print('updated test stats saved'); - - // Calculate updated statistics - final studyDates = List.from(currentData.studyDates); - final today = DateTime.now(); - final todayNormalized = DateTime(today.year, today.month, today.day); - - // Add today's study date if not already present - if (!studyDates.any((date) => - date.year == todayNormalized.year && - date.month == todayNormalized.month && - date.day == todayNormalized.day)) { - studyDates.add(todayNormalized); - } - - // Calculate current streak based on study dates - final currentStreak = _statisticsCalculator.calculateStreak(studyDates); - - // Calculate longest streak - final longestStreak = currentStreak > currentData.longestStreak - ? currentStreak - : currentData.longestStreak; - - // Calculate total study time (simplified - add time from this test) - final testDurationMinutes = 5; // Assume 5 minutes per test as default - final totalStudyTimeMinutes = - currentData.totalStudyTimeMinutes + testDurationMinutes; - - // Calculate pack progress if we have pack info - final packProgress = - List.from(currentData.packProgress); - - // Update pack progress if test was for a specific pack - if (testStat.testId != null) { - // This would need pack information lookup - simplified for now - // In a real implementation, we'd update the specific pack's progress - } - - // Check and unlock achievements with updated data - final tempUpdatedData = currentData.copyWith( - currentStreak: currentStreak, - longestStreak: longestStreak, - studyDates: studyDates, - totalStudyTimeMinutes: totalStudyTimeMinutes, - words: allWords, - ); - - // Check for newly unlocked achievements - await checkAndUpdateAchievements(user, tempUpdatedData); - - final updatedData = currentData.copyWith( - lastTestSessionToken: testStat.sessionToken, - words: allWords, - currentStreak: currentStreak, - longestStreak: longestStreak, - studyDates: studyDates, - totalStudyTimeMinutes: totalStudyTimeMinutes, - lastTimeOnline: today, - ) - ..testsStatistics.add(updatedTestStat) - ..user.value = user; - - // Note: packProgress and achievements are handled separately - // since UserDataModel expects Model types, not DTO types - - print('saving updated user data'); - await isar.userDataModels.put(updatedData); - await updatedData.testsStatistics.save(); - await updatedData.user.save(); - print('user data saved'); - - // Track session activity - await _updateSessionFromTest(user, testStat, updatedData); - } catch (e, s) { - print('error while saving data ${e.toString()} ${s.toString()}'); - log('error', error: e, stackTrace: s); - } - }); - } - - /// Check and update achievements for a user - Future> checkAndUpdateAchievements( - UserModel user, - UserDataModel userData, - ) async { - try { - final newlyUnlockedAchievements = - await _achievementManager.checkAndUnlockAchievements( - user.id!, - userData, - ); - - if (newlyUnlockedAchievements.isNotEmpty) { - print( - 'New achievements unlocked for user ${user.id}: ${newlyUnlockedAchievements.map((a) => a.title).join(', ')}'); - } - - return newlyUnlockedAchievements; - } catch (e, s) { - print('Error checking achievements for user ${user.id}: $e\n$s'); - return []; - } - } - - /// Update session tracking based on test completion - Future _updateSessionFromTest( - UserModel user, - TestStatisticsDto testStat, - UserDataModel updatedData, - ) async { - if (user.id == null) return; - - try { - // Get or create session for this user - final sessionId = await _sessionTracker.getOrCreateSession( - user.id!, - testId: testStat.testId.toString(), - ); - - // Calculate test statistics - final wordsLearned = testStat.words.words.length; - final correctAnswers = testStat.words.words.fold( - 0, - (sum, word) => sum + word.correct.toInt(), - ); - final totalAnswers = testStat.words.words.fold( - 0, - (sum, word) => sum + word.correct.toInt() + word.incorrect.toInt(), - ); - final accuracy = totalAnswers > 0 ? correctAnswers / totalAnswers : 0.0; - - // Update session progress - await _sessionTracker.updateSessionProgress( - sessionId, - wordsLearned: wordsLearned, - testsCompleted: 1, - accuracy: accuracy, - ); - - print( - 'Updated session $sessionId: +$wordsLearned words, accuracy: ${accuracy.toStringAsFixed(2)}'); - } catch (e, s) { - print('Error updating session tracking: $e\n$s'); - // Don't fail the main operation if session tracking fails - } - } - - Future updateUserSettings(UserModel user, UserSettingsDto settings) { - return isar.writeTxn( - () => isar.userModels.put( - user.copyWith(userSettings: settings.encode()), + // Create user data + await _db.userDao.createUserData( + UserDatasCompanion.insert( + userId: userId, + registrationDate: drift.Value(DateTime.now()), ), ); + + final user = await _db.userDao.getUserById(userId); + if (user == null) { + throw Exception('Failed to create user'); + } + + final userModel = await user.toUserModel(); + final token = await createOrGetAuthToken(userModel, externalId); + + // Give free packs to new user + await _freePacksDistributor.giveFreePacksToUser(userModel); + + print('User $name $email created successfully'); + return (userModel, token); } - Future editUser(UserDto user) async { - if (user.id == null || user.id! < 0) { - throw Exception('Cant edit new user'); - } - final existUser = await isar.userModels.get(user.id!); - if (existUser == null) { - throw Exception('User ${user.id} not found'); - } - final dtoPackIds = user.packs.map((s) => int.tryParse(s)).whereNotNull(); - final dtoPacks = dtoPackIds.isEmpty - ? [] - : (await isar.cardPackModels.getAll(dtoPackIds.toList())) - .whereNotNull() - .toList(); - isar.writeTxn(() async { - final updatedUser = existUser.copyWith( - name: user.name ?? existUser.name, - // subscription: user.subscription, - // userSettings: user.userSettingsDto?.encode() ?? existUser.userSettings, - // userData: user.userDataDto?.encode() ?? existUser.userData, - ); - final id = await isar.userModels.put(updatedUser); - final packs = (await isar.userModels.get(id))!.packs; - await updatedUser.subscriptionModel.load(); - final subscriptionModel = updatedUser.subscriptionModel.value; - print( - 'Editing user ${user.id}, subscription = ${user.subscription} and hasModel = ${subscriptionModel != null}', - ); - if (user.subscription == true) { - UserSubscriptionModel userSubscriptionModel; - if (subscriptionModel == null) { - print('Creating new sub model'); - userSubscriptionModel = UserSubscriptionModel( - start: DateTime.now(), - finish: DateTime.now().add(Duration(days: 1)), - features: [ - SubscriptionFeatureEnum.packs, - SubscriptionFeatureEnum.ads, - ], - ); - } else { - print('Updating sub model'); - userSubscriptionModel = subscriptionModel.copyWith( - start: DateTime.now(), - finish: DateTime.now().add(Duration(days: 1)), - features: [ - SubscriptionFeatureEnum.packs, - SubscriptionFeatureEnum.ads, - ]); - } - updatedUser.subscriptionModel.value = userSubscriptionModel; - await isar.userSubscriptionModels.put(userSubscriptionModel); - await updatedUser.subscriptionModel.save(); - } else if (subscriptionModel != null) { - final updatedModel = subscriptionModel.copyWith( - start: DateTime.now(), - finish: DateTime.now(), - features: [], - )..user.value = updatedUser; - await isar.userSubscriptionModels.put(updatedModel); - updatedUser.subscriptionModel.value = updatedModel; - await updatedUser.subscriptionModel.save(); - } - await packs.reset(); - await (packs..addAll(dtoPacks)).save(); - }); - return true; - } - - Future deleteUser(String stringId) async { - final id = int.tryParse(stringId); - if (id != null) { - await isar.writeTxn(() async { - final tokens = await isar.tokenModels - .filter() - .userIdEqualTo(id) - .idProperty() - .findAll(); - if (tokens.isNotEmpty) { - await isar.tokenModels.deleteAll(tokens); - } - await isar.testStatisticsModels - .filter() - .userData((q) => q.user((u) => u.idEqualTo(id))) - .deleteAll(); - await isar.userDataModels - .filter() - .user((q) => q.idEqualTo(id)) - .deleteAll(); - return isar.userModels.delete(id); - }); - return true; - } - return false; - } -} + // TODO: Implement remaining methods as needed + // updateUserSettings, addTestStatistics, editUser, deleteUser +} \ No newline at end of file diff --git a/mnemo_cards_backend/lib/user/user_manager_drift.dart b/mnemo_cards_backend/lib/user/user_manager_drift.dart new file mode 100644 index 0000000..e19bb38 --- /dev/null +++ b/mnemo_cards_backend/lib/user/user_manager_drift.dart @@ -0,0 +1,160 @@ +import 'dart:developer'; + +import 'package:injectable/injectable.dart'; +import 'package:drift/drift.dart' as drift; +import 'package:mnemo_cards_backend/database/database.dart'; +import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; +import 'package:mnemo_cards_common/mnemo_cards_common.dart'; + +import '../main.dart'; +import '../packs/free_packs_distributor.dart'; +import '../statistics/session_tracker.dart'; +import '../statistics/statistics_calculator.dart'; +import '../statistics/achievement_manager.dart'; +import 'secure.dart'; +import 'user_drift_extension.dart'; + +Map _onlineUsers = {}; + +@lazySingleton +class UserManager { + final AppDatabase _db; + final FreePacksDistributor _freePacksDistributor; + final SessionTracker _sessionTracker; + final StatisticsCalculator _statisticsCalculator; + final AchievementManager _achievementManager; + + UserManager( + this._db, + this._freePacksDistributor, + this._sessionTracker, + this._statisticsCalculator, + this._achievementManager, + ); + + Future fetchUser(int id) async { + final user = await _db.userDao.getUserById(id); + if (user == null) return null; + return await user.toUserModel(); + } + + DateTime? lastOnline(int id) => _onlineUsers[id]; + + Future createOrGetAuthToken(UserModel user, String externalId) async { + if (user.id == null) { + throw Exception('Cant create token for empty id'); + } + final now = DateTime.now(); + final token = await _db.userDao.getTokenByUserId(user.id!); + if (token != null) { + if (token.expires.isAfter(now)) { + return token.token; + } + await _db.userDao.deleteToken(token.id); + } + final userToken = Secure.token(); + await _db.userDao.createToken( + TokensCompanion.insert( + token: userToken, + externalUserId: externalId, + userId: user.id!, + expires: now.add(const Duration(days: 360)), + ), + ); + return userToken; + } + + Future getUserByToken(String authToken) async { + final token = await _db.userDao.getTokenByValue(authToken); + if (token == null) { + return null; + } + final now = DateTime.now(); + if (token.expires.isBefore(now)) { + await _db.userDao.deleteToken(token.id); + return null; + } + final user = await fetchUser(token.userId); + if (user != null) { + _onlineUsers[token.userId] = now; + if (_onlineUsers.length > 300) { + updateOnlineUsers(); + } + } + return user; + } + + Future updateOnlineUsers() async { + if (_onlineUsers.isEmpty) { + return; + } + + final userIds = _onlineUsers.keys.whereNotNull().toList(); + + for (final userId in userIds) { + final lastOnline = _onlineUsers[userId]; + if (lastOnline != null) { + await _db.userDao.updateUserDataPartial( + UserDatasCompanion( + userId: drift.Value(userId), + lastTimeOnline: drift.Value(lastOnline), + updatedAt: drift.Value(DateTime.now()), + ), + ); + } + } + + _onlineUsers.clear(); + } + + Future<(UserModel, String)> createOrGetUser({ + required String externalId, + required String email, + String? name, + }) async { + // Check if user exists by externalId + final existingUser = await _db.userDao.getUserByExternalId(externalId); + if (existingUser != null) { + print('User found $name $email'); + final userModel = await existingUser.toUserModel(); + final token = await createOrGetAuthToken(userModel, externalId); + return (userModel, token); + } + + print('Creating new user $name $email'); + + // Create new user + final userCompanion = UsersCompanion.insert( + externalUserId: externalId, + name: drift.Value(name), + email: drift.Value(email), + ); + + final userId = await _db.userDao.createUser(userCompanion); + + // Create user data + await _db.userDao.createUserData( + UserDatasCompanion.insert( + userId: userId, + registrationDate: drift.Value(DateTime.now()), + ), + ); + + final user = await _db.userDao.getUserById(userId); + if (user == null) { + throw Exception('Failed to create user'); + } + + final userModel = await user.toUserModel(); + final token = await createOrGetAuthToken(userModel, externalId); + + // Give free packs to new user + await _freePacksDistributor.giveFreePacksToUser(userModel); + + print('User $name $email created successfully'); + return (userModel, token); + } + + // TODO: Implement remaining methods as needed + // updateUserSettings, addTestStatistics, editUser, deleteUser +} \ No newline at end of file diff --git a/mnemo_cards_backend/public/open_api.yaml b/mnemo_cards_backend/public/open_api.yaml index 6773a20..6111773 100644 --- a/mnemo_cards_backend/public/open_api.yaml +++ b/mnemo_cards_backend/public/open_api.yaml @@ -129,15 +129,31 @@ paths: responses: 200: description: "Operation completed!" - /ads/product/acquire/: + /cards: + get: + tags: + - AdminCardsApiV2 + summary: getAllCards + operationId: getAllCards + responses: + 200: + description: "Operation completed!" post: tags: - - AdsApiV2 - summary: "POST /api/v2/ads/product/acquire/{key}" - description: Confirms rewarded ad completion and grants product access to the user. - operationId: acquireProductForAd + - AdminCardsApiV2 + summary: createCard + operationId: createCard + responses: + 200: + description: "Operation completed!" + /cards/: + get: + tags: + - AdminCardsApiV2 + summary: getCard + operationId: getCard parameters: - - name: key + - name: cardId in: path required: true schema: @@ -145,42 +161,11 @@ paths: responses: 200: description: "Operation completed!" - /adsgram/reward: - get: - tags: - - AdsApiV2 - summary: "GET /api/v2/adsgram/reward?userId={userId}" - description: Callback endpoint for Adsgram rewarded ad completion.\nThis endpoint is called by Adsgram when a user completes a rewarded ad. - operationId: adsgramRewardCallback - responses: - 200: - description: "Operation completed!" - /admin/cards: - get: + put: tags: - AdminCardsApiV2 - summary: getCards - description: "GET /api/v2/admin/cards\nGet all cards with pagination and optional search\nQuery params: ?page=1&limit=20&search=term" - operationId: getCards - responses: - 200: - description: "Operation completed!" - post: - tags: - - AdminCardsApiV2 - summary: upsertCard - description: POST /api/v2/admin/cards\nCreate or update a card - operationId: upsertCard - responses: - 200: - description: "Operation completed!" - /admin/cards/: - get: - tags: - - AdminCardsApiV2 - summary: getCard - description: "GET /api/v2/admin/cards/:id\nGet a specific card by ID" - operationId: getCard + summary: updateCard + operationId: updateCard parameters: - name: cardId in: path @@ -194,7 +179,6 @@ paths: tags: - AdminCardsApiV2 summary: deleteCard - description: "DELETE /api/v2/admin/cards/:id\nDelete a card by ID" operationId: deleteCard parameters: - name: cardId @@ -354,275 +338,6 @@ paths: responses: 200: description: "Operation completed!" - /admin/users: - get: - tags: - - AdminUsersApiV2 - summary: getUsers - description: GET /api/v2/admin/users\nReturns list of users by optional ids. - operationId: getUsers - responses: - 200: - description: "Operation completed!" - post: - tags: - - AdminUsersApiV2 - summary: upsertUser - description: POST /api/v2/admin/users\nCreates or updates user data (admin editing). - operationId: upsertUser - responses: - 200: - description: "Operation completed!" - /admin/users/ids: - get: - tags: - - AdminUsersApiV2 - summary: getUserIds - description: GET /api/v2/admin/users/ids\nReturns comma separated user ids. - operationId: getUserIds - responses: - 200: - description: "Operation completed!" - /admin/users//purchases: - get: - tags: - - AdminUsersApiV2 - summary: getUserPurchases - description: "GET /api/v2/admin/users//purchases\nReturns user payments history." - operationId: getUserPurchases - parameters: - - name: userId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - /admin/users/: - delete: - tags: - - AdminUsersApiV2 - summary: deleteUser - description: "DELETE /api/v2/admin/users/\nDeletes user." - operationId: deleteUser - parameters: - - name: userId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - /games: - get: - tags: - - GamesApiV2 - summary: getGames - description: GET /api/v2/games\nGet all available games\nReturns list of games with metadata - operationId: getGames - responses: - 200: - description: "Operation completed!" - /games//assets: - get: - tags: - - GamesApiV2 - summary: getGameAssets - description: "GET /api/v2/games/{gameId}/assets\nGet game assets\nReturns game assets file (zip) or asset info" - operationId: getGameAssets - parameters: - - name: gameId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - /packs: - get: - tags: - - PacksApiV2 - summary: getPacks - description: "GET /api/v2/packs\nGet all pack previews with pagination\nQuery params: ?search=term&language=lang&page=1&limit=20" - operationId: getPacks - responses: - 200: - description: "Operation completed!" - /packs/: - get: - tags: - - PacksApiV2 - summary: getPack - description: "GET /api/v2/packs/{packId}\nGet pack details by ID\nReturns full pack details with purchase status if authenticated" - operationId: getPack - parameters: - - name: packId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - /packs//buy: - get: - tags: - - PacksApiV2 - summary: getPackBuyPage - description: "GET /api/v2/packs/{packId}/buy\nReturns pack purchase details (includes rewarded ads offer when available)\nWorks for both authenticated and unauthenticated users" - operationId: getPackBuyPage - parameters: - - name: packId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - /packs//cards: - get: - tags: - - PacksApiV2 - summary: getPackCards - description: "GET /api/v2/packs/{packId}/cards\nGet all cards in a pack\nSupports pagination via query params: ?page=1&limit=20" - operationId: getPackCards - parameters: - - name: packId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - /packs//cards//image: - get: - tags: - - PacksApiV2 - summary: getCardImage - description: "GET /api/v2/packs/{packId}/cards/{cardId}/image\nGet card image\nReturns PNG image file\n\nImages are accessible for enabled packs even without authentication\nto allow image preview in public pack listings" - operationId: getCardImage - parameters: - - name: packId - in: path - required: true - schema: - type: string - - name: cardId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - /packs//cards//voices: - get: - tags: - - PacksApiV2 - summary: getCardVoices - description: "GET /api/v2/packs/{packId}/cards/{cardId}/voices\nGet card voices metadata\nReturns JSON list of VoiceDto" - operationId: getCardVoices - parameters: - - name: packId - in: path - required: true - schema: - type: string - - name: cardId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - /voice/: - get: - tags: - - PacksApiV2 - summary: getVoiceFile - description: "GET /api/v2/voice/{voiceId}\nReturns audio/mp3 bytes for the voice file" - operationId: getVoiceFile - parameters: - - name: voiceId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - /packs//tests: - get: - tags: - - PacksApiV2 - summary: getPackTests - description: "GET /api/v2/packs/{packId}/tests\nGet tests for a pack" - operationId: getPackTests - parameters: - - name: packId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - /admin/packs: - get: - tags: - - AdminPacksApiV2 - summary: getPacks - description: "GET /api/v2/admin/packs\nGet all packs with pagination and optional search\nQuery params: ?page=1&limit=20&search=term&showDisabled=true" - operationId: getPacks - responses: - 200: - description: "Operation completed!" - post: - tags: - - AdminPacksApiV2 - summary: upsertPack - description: POST /api/v2/admin/packs\nCreate or update a pack - operationId: upsertPack - responses: - 200: - description: "Operation completed!" - /admin/packs/: - get: - tags: - - AdminPacksApiV2 - summary: getPack - description: "GET /api/v2/admin/packs/:id\nGet pack details by ID for editing" - operationId: getPack - parameters: - - name: packId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - delete: - tags: - - AdminPacksApiV2 - summary: deletePack - description: "DELETE /api/v2/admin/packs/:id\nDelete a pack by ID" - operationId: deletePack - parameters: - - name: packId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" /admin/auth/request-code: post: tags: @@ -669,64 +384,6 @@ paths: responses: 200: description: "Operation completed!" - /purchases/packs/: - post: - tags: - - PurchasesApiV2 - summary: createPackPurchase - description: "POST /api/v2/purchases/packs/{packId}\nCreate purchase intent for a pack\nReturns purchase info including payment URL for YooKassa" - operationId: createPackPurchase - parameters: - - name: packId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - /purchases/packs//status: - get: - tags: - - PurchasesApiV2 - summary: getPackPurchaseStatus - description: "GET /api/v2/purchases/packs/{packId}/status\nCheck if pack is purchased by the authenticated user" - operationId: getPackPurchaseStatus - parameters: - - name: packId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" - /purchases/payments: - post: - tags: - - PurchasesApiV2 - summary: createPayment - description: POST /api/v2/purchases/payments\nCreate a payment\nCurrently supports YooKassa for web payments - operationId: createPayment - responses: - 200: - description: "Operation completed!" - /purchases/payments//verify: - get: - tags: - - PurchasesApiV2 - summary: verifyPayment - description: "GET /api/v2/purchases/payments/{paymentId}/verify\nVerify payment status\nUpdates user purchases on success" - operationId: verifyPayment - parameters: - - name: paymentId - in: path - required: true - schema: - type: string - responses: - 200: - description: "Operation completed!" /auth/oauth/google: post: tags: @@ -880,6 +537,56 @@ paths: responses: 200: description: "Operation completed!" + /telegram-bot/random-card: + get: + tags: + - TelegramBotApiV2 + summary: getRandomCard + description: "GET /api/v2/telegram-bot/random-card\nGet a random card from all available cards" + operationId: getRandomCard + responses: + 200: + description: "Operation completed!" + /telegram-bot/share/check-limit: + post: + tags: + - TelegramBotApiV2 + summary: checkShareLimit + description: "POST /api/v2/telegram-bot/share/check-limit\nCheck if user can share today (rate limiting)\nBody: { telegramUserId: string, dailyLimit: number }" + operationId: checkShareLimit + responses: + 200: + description: "Operation completed!" + /telegram-bot/share/record: + post: + tags: + - TelegramBotApiV2 + summary: recordShareRequest + description: "POST /api/v2/telegram-bot/share/record\nRecord a share request for a user\nBody: { telegramUserId: string, telegramUsername?: string, sharedCardId?: number }" + operationId: recordShareRequest + responses: + 200: + description: "Operation completed!" + /telegram-bot/users/info: + get: + tags: + - TelegramBotApiV2 + summary: getUsersInfo + description: "GET /api/v2/telegram-bot/users/info\nGet user information (for admin commands)\nQuery: ?userId= for specific user, or no query for all users summary" + operationId: getUsersInfo + responses: + 200: + description: "Operation completed!" + /telegram-bot/words: + get: + tags: + - TelegramBotApiV2 + summary: getWords + description: "GET /api/v2/telegram-bot/words\nGet all words from all cards\nQuery: ?separator= for custom separator (default: comma)" + operationId: getWords + responses: + 200: + description: "Operation completed!" /admin/analytics/dashboard: get: tags: @@ -969,7 +676,7 @@ paths: tags: - TasksApiV2 summary: "GET /api/v2/users/me/tasks/progress - Get user task progress" - operationId: getUserProgress + operationId: getUserTaskProgress responses: 200: description: "Operation completed!" @@ -977,94 +684,31 @@ paths: get: tags: - TasksApiV2 - summary: "GET /api/v2/tasks/categories - Get available task categories and filters" + summary: "GET /api/v2/tasks/categories - Get task categories" operationId: getTaskCategories responses: 200: description: "Operation completed!" - /telegram-bot/random-card: - get: - tags: - - TelegramBotApiV2 - summary: getRandomCard - description: "GET /api/v2/telegram-bot/random-card\nGet a random card from all available cards" - operationId: getRandomCard - responses: - 200: - description: "Operation completed!" - /telegram-bot/share/check-limit: - post: - tags: - - TelegramBotApiV2 - summary: checkShareLimit - description: "POST /api/v2/telegram-bot/share/check-limit\nCheck if user can share today (rate limiting)\nBody: { telegramUserId: string, dailyLimit: number }" - operationId: checkShareLimit - responses: - 200: - description: "Operation completed!" - /telegram-bot/share/record: - post: - tags: - - TelegramBotApiV2 - summary: recordShareRequest - description: "POST /api/v2/telegram-bot/share/record\nRecord a share request for a user\nBody: { telegramUserId: string, telegramUsername?: string, sharedCardId?: number }" - operationId: recordShareRequest - responses: - 200: - description: "Operation completed!" - /telegram-bot/users/info: - get: - tags: - - TelegramBotApiV2 - summary: getUsersInfo - description: "GET /api/v2/telegram-bot/users/info\nGet user information (for admin commands)\nQuery: ?userId= for specific user, or no query for all users summary" - operationId: getUsersInfo - responses: - 200: - description: "Operation completed!" - /telegram-bot/words: - get: - tags: - - TelegramBotApiV2 - summary: getWords - description: "GET /api/v2/telegram-bot/words\nGet all words from all cards\nQuery: ?separator= for custom separator (default: comma)" - operationId: getWords - responses: - 200: - description: "Operation completed!" components: { } tags: - name: PromocodesApiV2 description: API v2 endpoints for promocode management and activation. - name: DiscountsApiV2 description: Admin endpoints for discount campaign management. - - name: AdsApiV2 - description: API v2 endpoints for rewarded ads flows. - name: AdminCardsApiV2 - description: Admin endpoints for card management in API v2. - name: SubscriptionsApiV2 description: "Subscriptions API v2\nRESTful endpoints for managing subscriptions & plans" - name: UsersApiV2 description: "API v2 endpoints for user profile and self-service operations." - - name: AdminUsersApiV2 - description: Admin endpoints for user management in API v2. - - name: GamesApiV2 - description: Games API v2\n\nRESTful endpoints for managing games and game assets - - name: PacksApiV2 - description: API v2 Packs endpoints\n\nRESTful endpoints for card packs with pagination and filtering - - name: AdminPacksApiV2 - description: Admin endpoints for pack management in API v2. - name: AdminAuthApiV2 description: Admin authentication API endpoints - - name: PurchasesApiV2 - description: Purchases API v2\n\nRESTful endpoints for managing purchases and payments - name: AuthApiV2 description: API v2 Authentication endpoints\n\nImplements OAuth2/JWT Bearer token authentication - name: TestsApiV2 description: Tests API v2\n\nRESTful endpoints for managing tests and test results + - name: TelegramBotApiV2 + description: "API v2 endpoints for Telegram Bot\n\nThese endpoints are authenticated via X-API-Key header\nand provide functionality previously accessed directly via Isar DB" - name: AdminAnalyticsApiV2 description: Admin endpoints for analytics and statistics in API v2. - name: TasksApiV2 - description: API v2 endpoints for user tasks management - - name: TelegramBotApiV2 - description: "API v2 endpoints for Telegram Bot\n\nThese endpoints are authenticated via X-API-Key header\nand provide functionality previously accessed directly via Isar DB" \ No newline at end of file + description: API v2 endpoints for user tasks management \ No newline at end of file diff --git a/mnemo_cards_backend/pubspec.lock b/mnemo_cards_backend/pubspec.lock index cd1b110..c44abd5 100644 --- a/mnemo_cards_backend/pubspec.lock +++ b/mnemo_cards_backend/pubspec.lock @@ -25,6 +25,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.13.0" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + sha256: c1d5f167683de03d5ab6c3b53fc9aeefc5d59476e7810ba7bbddff50c6f4392d + url: "https://pub.dev" + source: hosted + version: "0.11.2" archive: dependency: "direct dev" description: @@ -81,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" + buffer: + dependency: transitive + description: + name: buffer + sha256: "389da2ec2c16283c8787e0adaede82b1842102f8c8aae2f49003a766c5c6b3d1" + url: "https://pub.dev" + source: hosted + version: "1.2.3" build: dependency: transitive description: @@ -145,14 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.9.2" - characters: + charcode: dependency: transitive description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" checked_yaml: dependency: transitive description: @@ -169,6 +185,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.0" + cli_util: + dependency: transitive + description: + name: cli_util + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c + url: "https://pub.dev" + source: hosted + version: "0.4.2" clock: dependency: transitive description: @@ -241,14 +265,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.2" - dartx: - dependency: transitive - description: - name: dartx - sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" - url: "https://pub.dev" - source: hosted - version: "1.2.0" dio: dependency: "direct main" description: @@ -265,6 +281,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + drift: + dependency: "direct main" + description: + name: drift + sha256: b50a8342c6ddf05be53bda1d246404cbad101b64dc73e8d6d1ac1090d119b4e2 + url: "https://pub.dev" + source: hosted + version: "2.15.0" + drift_dev: + dependency: "direct dev" + description: + name: drift_dev + sha256: c037d9431b6f8dc633652b1469e5f53aaec6e4eb405ed29dd232fa888ef10d88 + url: "https://pub.dev" + source: hosted + version: "2.15.0" + drift_postgres: + dependency: "direct main" + description: + name: drift_postgres + sha256: "8c1e123c72aeb3982af53681d4c9327d2a54a980f3bc8658cd891429b0dd3b09" + url: "https://pub.dev" + source: hosted + version: "1.3.1" encrypt: dependency: "direct main" description: @@ -426,21 +466,13 @@ packages: source: hosted version: "1.0.4" isar: - dependency: "direct main" + dependency: transitive description: name: isar sha256: "99165dadb2cf2329d3140198363a7e7bff9bbd441871898a87e26914d25cf1ea" url: "https://pub.dev" source: hosted version: "3.1.0+1" - isar_generator: - dependency: "direct dev" - description: - name: isar_generator - sha256: "76c121e1295a30423604f2f819bc255bc79f852f3bc8743a24017df6068ad133" - url: "https://pub.dev" - source: hosted - version: "3.1.0+1" jaguar_jwt: dependency: "direct main" description: @@ -591,6 +623,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + postgres: + dependency: "direct main" + description: + name: postgres + sha256: "83ba7afb0e778cc1f373e514c6110d323b81dfbaced774d3a7f4a0e21536eb45" + url: "https://pub.dev" + source: hosted + version: "3.4.8" pub_semver: dependency: transitive description: @@ -631,6 +671,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + sasl_scram: + dependency: transitive + description: + name: sasl_scram + sha256: a47207a436eb650f8fdcf54a2e2587b850dc3caef9973ce01f332b07a6fc9cb9 + url: "https://pub.dev" + source: hosted + version: "0.1.1" + saslprep: + dependency: transitive + description: + name: saslprep + sha256: "3d421d10be9513bf4459c17c5e70e7b8bc718c9fc5ad4ba5eb4f5fd27396f740" + url: "https://pub.dev" + source: hosted + version: "1.0.3" shelf: dependency: "direct main" description: @@ -767,6 +823,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sqlite3: + dependency: transitive + description: + name: sqlite3 + sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2" + url: "https://pub.dev" + source: hosted + version: "2.9.4" + sqlparser: + dependency: transitive + description: + name: sqlparser + sha256: "7b20045d1ccfb7bc1df7e8f9fee5ae58673fce6ff62cefbb0e0fd7214e90e5a0" + url: "https://pub.dev" + source: hosted + version: "0.34.1" stack_trace: dependency: transitive description: @@ -831,14 +903,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.5" - time: - dependency: transitive - description: - name: time - sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221 - url: "https://pub.dev" - source: hosted - version: "2.1.4" timing: dependency: transitive description: @@ -855,14 +919,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + unorm_dart: + dependency: transitive + description: + name: unorm_dart + sha256: "0c69186b03ca6addab0774bcc0f4f17b88d4ce78d9d4d8f0619e30a99ead58e7" + url: "https://pub.dev" + source: hosted + version: "0.3.2" uuid: dependency: "direct main" description: name: uuid - sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "4.5.2" version: dependency: transitive description: @@ -927,14 +999,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.5.0" - xxh3: - dependency: transitive - description: - name: xxh3 - sha256: a92b30944a9aeb4e3d4f3c3d4ddb3c7816ca73475cd603682c4f8149690f56d7 - url: "https://pub.dev" - source: hosted - version: "1.0.1" yaml: dependency: transitive description: @@ -947,9 +1011,9 @@ packages: dependency: "direct main" description: name: yookassa_client - sha256: "667d04d0e2d8c7e5180d26a3966587cc99d38fb887d318e36562a202d1adb6e2" + sha256: e801e1bb22f21f883adbee15645e2c9b21c4a640f8e096006a6295c335c588aa url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.0.5" sdks: - dart: ">=3.4.0 <4.0.0" + dart: ">=3.5.0 <4.0.0" diff --git a/mnemo_cards_backend/pubspec.yaml b/mnemo_cards_backend/pubspec.yaml index 031e9cd..7e7edaa 100644 --- a/mnemo_cards_backend/pubspec.yaml +++ b/mnemo_cards_backend/pubspec.yaml @@ -4,8 +4,7 @@ description: Mnemo backend # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.0+4 -isar_version: &isar_version 3.1.0+1 # define the version to be used +version: 1.0.0+5 environment: sdk: '>=3.0.0 <4.0.0' @@ -19,12 +18,16 @@ dependencies: mnemo_cards_common_backend: path: ../mnemo_cards_common_backend + # PostgreSQL + Drift + drift: ^2.14.0 + drift_postgres: ^1.1.0 + postgres: ^3.0.4 + image: ^4.1.7 http: ^1.1.0 # shared_preferences: ^2.2.2 async: - isar: *isar_version # path_provider: ^2.1.1 get_it: ^7.6.4 injectable: ^2.4.1 @@ -46,7 +49,7 @@ dependencies: crypto: ^3.0.3 googleapis: ^13.1.0 googleapis_auth: - uuid: ^3.0.7 + uuid: ^4.5.2 yookassa_client: ^1.0.2 neat_periodic_task: ^2.0.1 @@ -54,8 +57,11 @@ dependencies: dev_dependencies: - build_runner: - isar_generator: *isar_version + build_runner: ^2.4.0 + + # Drift code generation + drift_dev: ^2.14.0 + shelf_router_generator: ^1.1.0 shelf_open_api_generator: injectable_generator: