# AGENTS.md ## Commands ```sh # 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:** 1. Update `packages/api-contract` first (schemas/types) 2. Implement handler in `apps/backend` 3. Consume from `apps/frontend` via 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 via `AppEnv`) provides `c.get('user')` and `c.get('session')`. - **User Roles**: The `User` model includes a `role` field (default: `member`). Use `requireSuperAdmin` middleware for protected admin routes. - **Session Persistence**: - Backend: Hono CORS must have `credentials: true`. - Frontend: `authClient` must have `fetchOptions.credentials: 'include'`. - Frontend: Axios instance must have `withCredentials: true`. ## Key Quirks - **Prisma 7**: Uses `prisma.config.ts`, requires `DATABASE_URL` env var at generate time. - **Gravatar Fallback**: Users without an `image` get an automatic Gravatar Identicon. - Backend: Handled in `user.service.ts` for profile API. - Frontend: Handled in `AuthProvider` (`auth.tsx`) for the session state. - **Zod v4**: Use `.issues` instead of `.errors` for validation errors. - **TanStack Router**: Routes are code-generated. Use `--filter frontend build` not 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.ts` provides the `sseManager` and SSE endpoint `/api/events/:channel`. - **Event flow**: When a public booking is created, the handler emits to channel `complex:{complexId}`. - **Frontend**: `home-page.tsx` connects 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-server` or a Redis client - Replace `sseManager` with Redis pub/sub - Or fall back to Socket.io with Redis adapter ## Docker Deployment (Dokploy) - **Build Context**: `./` (monorepo root) - **Dockerfile**: `Dockerfile` (in root, not `apps/backend/`) - **Required args**: `DATABASE_URL`, `VITE_API_BASE_URL`, `BETTER_AUTH_SECRET`, `BETTER_AUTH_URL` - **bun.lock must be tracked**: It's in `.gitignore` by 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 core - `APP_BASE_URL` - Frontend URL for trusted origins - `CORS_ORIGIN` - Frontend URL for CORS policy - `GOOGLE_CLIENT_ID`, `GOOGLE_CLIENT_SECRET` - Google OAuth (see below) **Frontend** (`apps/frontend/.env`): - `VITE_*` prefix required (Vite embeds these at build time) - `VITE_API_BASE_URL` - Backend URL (default: http://localhost:3000) ## Google OAuth To enable login with Google: 1. Create OAuth credentials in Google Cloud Console: - Application type: Web application - Authorized redirect URI: `{BETTER_AUTH_URL}/api/auth/callback/google` 2. Add env vars to `.env.example`: ``` GOOGLE_CLIENT_ID=your-google-client-id GOOGLE_CLIENT_SECRET=your-google-client-secret ``` 3. The backend configures `socialProviders.google` automatically. 4. Frontend uses `authClient.signIn.social({ provider: 'google', callbackURL: 'http://localhost:5173' })`.