151 lines
5.8 KiB
Markdown
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`.
|