Files
techxcar/readme-app.md
T
Luciano Milani 5de37bb512 Inicial
2026-07-02 12:47:55 +01:00

16 KiB

TechXCar — Análise do Repositório

Data: 02/07/2026 Branch: feat/plan2-auth-multitenancy


📋 Visão Geral

TechXCar é um SaaS multi-tenant de gestão de oficina automóvel, construído com Go (Fiber) no backend e React 19 + TypeScript + Vite no frontend. Cada oficina (tenant) tem um schema PostgreSQL isolado (tenant_<uuid>).


🏗 Stack Tecnológica

Camada Tecnologia
Frontend React 19, TypeScript 6, Vite 8, Tailwind CSS 4, React Router 7, TanStack Query 5, Zustand 5, React Hook Form + Zod
Backend Go 1.25, Fiber v2, pgx v5, golang-jwt, golang-migrate, bcrypt, go-redis
Base de Dados PostgreSQL 16 (multi-tenancy via schema-per-tenant)
Cache Redis 7
PDF go-pdf/fpdf (server-side, nativo — sem Chrome headless)
Infra Docker Compose, Nginx, Coolify

📁 Estrutura do Projeto

techxcar/
├── frontend/                    # React 19 + Vite
│   ├── src/
│   │   ├── components/
│   │   │   ├── ui/              # shadcn-style (button, input, label, badge, dialog)
│   │   │   └── layout/          # AppLayout, AdminLayout
│   │   ├── hooks/               # useAuth, useClients
│   │   ├── lib/                 # api.ts, queryClient.ts, types.ts, utils.ts
│   │   ├── pages/
│   │   │   ├── auth/            # LoginPage
│   │   │   ├── admin/           # DashboardPage, TenantsPage
│   │   │   ├── app/             # 10 páginas de gestão da oficina
│   │   │   └── public/          # InviteRedeemPage
│   │   ├── store/               # authStore (Zustand + persist)
│   │   └── test/                # setup Vitest
│   ├── public/                  # favicon, icons
│   ├── Dockerfile               # Nginx a servir build Vite
│   └── nginx.conf
├── backend/                     # Go + Fiber
│   ├── cmd/server/main.go       # Entrypoint
│   ├── internal/
│   │   ├── auth/                # (10 ficheiros) JWT, bcrypt, middleware, rate-limit, routes
│   │   ├── tenant/              # (6 ficheiros) multi-tenant, invites, provisioning, access
│   │   ├── client/              # (4 ficheiros) clientes + veículos CRUD
│   │   ├── catalog/             # (4 ficheiros) catálogo de itens CRUD
│   │   ├── workorder/           # (4 ficheiros) OT CRUD + state machine + items + staff hours
│   │   ├── staff/               # (4 ficheiros) técnicos CRUD
│   │   ├── expense/             # (4 ficheiros) despesas CRUD
│   │   ├── invoice/             # (4 ficheiros) faturação + geração PDF
│   │   ├── settings/            # (4 ficheiros) definições da oficina
│   │   ├── server/              # (2 ficheiros) Fiber app, health routes, error handler
│   │   └── config/              # (2 ficheiros) env config (DATABASE_URL, JWT_SECRET, etc.)
│   ├── pkg/
│   │   ├── database/            # pgxpool wrapper, migrations (public + tenant)
│   │   ├── redis/               # go-redis wrapper
│   │   └── pdf/                 # go-pdf/fpdf generation
│   ├── migrations/
│   │   ├── public/              # 2 migrações (tenants, invites, super_admins, platform_settings)
│   │   └── tenant/              # 2 migrações (11 tabelas por tenant)
│   ├── Dockerfile               # Multi-stage Go build
│   ├── go.mod / go.sum
│   └── Makefile (via CLAUDE.md)
├── docker-compose.yml           # Dev: postgres + redis + backend + frontend
├── docker-compose.prod.yml      # Produção (Coolify)
├── .env.example                 # Template de env vars
└── docs/
    └── superpowers/
        ├── specs/               # Design specs (arquitetura, multi-tenancy, impersonação)
        └── plans/               # Planos de implementação (6 planos)

🔐 Autenticação & Autorização

