mnemo_cards/mnemo_cards_backend/lib/cron/generate_promocodes.dart
Dmitry 0f49280805
Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Mobile App CI / test (push) Waiting to run
Mobile App CI / build-android (push) Blocked by required conditions
Mobile App CI / build-ios (push) Blocked by required conditions
Web App CI / test (push) Waiting to run
Web App CI / build (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions
Deploy Telegram Bot / Deploy Telegram Bot (push) Waiting to run
fixes and format
2025-12-20 21:26:15 +03:00

125 lines
4 KiB
Dart

import 'dart:developer';
import 'dart:math' as math;
import 'package:drift/drift.dart' as drift;
import 'package:mnemo_cards_backend/cron/task.dart' as task;
import 'package:mnemo_cards_backend/database/database.dart';
import 'package:mnemo_cards_backend/database/database.dart' as drift;
class GeneratePromocodes with task.Task {
final AppDatabase _db;
final random = math.Random.secure();
GeneratePromocodes(this._db);
@override
String get name => 'generate_promocodes';
@override
Future<void> task() async {
final campaigns = await _db.promoCodeDao.getCampaignsByStatuses([
'created',
'preparing',
]);
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(PromoCodesCampaign campaign) async {
print('Generating promo codes for campaign ${campaign.id}');
try {
if (campaign.status != 'preparing') {
await _db.promoCodeDao.updateCampaignStatus(campaign.id, 'preparing');
}
final PAGE_INITIAL = 50;
int page = PAGE_INITIAL;
while (true) {
// Получаем текущее количество промокодов
final existingCodes = await _db.promoCodeDao.getPromoCodesByCampaignId(
campaign.id,
);
final currentCount = existingCodes.length;
final leftToGenerate = campaign.generationSize - currentCount;
if (leftToGenerate <= 0) {
break;
}
final willGenerate = math.min(leftToGenerate, page);
try {
await _db.transaction(() async {
final promoCodes = <drift.PromoCodesCompanion>[];
for (int i = 0; i < willGenerate; i++) {
promoCodes.add(
drift.PromoCodesCompanion.insert(
campaignId: campaign.id,
code: _generateCode(campaign.template),
activations: const drift.Value(0),
),
);
}
await _db.promoCodeDao.createPromoCodes(promoCodes);
});
if (page < PAGE_INITIAL) {
page *= 2;
}
} catch (e) {
// Обработка уникальных нарушений (duplicate key)
final errorMessage = e.toString().toLowerCase();
if (page > 1 &&
(errorMessage.contains('unique') ||
errorMessage.contains('duplicate') ||
errorMessage.contains('violates unique constraint'))) {
page ~/= 2;
print(
'UniqueViolationError when generating codes for ${campaign.id}, retrying with page: $page',
);
continue;
} else {
rethrow;
}
}
}
// Проверяем, достигли ли мы нужного количества
final finalCodes = await _db.promoCodeDao.getPromoCodesByCampaignId(
campaign.id,
);
if (finalCodes.length >= campaign.generationSize) {
await _db.promoCodeDao.updateCampaignStatus(campaign.id, 'ready');
}
print('Finished generating promo codes for campaign ${campaign.id}');
} catch (e, s) {
print('Error when generating promo codes for ${campaign.id} $e $s');
log(
'Error when generating promo codes for ${campaign.id}',
error: e,
stackTrace: s,
);
}
}
@override
int get intervalSeconds => 10 * 60;
}