3.6 KiB
3.6 KiB
AGENTS.md
Commands
# Root (runs both frontend + backend)
bun run dev
# Individual packages
bun run dev:frontend
bun run dev:backend
bun run build:frontend
# Linting (Biome - ESLint was replaced)
bun run lint
bun run lint:fix # fixes and formats
# Prisma (run from backend)
bun --filter backend prisma:generate # regenerate client after schema changes
bun --filter backend prisma:migrate # apply migrations
Architecture
apps/frontend/ # React + Vite + TanStack Router/Query
apps/backend/ # Bun + Hono + Prisma
packages/api-contract/ # Shared Zod schemas, types, route definitions
Shared contract pattern:
- Update
packages/api-contractfirst (schemas/types) - Implement handler in
apps/backend - Consume from
apps/frontendvia workspace import@repo/api-contract
Authentication (Better Auth)
The project uses Better Auth for session management, replacing the legacy Supabase Auth.
- Backend Context:
AppContext(mapped viaAppEnv) providesc.get('user')andc.get('session'). - User Roles: The
Usermodel includes arolefield (default:member). UserequireSuperAdminmiddleware for protected admin routes. - Session Persistence:
- Backend: Hono CORS must have
credentials: true. - Frontend:
authClientmust havefetchOptions.credentials: 'include'. - Frontend: Axios instance must have
withCredentials: true.
- Backend: Hono CORS must have
Key Quirks
- Prisma 7: Uses
prisma.config.ts, requiresDATABASE_URLenv var at generate time. - Gravatar Fallback: Users without an
imageget an automatic Gravatar Identicon.- Backend: Handled in
user.service.tsfor profile API. - Frontend: Handled in
AuthProvider(auth.tsx) for the session state.
- Backend: Handled in
- Zod v4: Use
.issuesinstead of.errorsfor validation errors. - TanStack Router: Routes are code-generated. Use
--filter frontend buildnot raw vite build.
Real-time Updates (SSE)
The project uses Server-Sent Events (SSE) for real-time booking updates in the admin panel.
- Backend:
apps/backend/src/lib/sse.tsprovides thesseManagerand SSE endpoint/api/events/:channel. - Event flow: When a public booking is created, the handler emits to channel
complex:{complexId}. - Frontend:
home-page.tsxconnects to the SSE endpoint and invalidates the bookings query on new events.
Important - Multi-process limitation: The current SSE implementation works with a single Bun process only. If you deploy with multiple workers (e.g., clustering in Dokploy), events won't be shared across workers. For production with multiple workers, use Redis for pub/sub:
- Install
@hono/node-serveror a Redis client - Replace
sseManagerwith Redis pub/sub - Or fall back to Socket.io with Redis adapter
Docker Deployment (Dokploy)
- Build Context:
./(monorepo root) - Dockerfile:
Dockerfile(in root, notapps/backend/) - Required args:
DATABASE_URL,VITE_API_BASE_URL,BETTER_AUTH_SECRET,BETTER_AUTH_URL - bun.lock must be tracked: It's in
.gitignoreby default - remove it for Docker builds
Linting
Biome is used (not ESLint). Config in biome.json:
- Line width: 100
- Quote style: single
- Semicolons: always
noUnusedVariables: warn
Environment Variables
Backend (apps/backend/.env):
DATABASE_URL- PostgreSQL connection (required for Prisma)BETTER_AUTH_SECRET,BETTER_AUTH_URL- Auth coreAPP_BASE_URL- Frontend URL for trusted originsCORS_ORIGIN- Frontend URL for CORS policy
Frontend (apps/frontend/.env):
VITE_*prefix required (Vite embeds these at build time)VITE_API_BASE_URL- Backend URL (default: http://localhost:3000)