Mecanismo

  • JWT com access token (15 min) + refresh token (30 dias, httpOnly cookie)
  • Refresh rotativo: armazenado em Redis (refresh:<userID>) — cada refresh gera novo token
  • Rate limiting: 10 tentativas/min/IP nas rotas de auth (via Redis)

Roles

Role Acesso
super_admin Painel /admin, gestão de tenants, impersonação
tenant_admin Acesso total à oficina
manager Acesso operacional (sem settings financeiras)
technician Acesso às OTs atribuídas

Fluxo de Login

POST /api/v1/auth/login { email, password, tenant_slug? }
  1. Se tenant_slug vazio → autentica como super_admin (tabela public.super_admins)
  2. Se tenant_slug preenchido → procura tenant → procura user no schema do tenant
  3. Devolve access_token (body) + refresh_token (httpOnly cookie)

Middleware Stack

RequireAuth(secret) → valida JWT, extrai claims
RequireRole("tenant_admin", "manager") → verifica role
TenantMiddleware(db) → adquire conexão, SET search_path = tenant_<uuid>, public

🏢 Multi-tenancy (Schema-per-tenant)

Estrutura

  • Schema public: tenants, invites, super_admins, platform_settings
  • Schema tenant_<uuid> (por oficina): todas as tabelas de negócio

Provisionamento

  1. Super-admin cria tenant (INSERT INTO public.tenants)
  2. ProvisionTenantSchema() cria schema e corre migrations de tenant
  3. Cria user admin no schema do tenant

Isolamento

  • TenantMiddleware adquire conexão pgxpool dedicada por request
  • Define SET search_path = "tenant_<uuid>", public
  • Bug corrigido: schema name usa underscores em vez de hífens
  • validTenantID regex: ^[a-zA-Z0-9_-]{1,63}$

Impersonação (Super-admin → Tenant)

  1. POST /api/v1/admin/tenants/:id/access → gera token JWT com role="tenant_admin" + tenantID
  2. Frontend guarda sessão anterior em previousSession (não persistido)
  3. Navega para /app com banner amarelo: "TechXCar Admin — a gerir: "
  4. "Voltar" restaura sessão anterior (restoreSession())

🧩 Módulos de Negócio

1. Auth (internal/auth/)

