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.
Use Node.js 24:
nvm usenpm install
npm run devVite will print the local URL, usually http://localhost:5173.
npm run buildThe production build is written to dist/.
Install the Playwright browser once:
npx playwright install chromiumRun E2E tests:
npm run test:e2ePlaywright starts the app automatically and runs a smoke test against /.
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_URLfor 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.
Copy the env template once (or edit .env directly):
cp .env.example .envStart Postgres with Docker Compose:
docker compose up -dConnection details for local development:
- Host:
localhost - Port:
POSTGRES_PORTfrom.env(default5432) - Database:
POSTGRES_DBfrom.env(defaulthackathon) - User:
POSTGRES_USERfrom.env(defaulthackathon) - Password:
POSTGRES_PASSWORDfrom.env(defaulthackathon)
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.sqlSeed 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.sqlStop the database:
docker compose downAll frontend and backend env vars now live in the root .env file.
Server env vars:
DATABASE_URL(required unlessDEBUG_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 (default8787).ADMIN_SECRET(optional): required only forPATCH /api/admin/projects/:id/status.DEBUG_SQLITE_ONLY(optional): settrueto run the API on SQLite as the primary database (no Postgres dependency).DEBUG_SQLITE_MIRROR(optional): settrueto mirror mutation payloads into local SQLite for debugging.DEBUG_SQLITE_PATH(optional): SQLite file location (defaultserver/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:serverRun backend in SQLite-only dev mode (no Postgres required):
npm run dev:server:sqliteCore API usage:
GET /api/projects/cards?limit=20&offset=0- Returns approved project cards only.
- Includes aggregated fields:
signupCount,participantNamesPreview(up to 5), andisSignedUp(whenX-Client-Idis 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:serverServe the built frontend from Hono (single server for app + API):
npm run build
npm run build:server
npm run start:serverThen open http://localhost:8787.
Enable mirror mode in .env:
DEBUG_SQLITE_MIRROR=true
DEBUG_SQLITE_PATH=server/db/sqlite/debug-mirror.sqliteWhen 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:inspectReset the mirror file:
npm run debug:sqlite:resetLimitations:
- Mirror writes are best-effort and never block API responses.
- SQLite mirror is a debug aid, not a source of truth; Postgres remains authoritative.
Set this in .env when you want local backend development without Postgres:
DEBUG_SQLITE_ONLY=true
DEBUG_SQLITE_PATH=server/db/sqlite/debug-mirror.sqliteIn this mode, the API reads/writes participants, projects, and signups directly in SQLite.
Ensure local env file exists:
cp .env.example .envThen 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:fullstackThis starts:
- frontend (Vite) on
http://localhost:5173 - backend (Hono) on
http://localhost:8787
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"}'- 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.