/** Deploy separately: this bucket must not be the application's media bucket. */
interface Env {
    ARCHIVE: R2Bucket;
    BACKUP_SERVICE_TOKEN: string;
    APP_OPERATIONS_TOKEN: string;
    APP_URL: string;
    RETENTION_DAYS?: string;
    SITES_ACCESS_TOKEN?: string;
}
export default {
    async fetch(req: Request, env: Env) {
        if (req.headers.get('authorization') !== `Bearer ${env.BACKUP_SERVICE_TOKEN}`)
            return new Response('Unauthorized', { status: 401 });
        const path = new URL(req.url).pathname;
        const key = path.replace(/^\/archive\//, '');
        if (!/^[-a-zA-Z0-9/]+$/.test(key) || !path.startsWith('/archive/'))
            return new Response('Invalid key', { status: 400 });
        if (req.method === 'PUT') {
            await env.ARCHIVE.put(key, req.body, { httpMetadata: { contentType: 'application/octet-stream' }, customMetadata: { createdAt: new Date().toISOString(), expiresAt: req.headers.get('x-backup-expires') || new Date(Date.now() + 30 * 86400000).toISOString() } });
            return new Response('Saved');
        }
        if (req.method === 'GET') {
            const obj = await env.ARCHIVE.get(key);
            return obj ? new Response(obj.body, { headers: { 'Cache-Control': 'no-store', 'Content-Type': 'application/octet-stream' } }) : new Response('Not found', { status: 404 });
        }
        return new Response('Method not allowed', { status: 405 });
    },
    async scheduled(_controller: ScheduledController, env: Env, ctx: ExecutionContext) { ctx.waitUntil((async () => { const r = await fetch(`${env.APP_URL}/api/ops/backup`, { method: 'POST', headers: { Authorization: `Bearer ${env.APP_OPERATIONS_TOKEN}`, ...(env.SITES_ACCESS_TOKEN ? { 'OAI-Sites-Authorization': `Bearer ${env.SITES_ACCESS_TOKEN}` } : {}) } }); if (!r.ok)
        throw new Error('Backup failed; review the application dashboard.'); const cutoff = Date.now() - Number(env.RETENTION_DAYS || 30) * 86400000; let cursor: string | undefined; do {
        const list = await env.ARCHIVE.list({ cursor });
        for (const obj of list.objects) {
            const meta = await env.ARCHIVE.head(obj.key);
            if (new Date(meta?.customMetadata?.expiresAt || '9999-01-01').getTime() < Date.now())
                await env.ARCHIVE.delete(obj.key);
        }
        cursor = list.truncated ? list.cursor : undefined;
    } while (cursor); })()); }
};
