mnemo_cards/mnemo_cards_backend/lib/cron/generate_promocodes.dart

120 lines
4 KiB
Dart
Raw Normal View History

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