Files
playzer/AGENTS.md
2026-04-20 15:47:47 -03:00

5.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:

  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' }).

Complex Selection (Multi-tenancy)

The app supports users with multiple complexes. Each user has a role per complex (ADMIN or EMPLOYEE), stored in ComplexUser table.

Flow

  1. Login (password or Google OAuth):

    • After auth, call GET /api/complexes/mine to get user's complexes
    • If 1 complex → auto-select via POST /api/complexes/select → redirect to /
    • If >1 complexes → redirect to /select-complex
  2. Select Complex Page (/select-complex):

    • List all complexes with roles
    • User selects one → POST /api/complexes/select → set cookie → redirect to /
  3. Home Page (/):

    • If no cookie (first visit) → auto-select if 1 complex, else redirect
    • Use GET /api/complexes/me to get current selected complex
  • Name: selected-complex-id
  • httpOnly, secure (prod), sameSite: strict
  • MaxAge: 30 days
  • Set by POST /api/complexes/select

Endpoints

Method Endpoint Description
GET /api/complexes/mine List all complexes with role
GET /api/complexes/me Get currently selected complex
POST /api/complexes/select Select complex (sets cookie)

Profile Role

The profile page shows the user's role for the selected complex, not the global user role. This is fetched from ComplexUser table using the cookie value.