Fare and route-coach, production checklist updates

This commit is contained in:
Stephanos A
2026-07-02 22:42:15 +03:00
parent 4aadf588d4
commit 200476dfd6
37 changed files with 1672 additions and 248 deletions

View File

@@ -2,12 +2,20 @@ import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { PrismaService } from '../../common/prisma.service';
import { SmsClientService } from '../notifications/sms-client.service';
import { CurrencyService } from '../currency/currency.service';
/** Maximum time (hours) a passenger has to pay after booking. */
const MAX_PAYMENT_HOURS = 2;
/** Minutes before departure: cutoff for new bookings and payment deadline. */
const CUTOFF_MINUTES = 30;
// Retention windows
const OTP_RETENTION_HOURS = 1;
const FAYDA_SESSION_RETENTION_HOURS = 1;
const AUDIT_LOG_RETENTION_DAYS = 365;
const WEBHOOK_EVENT_RETENTION_DAYS = 90;
const GATE_LOG_RETENTION_DAYS = 180;
/**
* payment_deadline = MIN(booking_time + 2h, departure_time - 30min)
*/
@@ -32,6 +40,7 @@ export class TasksService {
constructor(
private readonly prisma: PrismaService,
private readonly sms: SmsClientService,
private readonly currencyService: CurrencyService,
) {}
// ─────────────────────────────────────────────────────────────────────────
@@ -243,4 +252,47 @@ export class TasksService {
this.logger.log(`Auto-cancelled ${cancelledCount} expired pending booking(s)`);
}
}
// ─────────────────────────────────────────────────────────────────────────
// Daily at 01:00 EAT: fetch mid-market rates from central bank API.
// ─────────────────────────────────────────────────────────────────────────
@Cron('0 1 * * *', { timeZone: 'Africa/Addis_Ababa' })
async syncExchangeRates() {
try {
await this.currencyService.syncExchangeRates();
} catch (err) {
this.logger.error(`Exchange rate sync failed: ${(err as Error).message}`);
}
}
// ─────────────────────────────────────────────────────────────────────────
// Daily at 02:00 EAT: purge expired/stale records to enforce data retention.
// ─────────────────────────────────────────────────────────────────────────
@Cron('0 2 * * *')
async purgeExpiredData() {
const now = new Date();
const otpCutoff = new Date(now.getTime() - OTP_RETENTION_HOURS * 60 * 60 * 1000);
const faydaCutoff = new Date(now.getTime() - FAYDA_SESSION_RETENTION_HOURS * 60 * 60 * 1000);
const auditCutoff = new Date(now.getTime() - AUDIT_LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000);
const webhookCutoff = new Date(now.getTime() - WEBHOOK_EVENT_RETENTION_DAYS * 24 * 60 * 60 * 1000);
const gateCutoff = new Date(now.getTime() - GATE_LOG_RETENTION_DAYS * 24 * 60 * 60 * 1000);
const [otps, faydaSessions, auditLogs, webhookEvents, gateLogs] = await Promise.all([
this.prisma.otpCode.deleteMany({
where: { OR: [{ expiresAt: { lte: otpCutoff } }, { verified: true, createdAt: { lte: otpCutoff } }] },
}),
this.prisma.faydaVerificationSession.deleteMany({
where: { OR: [{ expiresAt: { lte: faydaCutoff } }, { status: { in: ['COMPLETED', 'FAILED'] }, createdAt: { lte: faydaCutoff } }] },
}),
this.prisma.auditLog.deleteMany({ where: { createdAt: { lte: auditCutoff } } }),
this.prisma.paymentWebhookEvent.deleteMany({ where: { receivedAt: { lte: webhookCutoff } } }),
this.prisma.gateValidationLog.deleteMany({ where: { validatedAt: { lte: gateCutoff } } }),
]);
this.logger.log(
`Data retention purge: ${otps.count} OTPs, ${faydaSessions.count} Fayda sessions, ` +
`${auditLogs.count} audit logs, ${webhookEvents.count} webhook events, ${gateLogs.count} gate logs deleted`,
);
}
}