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;
|
|
|
|
|
}
|