95 lines
3.6 KiB
Markdown
95 lines
3.6 KiB
Markdown
# 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
|
|
|
|
**Frontend** (`apps/frontend/.env`):
|
|
- `VITE_*` prefix required (Vite embeds these at build time)
|
|
- `VITE_API_BASE_URL` - Backend URL (default: http://localhost:3000)
|