# The Childhood Vault — Cloudflare Edition This is your app, fully migrated off Supabase onto Cloudflare: - **Cloudflare Pages** hosts `index.html` (the site itself, free) - **Cloudflare R2** stores your photos/videos (S3-compatible object storage, no egress fees) - **Pages Functions** (small serverless functions in `functions/api/`) sit in between the two, so nothing sensitive is ever shipped to the browser ## What actually changed, not just where it's hosted The old Supabase version put a storage API key directly in the page's JavaScript, and "admin mode" was just a password check done *in the browser* — anyone could open devtools, skip the check, and call the Supabase API directly to delete everything. This version fixes that for real: - No key of any kind ships to the browser. All storage calls happen server-side, in the Functions. - The admin passkey is verified by the server (`/api/verify`) using a constant-time comparison. Deleting a memory requires the server to independently confirm the key — the client can't fake it. - Uploads are validated server-side too (file type, size, and the filename can't contain path-traversal tricks like `../`). ## File structure ``` index.html the app itself — open this at your Pages URL wrangler.toml config for local dev / CLI deploys functions/ api/ list.js GET — lists everything in the vault verify.js POST — checks the admin passkey upload.js POST — saves a new photo/video object/ [name].js GET streams a file back (supports video seeking), DELETE removes it ``` ## Setup via the dashboard (recommended — no command line needed) ### 1. Create the R2 bucket 1. Cloudflare dashboard → **R2 Object Storage** → **Create bucket** 2. Name it anything, e.g. `childhood-vault`. Location: "Automatic" is fine. ### 2. Create the Pages project 1. Cloudflare dashboard → **Workers & Pages** → **Create** → **Pages** → **Upload assets** 2. Project name: whatever you like — this becomes part of your `.pages.dev` URL 3. Upload this whole folder (keep `index.html` and `functions/` together, at the root — don't upload just `index.html` alone) 4. Deploy ### 3. Bind the R2 bucket to the project 1. Open your new Pages project → **Settings** → **Functions** → **R2 bucket bindings** → **Add binding** 2. Variable name: `VAULT_BUCKET` — this must match exactly, the code looks for this name 3. R2 bucket: the one you created in step 1 4. Save ### 4. Set your real admin passkey 1. Same project → **Settings** → **Environment variables** → **Add variable** 2. Name: `ADMIN_KEY`, Value: pick a real passphrase (this one is actually checked by the server now, so make it a good one, not `admin123`) 3. Click **Encrypt** so it's stored as a secret rather than plaintext 4. Save ### 5. Redeploy Binding and environment variable changes only take effect on the *next* deployment. Go to **Deployments** and retry the latest one (or re-upload the folder). ### 6. Try it Visit your `*.pages.dev` URL, click **Enter The Vault**, and you should see an empty gallery ready for photos. Click **Admin**, enter the passphrase you set in step 4 — you should see "Access granted." ### Optional: also require the passkey for uploads By default, anyone with your site link can add photos (matching how the original app behaved — handy if family members are dropping in photos without wanting to log in). If you'd rather only admins can upload: - Add another environment variable: `REQUIRE_ADMIN_FOR_UPLOAD` = `true`, then redeploy. ### Optional: use your own domain Pages project → **Custom domains** → add the domain/subdomain you want. Cloudflare handles the certificate automatically. ## Setup via Wrangler CLI (if you'd rather script it) ```bash npm install -g wrangler wrangler login # Create the bucket wrangler r2 bucket create childhood-vault # From inside this folder: wrangler pages project create childhood-vault wrangler pages deploy . --project-name=childhood-vault # Set the admin secret (you'll be prompted to type the value) wrangler pages secret put ADMIN_KEY --project-name=childhood-vault ``` `wrangler.toml` already declares the R2 binding, but Cloudflare currently applies R2 bindings for *existing* Pages projects most reliably through the dashboard (Settings → Functions → R2 bucket bindings) — if `wrangler pages deploy` doesn't pick it up automatically on your Wrangler version, just add the binding there once and it'll stick for every future deploy. ## Local development (test before you deploy) ```bash npm install -g wrangler wrangler pages dev . --r2=VAULT_BUCKET ``` This runs a local R2 emulator so you can test uploads/deletes without touching real data. Create a `.dev.vars` file (don't commit it) for your local admin key: ``` ADMIN_KEY=test123 ``` ## Moving your existing photos over If you already have memories sitting in the old Supabase bucket, the simplest path is: 1. Download each file from Supabase (the Storage dashboard lets you download the whole bucket, or use their CLI). 2. Re-upload them through the vault's own "Drop memories here" box once it's live on Cloudflare — that way filenames stay in the `timestamp_index_caption.ext` format the app expects for dates and captions. For a large archive, it's worth asking me for a small one-off migration script instead of doing this by hand — I can write one that copies files across directly if you give me API access to both. ## Costs R2's free tier: 10GB storage, 1 million Class A operations (writes/lists) and 10 million Class B operations (reads) per month, and — unlike S3 — **no egress fees** for serving files. Pages hosting itself is free. For a family photo vault, you should never see a bill.