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

5.8 KiB

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/)

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/)

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)

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/loginaccess_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.