Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 44 additions & 2 deletions OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,57 @@ The platform runs on a weekly cycle: **Saturday 00:00 → Friday 14:29** (Helsin

### Automated Cron

A Vercel Cron job runs every Friday at 11:30 UTC (14:30 EET/EEST):
Production runs on **Cloudflare Pages**. Something must call the snapshot endpoint every **Friday at 11:30 UTC** (14:30 EET/EEST):

- Endpoint: `GET /api/cron/demo-day`
- Authenticated via `CRON_SECRET` env var (Bearer token)
- Authenticated via `CRON_SECRET` env var (`Authorization: Bearer …` header)
- What it does:
1. Queries the top 3 products by votes for the current week
2. Inserts rows into `demo_day_winners`
3. Marks the `demo_days` row as `completed`

#### Cloudflare Cron Trigger (recommended)

Create a Worker with a cron schedule and store `CRON_SECRET` as a Worker secret.

**`wrangler.toml`**
```toml
name = "productbuilders-demo-day-cron"
main = "src/index.ts"
compatibility_date = "2024-09-23"

[triggers]
crons = ["30 11 * * 5"]
```

**`src/index.ts`**
```ts
export default {
async scheduled(
_controller: ScheduledController,
env: { CRON_SECRET: string; APP_URL: string },
) {
const res = await fetch(`${env.APP_URL}/api/cron/demo-day`, {
headers: { Authorization: `Bearer ${env.CRON_SECRET}` },
});
if (!res.ok) {
console.error("demo-day cron failed", res.status, await res.text());
}
},
};
```

Deploy the Worker, then set secrets:

```bash
wrangler secret put CRON_SECRET # same value as on Cloudflare Pages
wrangler secret put APP_URL # e.g. https://productbuilders.app
```

Check **Workers → your cron Worker → Logs** after the first run to confirm a `200` response.

> **Vercel deployments:** `vercel.json` configures this schedule automatically when hosted on Vercel and `CRON_SECRET` is set in project env vars.

### Manual Trigger

If the cron fails or you need to trigger it manually:
Expand Down
50 changes: 42 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Builders choose one of two paths when they submit:
- **Database, Auth, Storage:** Supabase (Postgres, Row Level Security, Auth, Storage)
- **Styling:** Tailwind CSS v4
- **Fonts:** Fraunces (display), Manrope (body), JetBrains Mono (metadata)
- **Deployment:** Vercel (with a weekly cron job)
- **Deployment:** Cloudflare Pages (with a scheduled Worker for the weekly demo-day snapshot)

## Local setup

Expand All @@ -60,8 +60,23 @@ npm install
- `supabase/migrations/001_initial_schema.sql`
- `supabase/migrations/002_demo_type_and_guided_fields.sql`
- `supabase/migrations/003_admin_read_all_products.sql`
3. Enable **Google OAuth** (optional) under Authentication, Providers, Google.
4. Create a public **Storage bucket** named `product-images`.
3. Under **Authentication → URL Configuration**, set:
- **Site URL:** your production URL (e.g. `https://productbuilders.app`)
- **Redirect URLs:** `https://productbuilders.app/auth/callback`, `http://localhost:3000/auth/callback` (add your `*.pages.dev` preview URL if needed)
4. Enable **Google OAuth** (optional) under Authentication, Providers, Google.
5. Create a public **Storage bucket** named `product-images`, then add upload/read policies in the SQL Editor:
```sql
CREATE POLICY "Users upload own images"
ON storage.objects FOR INSERT TO authenticated
WITH CHECK (
bucket_id = 'product-images'
AND (storage.foldername(name))[1] = 'products'
);

CREATE POLICY "Public read product images"
ON storage.objects FOR SELECT TO public
USING (bucket_id = 'product-images');
```

### 3. Configure environment

Expand Down Expand Up @@ -99,14 +114,33 @@ After creating your first account, grab your user UUID from the Supabase Auth da
- `npm run start`: serve the production build
- `npm run lint`: run ESLint

## Deploy to Vercel
## Deploy to Cloudflare Pages

1. Push to GitHub.
2. Import the repo in Vercel.
3. Add the environment variables from `.env.example`.
4. Deploy.
2. In [Cloudflare Dashboard](https://dash.cloudflare.com) → **Workers & Pages** → **Create** → **Pages** → connect this repo.
3. Use these build settings:
- **Framework preset:** Next.js (or your OpenNext setup if you use that adapter)
- **Build command:** `npm run build`
- **Build output:** follow Cloudflare’s Next.js guidance for your chosen adapter (often `.open-next/` when using OpenNext)
4. Add the environment variables from `.env.example` under **Settings → Environment variables** (Production and Preview):
- `NEXT_PUBLIC_SUPABASE_URL`
- `NEXT_PUBLIC_SUPABASE_ANON_KEY`
- `SUPABASE_SERVICE_ROLE_KEY`
- `CRON_SECRET`
5. Deploy and confirm auth redirects work against your Cloudflare URL (custom domain or `*.pages.dev`).

### Friday demo-day cron (required on Cloudflare)

Cloudflare Pages does **not** read `vercel.json`. Schedule something to call the snapshot endpoint every **Friday at 11:30 UTC** (14:30 Helsinki):

`vercel.json` configures a cron job that runs every Friday at 11:30 UTC (14:30 Helsinki) to snapshot the week's top 3 demo-day winners. See the operations guide for manual trigger options and admin tasks.
```
GET https://productbuilders.app/api/cron/demo-day
Authorization: Bearer YOUR_CRON_SECRET
```

Recommended: a **Cloudflare Cron Trigger** on a small Worker (see [OPERATIONS.md](./OPERATIONS.md)). Alternatives: the admin panel (“Take snapshot now”), an external cron service, or manual `curl`.

> **Vercel alternative:** `vercel.json` in this repo configures the same cron for Vercel deployments only. If you deploy there instead, set the same env vars in Vercel and skip the Worker setup.

## Project structure

Expand Down