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

122 lines
3.9 KiB
Dart

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<void> 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<void> _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 = <PromoCodeModel>[];
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;
}