From acc12363be08c3c1c32f9c2146f6c8e59d95af25 Mon Sep 17 00:00:00 2001 From: Jeff-Emmett Date: Tue, 28 Jan 2025 16:42:58 +0100 Subject: [PATCH] board backups to R2 --- worker/types.ts | 1 + worker/worker.ts | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ wrangler.toml | 11 +++++++++- 3 files changed, 67 insertions(+), 1 deletion(-) diff --git a/worker/types.ts b/worker/types.ts index 3a756ae..15313f1 100644 --- a/worker/types.ts +++ b/worker/types.ts @@ -4,6 +4,7 @@ export interface Environment { TLDRAW_BUCKET: R2Bucket + BOARD_BACKUPS_BUCKET: R2Bucket TLDRAW_DURABLE_OBJECT: DurableObjectNamespace DAILY_API_KEY: string; DAILY_DOMAIN: string; diff --git a/worker/worker.ts b/worker/worker.ts index 32606a5..ee35cc3 100644 --- a/worker/worker.ts +++ b/worker/worker.ts @@ -180,5 +180,61 @@ const router = AutoRouter({ } }) +async function backupAllBoards(env: Environment) { + try { + // List all room files from TLDRAW_BUCKET + const roomsList = await env.TLDRAW_BUCKET.list({ prefix: 'rooms/' }) + + const date = new Date().toISOString().split('T')[0] + + // Process each room + for (const room of roomsList.objects) { + try { + // Get the room data + const roomData = await env.TLDRAW_BUCKET.get(room.key) + if (!roomData) continue + + // Get the data as text since it's already stringified JSON + const jsonData = await roomData.text() + + // Create backup key with date only + const backupKey = `${date}/${room.key}` + + // Store in backup bucket as JSON + await env.BOARD_BACKUPS_BUCKET.put(backupKey, jsonData) + + console.log(`Backed up ${room.key} to ${backupKey}`) + } catch (error) { + console.error(`Failed to backup room ${room.key}:`, error) + } + } + + // Clean up old backups (keep last 30 days) + const thirtyDaysAgo = new Date() + thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30) + + const oldBackups = await env.BOARD_BACKUPS_BUCKET.list({ + prefix: thirtyDaysAgo.toISOString().split('T')[0] + }) + + for (const backup of oldBackups.objects) { + await env.BOARD_BACKUPS_BUCKET.delete(backup.key) + } + + return { success: true, message: 'Backup completed successfully' } + } catch (error) { + console.error('Backup failed:', error) + return { success: false, message: (error as Error).message } + } +} + +router + .get("/backup", async (_, env) => { + const result = await backupAllBoards(env) + return new Response(JSON.stringify(result), { + headers: { 'Content-Type': 'application/json' } + }) + }) + // export our router for cloudflare export default router diff --git a/wrangler.toml b/wrangler.toml index 840cbbd..fc7f66f 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -27,6 +27,15 @@ binding = 'TLDRAW_BUCKET' bucket_name = 'jeffemmett-canvas' preview_bucket_name = 'jeffemmett-canvas-preview' +[[r2_buckets]] +binding = 'BOARD_BACKUPS_BUCKET' +bucket_name = 'board-backups' +preview_bucket_name = 'board-backups-preview' + [observability] enabled = true -head_sampling_rate = 1 \ No newline at end of file +head_sampling_rate = 1 + +[triggers] +crons = ["0 0 * * *"] # Run at midnight UTC every day +# crons = ["*/10 * * * *"] # Run every 10 minutes \ No newline at end of file