Files
Luciano Milani 5de37bb512 Inicial
2026-07-02 12:47:55 +01:00

151 lines
5.8 KiB
Markdown

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## What this project is
**TechXCar** — a multi-tenant SaaS for car workshop management. Each workshop gets an isolated PostgreSQL schema. A super-admin manages the platform and provisions workshops via invite links or manual creation.
Design spec: `docs/superpowers/specs/2026-06-16-techxcar-design.md`
---
## Commands
### Backend (run from `backend/`)
```bash
go run ./cmd/server # start dev server (requires .env)
go build ./... # compile check
go test ./... # all tests (integration tests skip without env vars)
go test ./internal/server/... # single package
go test -run TestLoad_defaults ./internal/config/... # single test
```
Integration tests require:
- `TEST_DATABASE_URL=postgres://...`
- `TEST_REDIS_URL=redis://...`
### Frontend (run from `frontend/`)
```bash
npm run dev # localhost:5173 (proxies /api → localhost:8080)
npm run build # TypeScript check + Vite build
npm run test:run # Vitest single run
npm test # Vitest watch
npm run test:coverage
```
### Docker (run from root)
```bash
cp .env.example .env # first time only
docker compose up # dev: all 4 services
docker compose up backend # backend only (postgres + redis still needed)
docker compose -f docker-compose.prod.yml up # production stack
```
---
## Required env vars
| Var | Notes |
|---|---|
| `DATABASE_URL` | postgres connection string — required |
| `JWT_SECRET` | min 32 chars — required |
| `REDIS_URL` | defaults to `redis://localhost:6379` |
| `PORT` | defaults to `8080` |
| `APP_ENV` | defaults to `development` |
SMTP, Telegram, PDF storage, and billing data are **not** in `.env` — they live in `platform_settings` / `tenant_settings` tables, cached in Redis for 5 minutes, and managed through the Web UI settings panel.
---
## Architecture
### Multi-tenancy (critical to understand)
PostgreSQL uses **schema-per-tenant**:
- `public` schema: `tenants`, `invites`, `super_admins`, `platform_settings`
- `tenant_<uuid>` schema: all workshop data (users, clients, vehicles, work_orders, etc.)
Every API request sets `search_path = tenant_<uuid>, public` via `db.SetTenantSchema()` using the `tenant_id` claim from the JWT. The `tenantID` is validated against `^[a-zA-Z0-9_-]{1,63}$` before being interpolated into SQL (see `pkg/database/database.go`).
When a new tenant is provisioned, `db.ProvisionTenantSchema()` creates the schema and runs `migrations/tenant/` against it.
### Backend layout
```
backend/
├── cmd/server/main.go # entrypoint: loads config → DB → Redis → migrate → serve
├── internal/
│ ├── config/ # typed config from env (DATABASE_URL, JWT_SECRET required)
│ └── server/ # Fiber app, security middleware, route registration
├── pkg/
│ ├── database/ # pgxpool wrapper + MigratePublic + ProvisionTenantSchema
│ └── redis/ # go-redis wrapper
└── migrations/
├── public/ # applied at startup via MigratePublic()
└── tenant/ # applied per-tenant via ProvisionTenantSchema()
```
Future internal packages (per spec): `auth/`, `tenant/`, `workorder/`, `client/`, `catalog/`, `invoice/`, `staff/`, `expense/`, `notification/`, `report/`, `settings/`.
### API conventions
- Base path: `/api/v1/`
- Auth: `Authorization: Bearer <access_token>` header
- Response envelope: `{ "data": ..., "error": null, "meta": { "page": ..., "total": ... } }`
- Errors via the Fiber error handler in `internal/server/server.go`
### JWT flow
- `POST /api/v1/auth/login``access_token` in body (15 min) + `refresh_token` in httpOnly cookie (30 days)
- `POST /api/v1/auth/refresh` → new access token using the cookie
- Rate limiting on auth routes: 10 attempts/min per IP (Redis)
### Frontend layout (planned — frontend/ not yet scaffolded as of Plan 1)
```
frontend/src/
├── pages/
│ ├── auth/ # /login
│ ├── admin/ # /admin/* — super_admin only
│ └── app/ # /app/* — tenant roles
├── components/
│ ├── ui/ # shadcn/ui base components
│ └── shared/ # reusable app components
├── hooks/
├── lib/
│ ├── api.ts # fetch wrapper with auto token refresh on 401
│ └── queryClient.ts # TanStack Query config
├── store/ # Zustand stores (authStore persisted to localStorage)
└── i18n/ # pt-PT default; en structure prepared but not translated
```
Routes: `BrowserRouter`. Role guards in `App.tsx` redirect to `/login` for unauthorized roles.
### Async notifications
Telegram and email notifications are **not sent inline**. The HTTP handler publishes a job to a Redis list; a dedicated goroutine in `internal/notification/` consumes the queue and sends. This means HTTP responses are not blocked by notification delivery.
### Work order state machine
```
open → in_progress → completed → invoiced
cancelled (from any state except invoiced)
```
State transitions are logged to `wo_status_log` with timestamp and the user who triggered the change.
### PDF generation
Server-side in Go using `chromedp` (HTML template → Chrome headless → PDF). PDFs stored on a Docker volume (`/app/storage`) or S3-compatible storage (configurable in settings). Served via an authenticated endpoint, not from the webroot.
---
## Testing conventions
- Integration tests use `t.Skip()` when `TEST_DATABASE_URL` / `TEST_REDIS_URL` are absent — safe to run without infra.
- Backend uses `testify/assert` and `testify/require`.
- Frontend uses Vitest + Testing Library with jsdom.
- Test setup file: `frontend/src/test/setup.ts`.