Aplicação backend para cadastro/autenticação de usuários, CRUD de veículos e telemetria simulada em tempo real;
- Server: Node.js + TypeScript (Express, JWT,
ws, PostgreSQL viapg-promise) - Arquitetura: Clean Architecture (
domain/application/infra) - Docker: DB (scripts
yarn docker:start)
Este projeto usa Jest com TypeScript em ESM. A suíte está dividida entre unit e integration.
# todos os testes (unit + integration)
yarn test
# somente unitários
yarn test:unit
# somente integração
yarn test:integration
- Unitários: entidades (Account, Vehicle), use cases (Signup, Login, Vehicles), TelemetrySimulator
- Integração: fluxo signup → login → CRUD veículos → WS recebendo telemetry
test/
├─ unit/ # testes unitários
│ └─ **/*.test.ts
└─ integration/ # testes de integração
└─ **/*.test.ts
yarn
yarn docker:start
yarn devPORT=3001
DATABASE_URL=postgres://postgres:123456@localhost:5432/app
JWT_SECRET=uma_chave_segura
JWT_EXPIRES_IN=1h
TELEMETRY_ORIGIN_LAT=-8.05
TELEMETRY_ORIGIN_LNG=-34.90
TELEMETRY_DEBUG=trueCREATE SCHEMA IF NOT EXISTS mobs;
CREATE TABLE IF NOT EXISTS mobs.account (
account_id UUID PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL,
password TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS mobs.vehicle (
plate TEXT PRIMARY KEY,
model TEXT NOT NULL,
manufacturer TEXT NOT NULL,
year INTEGER NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT now(),
updated_at TIMESTAMP NOT NULL DEFAULT now()
);Todas as rotas exigem JWT no header:
Authorization: Bearer <token>
Request Body
{ "name": "John Doe", "email": "[email protected]", "password": "Strong#123" }Responses
- 200 ✅
{ "accountId": "uuid" }- 422 ❌
{ "error": "Invalid name | Invalid email | Invalid password" }Request Body
{ "email": "[email protected]", "password": "Strong#123" }Responses
- 200 ✅
{ "accessToken": "jwt" }- 422 ❌
{ "error": "Invalid credentials" }Retorna a lista de veículos cadastrados.
Response 200
[
{
"plate": "ABC-1234",
"model": "ModelName",
"manufacturer": "ManufacturerName",
"year": 2020
}
]Cria um novo veículo.
Request Body
{
"plate": "ABC-1234",
"model": "Corolla",
"manufacturer": "Toyota",
"year": 2020
}Responses
- 200 ✅
{ "plate": "ABC-1234" }- 422 ❌
{ "error": "Vehicle already exists | Invalid ..." }Busca detalhes de um veículo pela placa.
Responses
- 200 ✅
{ "plate": "ABC-1234", "model": "Corolla", "manufacturer": "Toyota", "year": 2020 }- 422 ❌
{ "error": "Vehicle not found" }Atualiza dados de um veículo (body pode ser parcial).
Request Body
{ "model": "Corolla XEi", "year": 2021 }Responses
- 200 ✅
{ "plate": "ABC-1234", "model": "Corolla XEi", "manufacturer": "Toyota", "year": 2021 }- 422 ❌
{ "error": "Vehicle not found | Invalid ..." }Remove um veículo.
Responses
- 200 ✅
{ "success": true }- 422 ❌
{ "error": "Vehicle not found" }GET /telemetry/:plate?limit=50 →
{
"plate": "ABC-1234",
"history": [
{ "lat": -8.05, "lng": -34.90, "speed": 60, "fuel": 78, "timestamp": 1724523456789 }
]
}URL: ws://<host>:<port>/ws?token=<JWT>
Token ausente/inválido → close code 1008 (Policy Violation).
- Assinar todas:
{ "type": "subscribe" } - Assinar uma:
{ "type": "subscribe", "plate": "ABC-1234" } - Cancelar uma:
{ "type": "unsubscribe", "plate": "ABC-1234" } - Cancelar todas:
{ "type": "unsubscribe" } - Pedir histórico:
{ "type": "history", "plate": "ABC-1234", "limit": 100 }
- Telemetria:
{ "type": "telemetry", "data": {...} } - Histórico:
{ "type": "telemetry-batch", "plate": "ABC-1234", "history": [ ... ] }
⏱️ Simulador: roda a cada 5000ms (configurável). Com TELEMETRY_DEBUG=true, logs [SIM] e [WS] aparecem no stdout.
MIT