/** * @ai-summary Service for classifying backups into tiered retention categories * @ai-context Pure functions for timestamp-based classification, no database dependencies */ import { BackupCategory, TIERED_RETENTION } from './backup.types'; /** * Classifies a backup by its timestamp into retention categories. * A backup can belong to multiple categories simultaneously. * * Categories: * - hourly: All backups * - daily: First backup at midnight UTC (hour = 0) * - weekly: First backup on Sunday at midnight UTC * - monthly: First backup on 1st of month at midnight UTC */ export function classifyBackup(timestamp: Date): BackupCategory[] { const categories: BackupCategory[] = ['hourly']; const utcHour = timestamp.getUTCHours(); const utcDay = timestamp.getUTCDate(); const utcDayOfWeek = timestamp.getUTCDay(); // 0 = Sunday // Midnight UTC qualifies for daily if (utcHour === 0) { categories.push('daily'); // Sunday at midnight qualifies for weekly if (utcDayOfWeek === 0) { categories.push('weekly'); } // 1st of month at midnight qualifies for monthly if (utcDay === 1) { categories.push('monthly'); } } return categories; } /** * Calculates the expiration date based on the backup's categories. * Uses the longest retention period among all applicable categories. * * Retention periods are count-based in the actual cleanup, but for display * we estimate based on typical backup frequency: * - hourly: 8 hours (8 backups * 1 hour) * - daily: 7 days (7 backups * 1 day) * - weekly: 4 weeks (4 backups * 1 week) * - monthly: 12 months (12 backups * 1 month) */ export function calculateExpiration( categories: BackupCategory[], timestamp: Date ): Date { const expirationDate = new Date(timestamp); if (categories.includes('monthly')) { expirationDate.setUTCMonth(expirationDate.getUTCMonth() + TIERED_RETENTION.monthly); } else if (categories.includes('weekly')) { expirationDate.setUTCDate(expirationDate.getUTCDate() + TIERED_RETENTION.weekly * 7); } else if (categories.includes('daily')) { expirationDate.setUTCDate(expirationDate.getUTCDate() + TIERED_RETENTION.daily); } else { // Hourly only - 8 hours expirationDate.setUTCHours(expirationDate.getUTCHours() + TIERED_RETENTION.hourly); } return expirationDate; } /** * Checks if a backup timestamp represents the first backup of the day (midnight UTC). */ export function isFirstBackupOfDay(timestamp: Date): boolean { return timestamp.getUTCHours() === 0; } /** * Checks if a timestamp falls on a Sunday. */ export function isSunday(timestamp: Date): boolean { return timestamp.getUTCDay() === 0; } /** * Checks if a timestamp falls on the first day of the month. */ export function isFirstDayOfMonth(timestamp: Date): boolean { return timestamp.getUTCDate() === 1; } /** * Classifies a backup and calculates its expiration in one call. * Convenience function for backup creation flow. */ export function classifyAndCalculateExpiration(timestamp: Date): { categories: BackupCategory[]; expiresAt: Date; } { const categories = classifyBackup(timestamp); const expiresAt = calculateExpiration(categories, timestamp); return { categories, expiresAt }; }