Ethan Hathaway

Ethan Hathaway - Personal Portfolio

A personal portfolio site for Ethan Hathaway showcasing things I make — woodworking, baking, cooking, and other crafts.

Stack

Development

bun install

# Local dev without platform bindings
bun run dev:vite

# Local dev with Cloudflare bindings (D1, assets, etc.)
bun run dev

# Remote dev with Cloudflare bindings (writes to remote D1/R2 preview bucket)
bun run dev:remote

Environment Files

Example .env.local:

DATABASE_URL=file:local.db
PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_your_key
CLERK_SECRET_KEY=sk_test_your_key
PUBLIC_R2_BASE_URL=

Example .env.remote:

DATABASE_URL=file:local.db
PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_your_key
CLERK_SECRET_KEY=sk_test_your_key
PUBLIC_R2_BASE_URL=https://<account-id>.r2.cloudflarestorage.com/portfolio-artifacts-22-dev

Build and Deploy

bun run build
bun run preview
bun run deploy

Integration Testing (API)

Run a local worker, then execute the API integration test. This requires a real Clerk session token.

bunx wrangler dev --port 8787
E2E_BASE_URL=http://localhost:8787 CLERK_TEST_TOKEN=... bunx playwright test e2e/categories.api.spec.ts

Cloudflare Secrets

Secrets must be set via wrangler secret put — never in wrangler.toml.

bunx wrangler secret put CLERK_SECRET_KEY
bunx wrangler secret put GOOGLE_CLIENT_SECRET
bunx wrangler secret put GOOGLE_TOKEN_ENCRYPTION_KEY

For local dev, add secrets to both .env and .dev.vars.

Example .dev.vars:

CLERK_SECRET_KEY=sk_test_...
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_TOKEN_ENCRYPTION_KEY=...

GOOGLE_TOKEN_ENCRYPTION_KEY is a self-generated key for encrypting Google OAuth tokens in D1. Generate one with:

openssl rand -base64 32

Google Photos Integration

Imports photos and videos from Google Photos into projects via the Picker API.

Google Cloud Setup

  1. Go to Google Cloud Console
  2. Create or select a project
  3. Enable the Photos Picker API (APIs & Services > Library > search “Photos Picker API”)
  4. Go to APIs & Services > Credentials and create an OAuth 2.0 Client ID (type: Web application)
  5. Add authorized redirect URIs:
    • Local: http://localhost:8788/api/integrations/google-photos/callback
    • Production: https://erhathaway.com/api/integrations/google-photos/callback
  6. Set GOOGLE_CLIENT_ID in wrangler.toml [vars]
  7. Set GOOGLE_CLIENT_SECRET and GOOGLE_TOKEN_ENCRYPTION_KEY as secrets (see above)

Usage

  1. Navigate to Admin > Integrations > Google Photos and click Connect
  2. Authorize with your Google account
  3. In any project editor, click Add Artifact > Google Photos
  4. Select photos/videos in the picker, then confirm the import

API Notes

Database (Cloudflare D1)

Storage (Cloudflare R2)

Migrations

# Generate SQL from the schema
bun run db:generate

# Apply a generated migration to D1
bunx wrangler d1 execute portfolio-db --file=./drizzle/<migration>.sql