Files
2026-06-20 20:16:20 +10:00

237 lines
8.6 KiB
Markdown

# Heygo
Heygo is an independent Cloudflare React + Workers scaffold for a future shortlinks app.
This repository lives at `/home/ai-bot/code/heygo` and is intentionally separate from the old Linux/Django Links repository. The old Links repository under `/home/ai-bot/code/links` is read-only reference material only and must not be modified by this project.
## Quick start (local)
> Prerequisites: Node.js 18+, npm, and [just](https://github.com/casey/just) installed.
```bash
git clone https://github.com/wahyd4/heygo.git
cd heygo
npm install
just build # build React SPA into dist/client
just migrate-local # apply D1 schema to local SQLite
just dev # start Worker on http://localhost:8787
```
Open `http://localhost:8787` in your browser — the SPA and API are both served by the Worker.
### Sign in as admin locally
OAuth isn't available in local dev. Use the dev-only login instead:
1. Start the worker: `just dev`
2. Open `http://localhost:8787/#/dev-login` in your browser
3. Enter your email (pre-filled with `wahyd4@gmail.com`) and click "Sign in as admin"
Or via CLI:
```bash
just dev-admin # creates admin user + session
just dev-admin other@example.com # different email
just dev-logout # clears the session cookie
```
This endpoint is guarded: it only works when `PUBLIC_HOST=localhost` (or `APP_BASE_URL`
contains `localhost`). In dev/prod deployments it returns 403. The dev login bypasses
OAuth, finds-or-creates the user, promotes them to admin, and sets a `heygo_session`
cookie — exactly what a real OAuth callback would do, minus the provider.
### Just commands
Run `just` (no args) to list all recipes. The most common ones:
| Command | What it does |
| --- | --- |
| `just build` | Type-check TypeScript + build React SPA into `dist/client` |
| `just dev` | Start Cloudflare Worker locally on port 8787 (API + SPA) |
| `just dev-ui` | Start Vite dev server on port 5173 with HMR (frontend only) |
| `just test` | Run the full Vitest suite (134 tests) |
| `just test-watch` | Run tests in watch mode |
| `just smoke` | Build, migrate, start worker, curl health — quick end-to-end check |
| `just typecheck` | Type-check without emitting |
### Local D1 database helpers
| Command | What it does |
| --- | --- |
| `just migrate-local` | Apply D1 migrations to local SQLite |
| `just db-tables` | List all tables in local D1 |
| `just db-links` | List all links in local D1 |
| `just db-query "SELECT …"` | Run arbitrary SQL on local D1 |
| `just add-link <alias> <url>` | Insert a test public shortlink |
| `just rm-link <alias>` | Delete a link by alias |
| `just db-reset` | Drop local D1 and re-apply migrations |
### First-time local setup
```bash
npm install
just build
just migrate-local
# optional: add a test shortlink to try it out
just add-link claude https://claude.ai
# start the Worker
just dev
```
Then test it:
```bash
curl -I http://localhost:8787/claude # → 302 redirect to claude.ai
curl http://localhost:8787/missing # → 404 HTML page
curl http://localhost:8787/api/health # → {"ok":true,"service":"heygo-worker"}
```
Open `http://localhost:8787` for the SPA management UI.
### How local dev works
- `just dev` runs `wrangler dev` which starts a local Cloudflare Workers runtime (Miniflare) with a local D1 SQLite database at `.wrangler/state/`.
- The Worker serves the built React SPA from `dist/client/` (hence `just build` is required first).
- The Worker API handles `/api/*` routes, public shortlink redirects, and private shortlink redirects.
- Local `wrangler.jsonc` sets `PUBLIC_HOST=localhost` and `PRIVATE_HOST=localhost`, so both public and private routes resolve on the same port. This is fine for testing APIs, the SPA, and public redirects. Private shortlink auth flows are covered by unit tests (`tests/private-redirect.test.ts`).
- To iterate on the React frontend with hot reload, use `just dev-ui` (Vite on port 5173) in a separate terminal. Note: Vite and the Worker run on different ports, so `/api/*` calls from Vite won't reach the Worker unless you set up a proxy.
## Verification
```bash
just build # tsc --noEmit && vite build
just test # vitest run
```
- `just build` type-checks the TypeScript project and builds the React SPA into `dist/client` for Workers Assets.
- `just test` runs Vitest tests against the Worker API handler.
## Deployment
Heygo ships as a single Cloudflare Worker fronted by two custom domains: a
**public** shortlink host and a **private** shortlink host. There are two
environments, each with its own D1 database, KV namespace, Worker, and domains.
| Environment | Public host | Private host | Worker | D1 database |
| --- | --- | --- | --- | --- |
| dev | `dev.heygo.cc` | `my.dev.heygo.cc` | `heygo-dev` | `heygo-shortlinks-dev` |
| prod | `heygo.cc` | `my.heygo.cc` | `heygo` | `heygo-shortlinks` |
The Worker reads `PUBLIC_HOST`, `PRIVATE_HOST`, `APP_BASE_URL`, and
`COOKIE_DOMAIN` from env vars (set in each wrangler config), so the same code
serves every environment without hardcoded hostnames.
Admin users are configured with `ADMIN_EMAILS` in the matching Wrangler config:
- `wrangler.dev.jsonc` for `dev.heygo.cc`
- `wrangler.prod.jsonc` for `heygo.cc`
- `wrangler.jsonc` for local development
Use a comma, semicolon, space, or newline separated list, for example:
```jsonc
"ADMIN_EMAILS": "owner@example.com, ops@example.com"
```
When a signed-in user's email matches this list, the app treats that user as
`admin` even if the stored database role is still `user`.
### Prerequisites
- A Cloudflare account with the `heygo.cc` zone added.
- A Cloudflare API token with D1, Workers KV, Workers Scripts, and Workers
Routes permissions.
- Terraform >= 1.5 installed.
### 1. Provision infrastructure with Terraform
The `terraform/` directory provisions the D1 database, KV namespace, and both
Worker custom domains. See `terraform/README.md` for full details.
```bash
export CLOUDFLARE_API_TOKEN=*** # fill account_id + zone_ids in tfvars first
just infra-init
just infra-plan-dev # review, then:
just infra-apply-dev
just infra-plan-prod # review, then:
just infra-apply-prod
```
### 2. Copy Terraform outputs into Wrangler configs
After `just infra-apply-dev`, read the created resource IDs:
```bash
just infra-output dev
```
Paste the printed `d1_database_id` into `wrangler.dev.jsonc`
(`d1_databases[].database_id`) and `kv_namespace_id` into
`wrangler.dev.jsonc` (`kv_namespaces[].id`). Repeat for prod outputs into
`wrangler.prod.jsonc`. The placeholder `00000000-...` IDs are there until you
fill them in.
### 3. Apply D1 migrations to remote
```bash
just migrate-dev
just migrate-prod
```
### 4. Deploy the Worker
```bash
just deploy-dev # builds + deploys to dev.heygo.cc + my.dev.heygo.cc
just deploy-prod # builds + deploys to heygo.cc + my.heygo.cc
```
### Dev environment
The dev environment uses `dev.heygo.cc` (public) and `my.dev.heygo.cc` (private),
both under the `.heygo.cc` cookie domain. Session cookies set by the app UI on
`dev.heygo.cc` are automatically visible to `my.dev.heygo.cc`, so private
shortlink auth works out of the box — no extra configuration needed.
Prod works the same way: `heygo.cc` and `my.heygo.cc` share `.heygo.cc`.
### Google sign-in
Create a Google OAuth web client and add these authorized redirect URIs:
- Dev: `https://dev.heygo.cc/api/auth/google/callback`
- Prod: `https://heygo.cc/api/auth/google/callback`
Then set the Worker secrets for the target environment:
```bash
npx wrangler secret put GOOGLE_CLIENT_ID --config wrangler.dev.jsonc
npx wrangler secret put GOOGLE_CLIENT_SECRET --config wrangler.dev.jsonc
```
Repeat with `wrangler.prod.jsonc` for production. The Google sign-in flow uses
the `openid email profile` scopes, stores the Google account ID in
`oauth_accounts`, creates a `heygo_session` cookie, and promotes matching
`ADMIN_EMAILS` users to admin.
### All deployment commands
| Command | What it does |
| --- | --- |
| `just infra-init` | `terraform init` |
| `just infra-plan-dev` | Review infra changes for dev |
| `just infra-apply-dev` | Apply infra for dev |
| `just infra-plan-prod` | Review infra changes for prod |
| `just infra-apply-prod` | Apply infra for prod |
| `just infra-output <dev\|prod>` | Show Terraform outputs (D1 ID, KV ID) |
| `just migrate-dev` | Apply D1 migrations to remote dev database |
| `just migrate-prod` | Apply D1 migrations to remote prod database |
| `just deploy-dev` | Build + deploy Worker to dev |
| `just deploy-prod` | Build + deploy Worker to prod |
## Current API skeleton
- `GET /api/health` returns JSON health status from the Worker.
- Unknown `/api/*` routes return JSON `404` responses.