mirror of
https://github.com/saltbo/zpan.git
synced 2026-09-19 01:51:11 +08:00
* fix(analytics): enforce trustworthy offline statistics Separate immutable counters from point-in-time snapshots, expose incomplete coverage instead of synthetic zeroes, and keep browser analytics result-only. Restore finite Free quota baselines, fail closed for invalid storage quota, reconcile traffic reports fairly, and add production-safe backfill and data-quality diagnostics. * fix(analytics): preserve global backfill totals Group generated hourly backfill rows by their projected values so SQLite cannot resolve output aliases to source organization columns and overwrite cross-organization totals.
73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
// CF Workers scheduled() handler.
|
|
|
|
import { createQuotaRepo } from '../server/adapters/repos/quota'
|
|
import { createDeps } from '../server/composition'
|
|
import { createCloudflarePlatform } from '../server/platform/cloudflare'
|
|
import { syncPendingRemoteDownloadUsageReports } from '../server/usecases/downloads/remote-download-usage'
|
|
import { purgeExpiredTrash, resolveTrashRetentionDays } from '../server/usecases/object'
|
|
import { INSTANCE_TELEMETRY_CRON, reportInstanceTelemetry } from '../server/usecases/site/instance-telemetry'
|
|
import { runLicensingRefresh } from '../server/usecases/site/licensing'
|
|
import { syncPendingCloudTrafficReports } from '../server/usecases/store/traffic-metering'
|
|
import { ZPAN_CLOUD_URL_DEFAULT } from '../shared/constants'
|
|
|
|
// Subset of the worker Env used by the scheduled handler.
|
|
// The full Env is defined in bootstrap.ts; this avoids circular imports.
|
|
export interface ScheduledEnv {
|
|
DB: D1Database
|
|
ZPAN_CLOUD_URL?: string
|
|
ZPAN_TELEMETRY_ALLOW_IP?: string
|
|
ZPAN_TRASH_RETENTION_DAYS?: string
|
|
[key: string]: unknown
|
|
}
|
|
|
|
const TRAFFIC_SYNC_CRON = '*/10 * * * *'
|
|
const QUOTA_RESET_CRON = '0 0 1 * *'
|
|
const TRASH_PURGE_CRON = '0 4 * * *'
|
|
type ScheduledTrigger = Pick<ScheduledEvent, 'cron'>
|
|
|
|
function envAllowsIp(value: string | undefined): boolean {
|
|
return !['0', 'false', 'no', 'off'].includes(value?.trim().toLowerCase() ?? '')
|
|
}
|
|
|
|
export async function handleScheduled(event: ScheduledTrigger, env: ScheduledEnv): Promise<void> {
|
|
const platform = createCloudflarePlatform(env)
|
|
const deps = createDeps(platform)
|
|
const cloudBaseUrl = env.ZPAN_CLOUD_URL ?? ZPAN_CLOUD_URL_DEFAULT
|
|
if (event.cron === TRAFFIC_SYNC_CRON) {
|
|
await deps.quota.reconcileFreePlanBaselines()
|
|
await Promise.all([
|
|
syncPendingCloudTrafficReports(deps, { cloudBaseUrl }),
|
|
syncPendingRemoteDownloadUsageReports(deps, { cloudBaseUrl }),
|
|
deps.adminStats.refreshHourlyRollups(new Date()),
|
|
])
|
|
return
|
|
}
|
|
|
|
if (event.cron === QUOTA_RESET_CRON) {
|
|
await createQuotaRepo(platform.db).resetExpiredTrafficQuotas()
|
|
return
|
|
}
|
|
|
|
if (event.cron === TRASH_PURGE_CRON) {
|
|
await purgeExpiredTrash(deps, resolveTrashRetentionDays(env.ZPAN_TRASH_RETENTION_DAYS))
|
|
return
|
|
}
|
|
|
|
if (event.cron === INSTANCE_TELEMETRY_CRON) {
|
|
await reportInstanceTelemetry(deps, {
|
|
config: {
|
|
allowIp: envAllowsIp(env.ZPAN_TELEMETRY_ALLOW_IP),
|
|
},
|
|
cron: event.cron,
|
|
trigger: 'scheduled',
|
|
runtime: {
|
|
runtime: 'workerd',
|
|
platform: 'cloudflare-workers',
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
await runLicensingRefresh(deps, cloudBaseUrl)
|
|
}
|