Designed solely for internal family use with minimal functionality:
- Single Family Password: Token stored in frontend localStorage.
- Secure Access: All photos are accessed via a Worker proxy; real R2 paths are never exposed.
- Photo Upload: Support for real Cloudflare R2 uploads.
- Daily Journal: One text entry per day.
- Timeline View: Display entries sorted by date.
- Photo Wall: A simple gallery view.
- Multi-user registration/login.
- Video uploads.
- Thumbnail generation.
- Multi-family support.
- Comments/Likes.
- Batch photo uploads.
Goal: Ready for genuine family use within one week.
Frontend (Static: Cloudflare Pages)
|
v
Cloudflare Worker (API/BFF & Proxy)
|
+--> Supabase Postgres (Stores metadata: entries / media)
|
+--> Cloudflare R2 (Private storage, no public access)
Security Policy:
- The frontend never directly accesses Supabase or R2.
- Media files are proxied through the
/api/media/*route. - Authentication:
- API requests use the
AuthorizationHeader. - Image requests use a URL parameter
?token=....
- API requests use the
create table entries (
id bigint generated always as identity primary key,
date date not null,
title text,
content text,
created_at timestamptz default now()
);create table media (
id bigint generated always as identity primary key,
entry_id bigint references entries(id) on delete set null,
r2_key text not null, -- Actual path in R2
file_type text not null, -- e.g., 'image'
taken_at timestamptz,
created_at timestamptz default now()
);Object Key Example:
2026-01-16/1737012345-abcd12.jpg
Naming Rule:
{YYYY-MM-DD}/{timestamp}-{random}.{ext}
All API paths are accessed via /api/....
POST /api/login
- Body:
{ "password": "..." } - Response:
{ "token": "FAMILY_TOKEN" }
GET /api/timeline
- Response:
[
{
"id": 123,
"date": "2026-01-16",
"title": "Baby's Day",
"content": "Baby was very happy today.",
"media": [
{ "id": 1, "url": "/api/media/key.jpg?token=xxxx" }
]
}
]GET /api/media/:key?token=xxxx
- The Worker validates the token, then streams data from the private R2 bucket.
- Sets
Cache-Control: privateto balance privacy and performance.
POST /api/upload (Multipart/form-data)
- Supports creating an entry and uploading an image simultaneously.
- Parameters:
file(the image),entry_id(optional),title(optional),content(optional),date(optional).
POST /api/entry
- Body:
{ "id": 123, "title": "...", "content": "...", "date": "..." } - If
idis present, it updates; otherwise, it creates a new entry.
DELETE /api/entry/:id
- Deletes the entry, its associated physical files in R2, and database media records.
BabyTimeLineMVP/
├─ public/ # Frontend Static Files (Cloudflare Pages)
│ ├─ js/
│ │ ├─ api.js # API Request Wrapper (Headers & Validation)
│ │ ├─ auth.js # Login & Token Management
│ │ ├─ timeline.js # Timeline Rendering
│ │ ├─ plan.js # "Expectations" Logic
│ │ ├─ complete.js # "Achievements" Logic
│ │ └─ record.js # Record Moments/History Logic
│ ├─ index.html
│ ├─ login.html
│ ├─ timeline.html
│ ├─ milestones.html
│ ├─ plan.html
│ ├─ complete.html
│ └─ record.html
│
├─ worker/ # Backend Code (Cloudflare Worker)
│ ├─ src/
│ │ ├─ index.ts # Routing Entry & Global Auth
│ │ ├─ r2.ts # R2 Operation Wrapper
│ │ ├─ supabase.ts # Supabase REST Wrapper
│ │ └─ routes/
│ │ ├─ auth.ts
│ │ ├─ timeline.ts
│ │ ├─ upload.ts
│ │ └─ media.ts # Media Proxy Route
│ ├─ wrangler.toml # Basic Config (No Secrets)
│ ├─ tsconfig.json # Worker Type Config
│ └─ package.json
- Maximum Security: Photos are not exposed to the public internet; prevents leakage.
- No Custom Domain: No need to configure a custom domain for the R2 bucket.
- Simple Auth: Unified validation using
FAMILY_TOKEN.
- All sensitive keys (
SUPABASE_SERVICE_ROLE_KEY,FAMILY_TOKEN) are stored viawrangler secret putand are never exposed in the source code.
- Infrastructure Initialization: Folder structure and skeleton code.
- Backend Implementation: Supabase REST wrapper, R2 proxy, global auth logic.
- Security Hardening: Token mechanism, proxy mode, secrets management.
- Database Setup: Execute SQL in Supabase.
- Frontend Integration: Replace skeleton logic with real UI operations.
- Deployment:
npx wrangler deploy.
To run the project locally:
-
Install Dependencies:
npm install
-
Run Worker: Navigate to the worker directory and run the development server:
cd worker npm run dev # Or directly via wrangler npx wrangler dev
-
Access the App: Visit
http://localhost:8787in your browser.
Thanks to Cloudflare Workers' static asset hosting, you only need to deploy the Worker.
-
Prepare Resources:
- Create the database in Supabase (use the SQL schema provided in Section 3).
- Create an R2 bucket named
baby-timeline-mediain Cloudflare.
-
Configure Secrets:
cd worker npx wrangler secret put SUPABASE_SERVICE_ROLE_KEY npx wrangler secret put FAMILY_TOKEN -
Deploy:
npx wrangler deploy
The Worker will automatically handle API requests and host all static files from the /public directory.