import 'dart:developer'; import 'dart:math' as math; import 'package:isar/isar.dart'; import 'package:mnemo_cards_backend/cron/task.dart'; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; import '../main.dart'; class GeneratePromocodes with Task { final random = math.Random.secure(); @override String get name => 'generate_promocodes'; @override Future task() async { final campaigns = await isar.txn(() async { return await isar.promoCodesCampaignModels .filter() .statusEqualTo(PromoCodeCampaignModelStatus.created) .or() .statusEqualTo(PromoCodeCampaignModelStatus.preparing) .findAll(); }); await Future.wait([ for (final campaign in campaigns) _generateCampaignPromoCodes(campaign), ]); } static final _letters = 'CDFGJLNQRSUVWXYZ'; static final _digits = '123456789'; static final _symbols = _letters + _digits; String get randomSymbol => _symbols[random.nextInt(_symbols.length)]; String get randomLetter => _letters[random.nextInt(_letters.length)]; String get randomDigit => _digits[random.nextInt(_digits.length)]; String _generateCode(String template) => template .replaceAllMapped(r'$d', (_) => randomDigit) .replaceAllMapped(r'$l', (_) => randomLetter) .replaceAllMapped(r'$s', (_) => randomSymbol); Future _generateCampaignPromoCodes( PromoCodesCampaignModel campaign, ) async { print('Generating promo codes for campaign ${campaign.id}'); try { if (campaign.status != PromoCodeCampaignModelStatus.preparing) { await isar.writeTxn(() async { isar.promoCodesCampaignModels.put(campaign.copyWith( status: PromoCodeCampaignModelStatus.preparing)); }); } final PAGE_INITIAL = 50; int page = PAGE_INITIAL; int leftToGenerate = campaign.generationSize - campaign.promoCodes.length; while (leftToGenerate > 0) { final willGenerate = math.min(leftToGenerate, page); try { await isar.writeTxn(() async { final promoCodes = []; for (int i = 0; i < willGenerate; i++) { promoCodes.add( PromoCodeModel( code: _generateCode(campaign.template), activations: 0, )..campaign.value = campaign, ); } await isar.promoCodeModels.putAll(promoCodes); await campaign.promoCodes.load(); campaign.promoCodes.addAll(promoCodes); await campaign.promoCodes.save(); }); if (page < PAGE_INITIAL) { page *= 2; } } on Object catch (e) { if (e is IsarError && page > 1 && e.message.contains('Unique index violated')) { page ~/= 2; print( 'UniqueViolationError when generating codes for ${campaign.id}, retrying with page: $page', ); continue; } else { rethrow; } } leftToGenerate = campaign.generationSize - campaign.promoCodes.length; } final loadedCampaign = await isar.promoCodesCampaignModels.get(campaign.id!); if (loadedCampaign == null) { log('Campaign not found in db: ${campaign.id}'); return; } if (loadedCampaign.promoCodes.length >= campaign.generationSize) { await isar.writeTxn( () async { isar.promoCodesCampaignModels.put( loadedCampaign.copyWith( status: PromoCodeCampaignModelStatus.ready, ), ); }, ); } print('Finished generating promo codes for campaign ${campaign.id}'); } catch (e, s) { print('Error when generating promo codes for ${campaign.id} $e $s'); } } @override int get intervalSeconds => 10 * 60; }