Ficheiro Descrição
handler.go Login, refresh, logout handlers
jwt.go Geração/validação de tokens (access + refresh)
bcrypt.go Hash + verify com cost 12
middleware.go RequireAuth, RequireRole, TenantMiddleware
ratelimit.go Rate limiter com Redis storage
routes.go Registo de rotas /api/v1/auth/*

2. Tenant / Invites (internal/tenant/)

Ficheiro Descrição
handler.go CRUD tenants, invites, redeem, access
repository.go Queries: super_admins, tenants, users, invites
routes.go Registo de rotas + loginAdapter

Endpoints:

  • GET /api/v1/admin/tenants — listar (super_admin)
  • POST /api/v1/admin/tenants — criar + provisionar schema
  • POST /api/v1/admin/tenants/:id/invite — gerar convite para tenant
  • POST /api/v1/admin/tenants/:id/access — impersonar tenant
  • POST /api/v1/admin/invites — gerar convite global
  • GET /api/v1/invites/:token — consultar convite (público)
  • POST /api/v1/invites/:token/redeem — resgatar convite + criar conta

3. Work Orders (internal/workorder/)

Máquina de estados:

open → in_progress → completed → invoiced
                  ↓
              cancelled  (de qualquer estado excepto invoiced)

Endpoints:

  • GET /api/v1/work-orders — listar (com filtro ?status=)
  • POST /api/v1/work-orders — criar
  • GET /api/v1/work-orders/:id — detalhe (com items + staff_hours)
  • PUT /api/v1/work-orders/:id — actualizar
  • POST /api/v1/work-orders/:id/transition — transição de estado
  • POST /api/v1/work-orders/:id/items — adicionar item
  • DELETE /api/v1/work-orders/:id/items/:itemId — remover item
  • POST /api/v1/work-orders/:id/staff-hours — adicionar horas técnico
  • DELETE /api/v1/work-orders/:id/staff-hours/:shId — remover horas

Destaques:

  • wo_status_log regista cada transição (from, to, changed_by, timestamp)
  • Totais calculados via GENERATED ALWAYS AS no PostgreSQL

4. Clientes & Veículos (internal/client/)

  • CRUD clientes + veículos
  • Veículos aninhados a clientes (opcional)
  • Veículo pode ser criado sem cliente

5. Catálogo (internal/catalog/)

  • Items com código, nome, categoria, unidade (un, hora, litro, kg)
  • Preço base + activo/inactivo
  • Preço copiado para OT no momento de adição (editável)

6. Staff (internal/staff/)

  • Interno (com conta no sistema) / Externo (registo simplificado)
  • Custo/hora para cálculo de mão de obra

7. Despesas (internal/expense/)

  • Tipos: fuel, parts, tools, other
  • Filtro por tipo, ordenado por data

8. Definições (internal/settings/)

Chave Descrição
company_name Nome da oficina
company_nif NIF
company_address Morada
company_iban IBAN
company_phone Telefone
company_email Email

Apenas tenant_admin pode escrever; manager pode ler.

9. Faturação (internal/invoice/)

  • Criação de orçamentos (quote) e faturas (invoice)
  • Geração PDF com go-pdf/fpdf (dados da oficina + cliente + veículo + items + staff hours)
  • Ao faturar, OT transita automaticamente para invoiced
  • PDFs servidos via endpoint autenticado

10. Health (internal/server/health.go)

GET /api/v1/health → { "status": "ok", "version": "0.1.0" }

🖥 Frontend

Páginas Implementadas

Rota Página Função
/login LoginPage Autenticação (com/sem slug)
/invite/:token InviteRedeemPage Resgatar convite (criar oficina)
/admin AdminDashboardPage Placeholder
/admin/tenants TenantsPage Listar/criar tenants, gerar convites, Gerir (impersonar)
/app DashboardPage Placeholder
/app/clients ClientsPage CRUD clientes + veículos inline
/app/catalog CatalogPage CRUD catálogo com categorias
/app/work-orders WorkOrdersPage Listar OTs com filtro de estado
/app/work-orders/:id WorkOrderDetailPage Detalhe OT: items, staff_hours, transições
/app/staff StaffPage CRUD técnicos
/app/expenses ExpensesPage CRUD despesas com filtro
/app/invoices InvoicesPage Listar faturas, gerar novo documento, download PDF
/app/settings SettingsPage Definições da oficina (formulário)

UI/UX

  • Tema escuro: bg-slate-950, texto branco, sidebar slate-900
  • Componentes shadcn-style: Button, Input, Label, Badge, Dialog
  • Formulários: React Hook Form + Zod (validação client-side)
  • Cache: TanStack Query (staleTime 5 min, retry apenas em erros 500+)
  • Auto-refresh: apiFetch tenta refresh em 401, limpa sessão se falhar

Store (Zustand)

authStore (persistida em localStorage):
  - user, accessToken, isAuthenticated
  - previousSession (NÃO persistido — apenas para impersonação)
  - setAuth, clearAuth, updateToken
  - impersonateTenant, restoreSession

Layouts

AppLayout (área da oficina):

  • Sidebar com navegação: Dashboard, OT, Clientes, Catálogo, Técnicos, Despesas, Faturação, Definições
  • Banner de impersonação (amarelo) quando super-admin gere tenant
  • Botão "Terminar sessão"

AdminLayout (área super-admin):

  • Sidebar simplificada: Dashboard, Oficinas
  • Botão "Terminar sessão"

Protecção: RequireAuth wrapper verifica roles + redirect para /login se não autenticado


🗄 Base de Dados

Migrations Public (2)

Migração Descrição
000001 uuid-ossp, tenants, invites, super_admins, platform_settings + índices
000002 invites.tenant_id → nullable + índice condicional

Migrations Tenant (2)

Migração Descrição
000001 11 tabelas: users, clients, vehicles, staff, catalog_items, work_orders, wo_items, wo_staff_hours, wo_status_log, invoices, expenses, tenant_settings
000002 Adiciona fuel_type a vehicles

Destaques do Schema

  • Colunas geradas: wo_items.total = qty * unit_price * (1 - discount_pct / 100), wo_staff_hours.total = hours * cost_per_hour
  • Serial unique: work_orders.number e invoices.number (únicos por schema tenant)
  • ON DELETE: SET NULL para FKs opcionais, CASCADE para dependentes, RESTRICT para invoices
  • Índices: status, client_id, plate, date

🧪 Testes

Backend (Go + testify)

Pacote Testes
auth JWT gen/validation (access + refresh), bcrypt (hash + verify), middleware (auth, role), login handler (stub repo)
tenant CRUD super_admin, CRUD tenant, invites (create + use), access handler (success, not found, inactive)
workorder CRUD OT, transições de estado, validação de transições (11 casos)
config Parse de env vars
database Pool creation
server Health endpoint
redis Conexão

Integração: testes com TEST_DATABASE_URL / TEST_REDIS_URL usam t.Skip() quando não definidas.

Frontend (Vitest + Testing Library)

  • authStore.test.ts
  • useClients.test.ts
  • Setup: src/test/setup.ts

🐳 Docker

docker-compose.yml (Dev)

services:
  postgres:   # 16-alpine, healthcheck, porta 5432
  redis:      # 7-alpine, appendonly, healthcheck, porta 6379
  backend:    # Go build, porta 8080, depende de postgres+redis
  frontend:   # Nginx build Vite, porta 3000, depende de backend

volumes: postgres_data, redis_data, pdf_storage

docker-compose.prod.yml

Produção para Coolify com HTTPS via Let's Encrypt.

Dockerfiles

  • Backend: Multi-stage (builder Go → distroless/debug)
  • Frontend: Build Vite → Nginx (porta 8080 no container)

🔍 Observações Técnicas

Pontos Fortes

  • Arquitetura multi-tenant sólida: schema isolation com validação de inputs e search_path dinâmico
  • Máquina de estados robusta: transições validadas, log de todas as mudanças
  • Auto-refresh de token: renovação silenciosa sem perder sessão
  • Impersonação bem desenhada: session restore puramente client-side, sem persistência
  • PDF nativo: go-pdf/fpdf em vez de chromedp (menos dependências, mais rápido)
  • Colunas GENERATED: totais computados pelo PostgreSQL (consistência garantida)
  • Cobertura de testes: handler tests com stub repo, integração opcional

Problemas / Risco

  1. Acoplamento entre packages: internal/invoice/handler.go importa internal/workorder para GetWorkOrderDetail e TransitionStatus — risco de dependência circular ao crescer
  2. PDF path hardcoded: storageRoot = "/app/storage" — spec diz configurável (S3-compatible)
  3. Sem paginação: listagens (GET /clients, etc.) sem limit/offset — problemático com muitos registos
  4. Go 1.25.0: versão futura (não lançada oficialmente) — pode não compilar em todos os ambientes
  5. Vite 8: bleeding edge — possível instabilidade ou breaking changes
  6. Dashboard placeholders: admin e app Dashboard sem métricas reais
  7. fuel_type hardcoded: frontend tem lista fixa em vez de vir do backend/catálogo
  8. Sem notificações: módulo notification/ (Telegram + SMTP) não implementado
  9. Sem relatórios: módulo report/ não implementado
  10. i18n: apenas estrutura preparada, só Português de Portugal

Estado Actual

Implementado

  • Autenticação (login, refresh, logout, rate-limit)
  • Multi-tenancy (schema isolation + provisioning)
  • Convites (redeem flow completo com criação de tenant + schema + admin)
  • Impersonação super-admin (acesso a tenants com session restore)
  • Clientes + Veículos (CRUD, inline por cliente)
  • Catálogo de itens (CRUD com categorias e unidades)
  • Ordens de Trabalho (CRUD + state machine + items + staff hours + status log)
  • Técnicos (CRUD, internos/externos, custo/hora)
  • Despesas (CRUD por tipo)
  • Definições da oficina (6 chaves)
  • Faturação/Orçamentos (geração PDF com go-pdf/fpdf)
  • Frontend completo (11 páginas, tema escuro, navegação sidebar)

Não Implementado

  • Notificações (Telegram + SMTP)
  • Relatórios com métricas reais
  • i18n completo (apenas PT)
  • Paginação em listagens
  • Upload de logo para PDF
  • Armazenamento S3 para PDFs