const { parseDateTimeValue, formatDateTimeForSqlite, getNextDownloadResetTime } = require('./datetime'); const MAX_DOWNLOAD_TRAFFIC_BYTES = 10 * 1024 * 1024 * 1024 * 1024; function normalizeDownloadTrafficQuota(rawQuota) { const parsedQuota = Number(rawQuota); if (!Number.isFinite(parsedQuota)) return 0; if (parsedQuota < 0) return -1; return Math.min(MAX_DOWNLOAD_TRAFFIC_BYTES, Math.floor(parsedQuota)); } function normalizeDownloadTrafficUsed(rawUsed, quota = 0) { const parsedUsed = Number(rawUsed); const normalizedUsed = Number.isFinite(parsedUsed) && parsedUsed > 0 ? Math.floor(parsedUsed) : 0; return quota >= 0 ? Math.min(normalizedUsed, quota) : normalizedUsed; } function getDownloadTrafficState(user) { const quota = normalizeDownloadTrafficQuota(user?.download_traffic_quota); const used = normalizeDownloadTrafficUsed(user?.download_traffic_used, quota); const isUnlimited = quota < 0; return { quota, used, isUnlimited, remaining: isUnlimited ? Number.POSITIVE_INFINITY : Math.max(0, quota - used) }; } function resolveDownloadTrafficPolicyUpdates(user, now = new Date()) { if (!user) { return { updates: {}, hasUpdates: false, expired: false, resetApplied: false }; } const updates = {}; let hasUpdates = false; let expired = false; let resetApplied = false; const normalizedQuota = normalizeDownloadTrafficQuota(user.download_traffic_quota); const normalizedUsed = normalizeDownloadTrafficUsed(user.download_traffic_used, normalizedQuota); if (normalizedQuota !== Number(user.download_traffic_quota || 0)) { updates.download_traffic_quota = normalizedQuota; hasUpdates = true; } if (normalizedUsed !== Number(user.download_traffic_used || 0)) { updates.download_traffic_used = normalizedUsed; hasUpdates = true; } const resetCycle = ['none', 'daily', 'weekly', 'monthly'].includes(user.download_traffic_reset_cycle) ? user.download_traffic_reset_cycle : 'none'; if (resetCycle !== (user.download_traffic_reset_cycle || 'none')) { updates.download_traffic_reset_cycle = resetCycle; hasUpdates = true; } const expiresAt = parseDateTimeValue(user.download_traffic_quota_expires_at); if (normalizedQuota <= 0 && user.download_traffic_quota_expires_at) { updates.download_traffic_quota_expires_at = null; hasUpdates = true; } else if (normalizedQuota > 0 && expiresAt && now >= expiresAt) { updates.download_traffic_quota = -1; updates.download_traffic_used = 0; updates.download_traffic_quota_expires_at = null; updates.download_traffic_reset_cycle = 'none'; updates.download_traffic_last_reset_at = null; hasUpdates = true; expired = true; } if (!expired && resetCycle !== 'none') { const lastResetAt = user.download_traffic_last_reset_at; if (!lastResetAt) { updates.download_traffic_last_reset_at = formatDateTimeForSqlite(now); hasUpdates = true; } else { const nextResetAt = getNextDownloadResetTime(lastResetAt, resetCycle); if (nextResetAt && now >= nextResetAt) { updates.download_traffic_used = 0; updates.download_traffic_last_reset_at = formatDateTimeForSqlite(now); hasUpdates = true; resetApplied = true; } } } else if (resetCycle === 'none' && user.download_traffic_last_reset_at) { updates.download_traffic_last_reset_at = null; hasUpdates = true; } return { updates, hasUpdates, expired, resetApplied }; } module.exports = { MAX_DOWNLOAD_TRAFFIC_BYTES, normalizeDownloadTrafficQuota, normalizeDownloadTrafficUsed, getDownloadTrafficState, resolveDownloadTrafficPolicyUpdates };