From 948fb9db0d34efb62eace57bfc98309f372e8336 Mon Sep 17 00:00:00 2001 From: valeboth <109695782+valeboth@users.noreply.github.com> Date: Wed, 26 Aug 2026 12:18:25 +0300 Subject: [PATCH] feat: daily cleanup cron for old D1 data - Cron Trigger (wrangler.toml: 04:00 UTC daily) + scheduled handler in index.ts - lib/cleanup.ts: batch-delete rooms older than 30 days (+ their swipes/matches), then orphaned users and profiles - default export now { fetch, scheduled }; Room export unchanged - health -> v3.7 Validated the cleanup SQL against local D1 (5 statements OK). --- src/index.ts | 11 +++++++++-- src/lib/cleanup.ts | 24 ++++++++++++++++++++++++ wrangler.toml | 4 ++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 src/lib/cleanup.ts diff --git a/src/index.ts b/src/index.ts index ba19fa6..836ca57 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,13 +4,14 @@ import { users } from "./routes/users"; import { profile } from "./routes/profile"; import { rooms } from "./routes/rooms"; import { search } from "./routes/search"; +import { cleanupOldData } from "./lib/cleanup"; // Durable Object — live per-room state (WebSocket + match broadcast). export { Room } from "./durable-objects/room"; const app = new Hono<{ Bindings: Env }>(); -app.get("/api/health", (c) => c.json({ ok: true, service: "cinemate", version: "v3.6" })); +app.get("/api/health", (c) => c.json({ ok: true, service: "cinemate", version: "v3.7" })); app.route("/api/users", users); app.route("/api/profile", profile); @@ -24,4 +25,10 @@ app.get("/join", (c) => c.env.ASSETS.fetch(new Request(new URL("/index.html", c. app.notFound((c) => c.json({ error: "not_found" }, 404)); -export default app; +// fetch = the Hono app; scheduled = the daily D1 cleanup (Cron Trigger, see wrangler.toml). +export default { + fetch: (request: Request, env: Env, ctx: ExecutionContext) => app.fetch(request, env, ctx), + scheduled: (_event: ScheduledController, env: Env, ctx: ExecutionContext) => { + ctx.waitUntil(cleanupOldData(env)); + }, +} satisfies ExportedHandler; diff --git a/src/lib/cleanup.ts b/src/lib/cleanup.ts new file mode 100644 index 0000000..3cd1cc4 --- /dev/null +++ b/src/lib/cleanup.ts @@ -0,0 +1,24 @@ +// Scheduled cleanup: drop data older than the retention window so D1 stays tidy. +// Rooms are ephemeral ("what do we watch tonight"), so 30 days is plenty. +// Runs from the Cron Trigger (see wrangler.toml + the scheduled handler in index.ts). + +import type { Env } from "../types"; + +const RETENTION = "-30 days"; + +export async function cleanupOldData(env: Env): Promise { + const oldRooms = `SELECT id FROM rooms WHERE created_at < datetime('now', '${RETENTION}')`; + // Delete children first (not relying on FK cascade being enabled), then rooms, + // then orphaned users and their profiles. + await env.DB.batch([ + env.DB.prepare(`DELETE FROM swipes WHERE room_id IN (${oldRooms})`), + env.DB.prepare(`DELETE FROM matches WHERE room_id IN (${oldRooms})`), + env.DB.prepare(`DELETE FROM rooms WHERE created_at < datetime('now', '${RETENTION}')`), + env.DB.prepare( + `DELETE FROM users WHERE created_at < datetime('now', '${RETENTION}') + AND id NOT IN (SELECT user_a_id FROM rooms) + AND id NOT IN (SELECT user_b_id FROM rooms WHERE user_b_id IS NOT NULL)`, + ), + env.DB.prepare(`DELETE FROM profiles WHERE user_id NOT IN (SELECT id FROM users)`), + ]); +} diff --git a/wrangler.toml b/wrangler.toml index 8558bae..7b5dd3c 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -12,6 +12,10 @@ workers_dev = false # The Worker serves both the files in public/ and the /api routes on one origin. # Assets take priority: GET / -> index.html, /app.js -> file; routes without a # matching file (e.g. /api/*) reach the Worker. No CORS, a single deploy. +# Daily cleanup of old rooms/swipes/matches (see scheduled handler in src/index.ts). +[triggers] +crons = ["0 4 * * *"] + [assets] directory = "./public" # ASSETS binding: lets the Worker serve index.html for SPA paths like /join.