Skip to content

Latest commit

 

History

History
256 lines (174 loc) · 6.54 KB

File metadata and controls

256 lines (174 loc) · 6.54 KB

AI Coding Hackathon

A small React + TypeScript app for hackathon participants to browse AI coding projects, join one project at a time, switch or give up their current selection, and propose new project ideas.

Run Locally

Use Node.js 24:

nvm use
npm install
npm run dev

Vite will print the local URL, usually http://localhost:5173.

Build

npm run build

The production build is written to dist/.

End-to-End Tests (Playwright)

Install the Playwright browser once:

npx playwright install chromium

Run E2E tests:

npm run test:e2e

Playwright starts the app automatically and runs a smoke test against /.

Deploy

Deploy the dist/ folder to any static host. For Netlify, use:

  • Build command: npm run build
  • Publish directory: dist
  • Context env config: see netlify.toml (VITE_API_URL for deploy previews and production)

Environment and migration strategy for preview/production is documented in doc/environments.md.

The current MVP stores identity, signups, and pending proposals in localStorage; no backend environment variables are required yet.

Local Postgres Setup

Copy the env template once (or edit .env directly):

cp .env.example .env

Start Postgres with Docker Compose:

docker compose up -d

Connection details for local development:

  • Host: localhost
  • Port: POSTGRES_PORT from .env (default 5432)
  • Database: POSTGRES_DB from .env (default hackathon)
  • User: POSTGRES_USER from .env (default hackathon)
  • Password: POSTGRES_PASSWORD from .env (default hackathon)

Apply the schema migration:

set -a; source .env; set +a
docker compose exec -T postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" < server/db/migrations/001_initial_schema.sql

Seed starter projects:

set -a; source .env; set +a
docker compose exec -T postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" < server/db/seeds/001_projects.sql

Stop the database:

docker compose down

Backend Server (Hono)

All frontend and backend env vars now live in the root .env file.

Server env vars:

  • DATABASE_URL (required unless DEBUG_SQLITE_ONLY=true): Postgres connection string used by the backend.
  • CORS_ORIGIN (required): Comma-separated allowlist. Include local frontend (http://localhost:5173) and production Netlify origin(s).
  • PORT (optional): API server port (default 8787).
  • ADMIN_SECRET (optional): required only for PATCH /api/admin/projects/:id/status.
  • DEBUG_SQLITE_ONLY (optional): set true to run the API on SQLite as the primary database (no Postgres dependency).
  • DEBUG_SQLITE_MIRROR (optional): set true to mirror mutation payloads into local SQLite for debugging.
  • DEBUG_SQLITE_PATH (optional): SQLite file location (default server/db/sqlite/debug-mirror.sqlite).

Identity limitation (MVP): protected mutation routes resolve the participant from X-Client-Id and scope changes to that participant. This is not real authentication; if a client ID is exposed, another client could impersonate it. Future fix: replace header-only identity with session tokens or full auth.

Run the backend in dev mode:

npm run dev:server

Run backend in SQLite-only dev mode (no Postgres required):

npm run dev:server:sqlite

Core API usage:

  • GET /api/projects/cards?limit=20&offset=0
    • Returns approved project cards only.
    • Includes aggregated fields: signupCount, participantNamesPreview (up to 5), and isSignedUp (when X-Client-Id is provided).
    • Pagination output: { items, limit, offset, hasMore }.
  • GET /api/projects/:id
    • Returns approved project details only.
    • Includes full participant list for that project.

Build backend output:

npm run build:server

Serve the built frontend from Hono (single server for app + API):

npm run build
npm run build:server
npm run start:server

Then open http://localhost:8787.

Debug SQLite Mirror (Development)

Enable mirror mode in .env:

DEBUG_SQLITE_MIRROR=true
DEBUG_SQLITE_PATH=server/db/sqlite/debug-mirror.sqlite

When enabled, mutation payloads from bootstrap/join/switch/give up/propose/admin status updates are mirrored into SQLite tables (participants, projects, signups, events) for local debugging only.

Inspect mirrored data:

npm run debug:sqlite:inspect

Reset the mirror file:

npm run debug:sqlite:reset

Limitations:

  • Mirror writes are best-effort and never block API responses.
  • SQLite mirror is a debug aid, not a source of truth; Postgres remains authoritative.

SQLite-Only Primary Mode (Development)

Set this in .env when you want local backend development without Postgres:

DEBUG_SQLITE_ONLY=true
DEBUG_SQLITE_PATH=server/db/sqlite/debug-mirror.sqlite

In this mode, the API reads/writes participants, projects, and signups directly in SQLite.

Full Stack Local Development

Ensure local env file exists:

cp .env.example .env

Then run full stack locally:

docker compose up -d
set -a; source .env; set +a
docker compose exec -T postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" < server/db/migrations/001_initial_schema.sql
docker compose exec -T postgres psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" < server/db/seeds/001_projects.sql
npm run dev:fullstack

This starts:

  • frontend (Vite) on http://localhost:5173
  • backend (Hono) on http://localhost:8787

Moderation Flow (SQL)

The app should only list projects with status = 'approved'. Proposals are created as pending, then manually reviewed:

List pending projects:

select id, title, status, created_at
from projects
where status = 'pending'
order by created_at desc;

Approve a project:

update projects
set status = 'approved'
where id = '<project-uuid>';

Reject a project:

update projects
set status = 'rejected'
where id = '<project-uuid>';

Optional admin API moderation:

curl -X PATCH "http://localhost:8787/api/admin/projects/<project-uuid>/status" \
  -H "Content-Type: application/json" \
  -H "X-Admin-Secret: $ADMIN_SECRET" \
  -d '{"status":"approved"}'

Frontend Security Notes

  • Netlify security headers are defined in public/_headers (CSP, frame-ancestors, Referrer-Policy, X-Content-Type-Options, Permissions-Policy).
  • Propose form input is validated on both client and server for required fields, max lengths, and script-tag rejection.
  • User-provided strings are rendered with standard React JSX interpolation (no dangerouslySetInnerHTML), so React escapes content by default.