board backups to R2
This commit is contained in:
parent
c2abfcd3e3
commit
acc12363be
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
export interface Environment {
|
export interface Environment {
|
||||||
TLDRAW_BUCKET: R2Bucket
|
TLDRAW_BUCKET: R2Bucket
|
||||||
|
BOARD_BACKUPS_BUCKET: R2Bucket
|
||||||
TLDRAW_DURABLE_OBJECT: DurableObjectNamespace
|
TLDRAW_DURABLE_OBJECT: DurableObjectNamespace
|
||||||
DAILY_API_KEY: string;
|
DAILY_API_KEY: string;
|
||||||
DAILY_DOMAIN: string;
|
DAILY_DOMAIN: string;
|
||||||
|
|
|
||||||
|
|
@ -180,5 +180,61 @@ const router = AutoRouter<IRequest, [env: Environment, ctx: ExecutionContext]>({
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
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 our router for cloudflare
|
||||||
export default router
|
export default router
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,15 @@ binding = 'TLDRAW_BUCKET'
|
||||||
bucket_name = 'jeffemmett-canvas'
|
bucket_name = 'jeffemmett-canvas'
|
||||||
preview_bucket_name = 'jeffemmett-canvas-preview'
|
preview_bucket_name = 'jeffemmett-canvas-preview'
|
||||||
|
|
||||||
|
[[r2_buckets]]
|
||||||
|
binding = 'BOARD_BACKUPS_BUCKET'
|
||||||
|
bucket_name = 'board-backups'
|
||||||
|
preview_bucket_name = 'board-backups-preview'
|
||||||
|
|
||||||
[observability]
|
[observability]
|
||||||
enabled = true
|
enabled = true
|
||||||
head_sampling_rate = 1
|
head_sampling_rate = 1
|
||||||
|
|
||||||
|
[triggers]
|
||||||
|
crons = ["0 0 * * *"] # Run at midnight UTC every day
|
||||||
|
# crons = ["*/10 * * * *"] # Run every 10 minutes
|
||||||
Loading…
Reference in New Issue