import { insertActivityLog, listAdminUserIds, listUserAutoDeletePolicies, type AutoDeletePolicy } from './db' import { getGlobalAutoDeletePolicy } from './env' import { deleteImage, type StoredImage } from './storage' import { listImageKeysForAutoDelete } from './image-index' import { logInfo, logWarn, serializeError } from './logger' export interface AutoDeleteResult { deleted: number failed: number skipped: boolean days: number } const MS_PER_DAY = 24 * 60 * 60 * 1000 function imageAgeDays(uploadedMs: number, nowMs: number): number { return (nowMs - uploadedMs) / MS_PER_DAY } function policyMatches( uploadedMs: number, nowMs: number, policy: AutoDeletePolicy ): boolean { if (policy.days <= 0) return false if (policy.enabledAt) { const enabledMs = new Date(policy.enabledAt).getTime() if (!Number.isFinite(enabledMs) || uploadedMs < enabledMs) { return false } } return imageAgeDays(uploadedMs, nowMs) >= policy.days } export function shouldAutoDeleteImage( image: StoredImage, globalPolicy: AutoDeletePolicy, adminUserIds: Set, userPolicies: Map, nowMs = Date.now() ): boolean { const uploadedMs = new Date(image.uploadedAt).getTime() if (!Number.isFinite(uploadedMs) || uploadedMs > nowMs) { return false } const ownerId = image.userId ?? null if (ownerId != null) { const userPolicy = userPolicies.get(ownerId) if (userPolicy && policyMatches(uploadedMs, nowMs, userPolicy)) { return true } if (adminUserIds.has(ownerId) && policyMatches(uploadedMs, nowMs, globalPolicy)) { return true } return false } return policyMatches(uploadedMs, nowMs, globalPolicy) } export async function runAutoDeleteCleanup(): Promise { const globalPolicy = getGlobalAutoDeletePolicy() const userPolicies = listUserAutoDeletePolicies() const adminUserIds = listAdminUserIds() if (globalPolicy.days <= 0 && userPolicies.size === 0) { return { deleted: 0, failed: 0, skipped: true, days: 0 } } const nowMs = Date.now() const images = await listImageKeysForAutoDelete() let deleted = 0 let failed = 0 for (const image of images) { if (!shouldAutoDeleteImage(image, globalPolicy, adminUserIds, userPolicies, nowMs)) { continue } try { await deleteImage(image.key) insertActivityLog({ action: 'delete', key: image.key, originalName: image.originalName, size: image.size, contentType: image.contentType, source: 'web', userId: image.userId ?? null, backendId: image.backendId ?? null }) deleted++ } catch (error) { logWarn('auto-delete failed', { key: image.key, error: serializeError(error) }) failed++ } } if (deleted > 0 || failed > 0) { logInfo('auto-delete cleanup finished', { globalDays: globalPolicy.days, deleted, failed }) } return { deleted, failed, skipped: false, days: globalPolicy.days } }