top-tran/README.md
2026-05-04 19:50:31 -03:00

13 KiB

Toptran

Aplicação full-stack composta por um backend REST em Node.js/Express com autenticação JWT e um aplicativo mobile em React Native/Expo, voltado para o gerenciamento de corridas de trabalhadores do transporte.


Estrutura do projeto

toptran/
├── backend/        → API REST (Node.js, Express, Prisma, PostgreSQL)
└── toptran-app/    → App mobile (React Native, Expo)

Backend

Tecnologias

Pacote Versão Função
Node.js LTS Runtime
Express 5 Framework HTTP
TypeScript 5 Tipagem
Prisma 7 ORM
PostgreSQL Banco de dados
bcryptjs Hash de senha
jsonwebtoken Access token (JWT)
zod Validação de input
@prisma/adapter-pg Driver nativo do Prisma 7

Arquitetura em camadas

src/
├── server.ts                        → Inicialização do Express e registro das rotas
├── lib/
│   └── prisma.ts                    → Singleton do PrismaClient com adapter PG
├── repositories/
│   ├── users.repository.ts          → Queries de usuários no Prisma
│   ├── tokens.repository.ts         → Queries de tokens no Prisma
│   ├── companies.repository.ts      → Queries de empresas no Prisma
│   └── rides.repository.ts          → Queries de corridas no Prisma
├── services/
│   ├── auth.service.ts              → Lógica de registro, login, logout e JWT
│   ├── users.service.ts             → Lógica de leitura, atualização e remoção de usuários
│   └── sync.service.ts              → Lógica de sincronização de empresas e corridas
├── controllers/
│   ├── auth.controller.ts           → Recebe requests e chama auth.service
│   ├── users.controller.ts          → Recebe requests e chama users.service
│   └── sync.controller.ts           → Recebe requests e chama sync.service
├── middlewares/
│   └── auth.middleware.ts           → Valida o Bearer token nas rotas protegidas
└── routes/
    ├── auth.routes.ts               → Rotas públicas de autenticação
    ├── users.routes.ts              → Rotas protegidas de usuário
    └── sync.routes.ts               → Rotas protegidas de sincronização

Fluxo de uma requisição:

Request → routes → middleware (se protegida) → controller → service → repository → Prisma → PostgreSQL

Modelos do banco de dados

model users {
  id           String   @id
  name         String
  email        String   @unique
  password     String
  profilePhoto String?  @default("")
  bio          String?  @default("")
  createdAt    DateTime @default(now())
  updatedAt    DateTime
  rides        rides[]
  tokens       tokens[]
}

model tokens {
  id        String    @id
  token     String    @unique
  type      TokenType
  userId    String
  expiresAt DateTime
  createdAt DateTime  @default(now())
  users     users     @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model companies {
  id          String    @id
  name        String
  cost_per_km Decimal   @db.Decimal(10, 2)
  notes       String?   @default("")
  createdAt   DateTime? @default(now()) @db.Timestamptz(6)
  updatedAt   DateTime? @default(now()) @db.Timestamptz(6)
}

model rides {
  id          String    @id
  user_id     String
  company     String
  km          Decimal   @db.Decimal(10, 2)
  cost_per_km Decimal   @db.Decimal(10, 2)
  total       Decimal   @db.Decimal(10, 2)
  ride_date   String
  synced      Int?      @default(0) @db.SmallInt
  createdAt   DateTime? @default(now()) @db.Timestamptz(6)
  updatedAt   DateTime? @default(now()) @db.Timestamptz(6)
  users       users     @relation(fields: [user_id], references: [id], onDelete: Cascade)
}

enum TokenType {
  REFRESH
}

Variáveis de ambiente

Crie o arquivo backend/.env com as variáveis abaixo:

DB_USER=postgres
DB_PASSWORD=postgres
DB_HOST=localhost
DB_PORT=5432
DB_NAME=toptran

DATABASE_URL="postgresql://postgres:postgres@localhost:5432/toptran"

PORT=4000
JWT_SECRET=sua_chave_secreta_aqui

JWT_SECRET é obrigatório em produção. O access token expira em 15 minutos e o refresh token em 7 dias.

Instalação e execução

cd backend

# Instalar dependências
npm install

# Gerar cliente Prisma
npx prisma generate

# Rodar migrations
npx prisma migrate deploy

# Iniciar em modo desenvolvimento
npm run dev

# Build de produção
npm run build
npm start

API Reference

Autenticação — /auth

Rotas públicas, não requerem token.

POST /auth/register

Cria uma nova conta de usuário.

// Request body
{
  "name": "João Silva",
  "email": "joao@email.com",
  "password": "minhasenha"
}

// Response 201
{
  "id": "uuid",
  "name": "João Silva",
  "email": "joao@email.com"
}

POST /auth/login

Autentica o usuário e retorna os tokens.

// Request body
{
  "email": "joao@email.com",
  "password": "minhasenha"
}

// Response 200
{
  "accessToken": "eyJ...",
  "refreshToken": "uuid"
}

POST /auth/logout

Revoga o refresh token.

// Request body
{
  "refreshToken": "uuid"
}

// Response 204 (sem body)

Usuários — /users

Rotas protegidas. Enviar o header Authorization: Bearer <accessToken>.

GET /users/me

Retorna os dados básicos do usuário autenticado.

// Response 200
{
  "id": "uuid",
  "name": "João Silva",
  "email": "joao@email.com",
  "createdAt": "2026-04-27T00:00:00.000Z"
}

GET /users/profile

Retorna o perfil completo do usuário, incluindo foto e bio.

// Response 200
{
  "id": "uuid",
  "name": "João Silva",
  "email": "joao@email.com",
  "profilePhoto": "base64_ou_url",
  "bio": "Motorista há 5 anos.",
  "createdAt": "2026-04-27T00:00:00.000Z"
}

PUT /users/me

Atualiza nome, e-mail e/ou senha. Todos os campos são opcionais.

// Request body
{
  "name": "João Atualizado",
  "email": "novo@email.com",
  "password": "novasenha"
}

// Response 200
{
  "id": "uuid",
  "name": "João Atualizado",
  "email": "novo@email.com"
}

PATCH /users/profile

Atualiza foto de perfil e/ou bio. Todos os campos são opcionais.

// Request body
{
  "profilePhoto": "base64_ou_url",
  "bio": "Nova bio aqui."
}

// Response 200
{
  "id": "uuid",
  "name": "João Silva",
  "profilePhoto": "base64_ou_url",
  "bio": "Nova bio aqui."
}

DELETE /users/me

Remove a conta do usuário autenticado.

// Response 204 (sem body)

Sincronização — /sync

Rotas protegidas. Enviar o header Authorization: Bearer <accessToken>.

GET /sync/companies

Retorna todas as empresas cadastradas no servidor.

// Response 200
{
  "success": true,
  "data": [
    { "id": "uuid", "name": "Empresa X", "cost_per_km": "2.50", "notes": "" }
  ]
}

POST /sync/companies

Sincroniza (upsert) uma lista de empresas no servidor.

// Request body
{
  "companies": [
    { "id": "uuid", "name": "Empresa X", "cost_per_km": 2.50, "notes": "" }
  ]
}

// Response 200
{
  "success": true,
  "message": "1 company/companies synced",
  "data": [...]
}

GET /sync/rides

Retorna todas as corridas cadastradas no servidor.

// Response 200
{
  "success": true,
  "data": [
    { "id": "uuid", "user_id": "uuid", "company": "Empresa X", "km": "12.5", "total": "31.25", ... }
  ]
}

POST /sync/rides

Sincroniza (upsert) uma lista de corridas no servidor.

// Request body
{
  "rides": [
    { "id": "uuid", "user_id": "uuid", "company": "Empresa X", "km": 12.5, "cost_per_km": 2.50, "total": 31.25, "ride_date": "2026-05-01" }
  ]
}

// Response 200
{
  "success": true,
  "message": "1 ride(s) synced",
  "data": [...]
}

Respostas de erro

Status Situação
400 Input inválido (falha na validação Zod)
401 Token ausente, inválido ou expirado
404 Recurso não encontrado
409 E-mail já cadastrado
500 Erro interno no servidor

Deploy com Docker / Podman

cd backend

# Build da imagem
podman build -t top-tran-backend .

# Subir com compose
podman-compose up -d

O container expõe a porta 3000 e o serviço é reiniciado automaticamente em caso de falha.


Mobile

Tecnologias

Pacote Versão Função
React Native 0.83.6 Framework mobile
Expo 55 Plataforma e toolchain
Expo Router 55 Navegação baseada em arquivos
Expo SQLite 55 Banco de dados local (offline-first)
Expo Secure Store 55 Armazenamento seguro de tokens
Expo Image Picker 55 Seleção de foto de perfil
Expo Print 55 Geração de PDF
Expo Sharing 55 Compartilhamento de arquivos
Axios 1 Cliente HTTP
TypeScript 5 Tipagem

Estrutura

src/
├── app/
│   ├── _layout.tsx          → Layout raiz: inicializa DB e provedor de auth
│   ├── index.tsx            → Tela de login
│   ├── signup.tsx           → Tela de cadastro
│   ├── home.tsx             → Tela inicial com resumo de corridas
│   ├── lancamento.tsx       → Lançamento de nova corrida
│   ├── historico.tsx        → Histórico de corridas
│   ├── empresas.tsx         → Gerenciamento de empresas (CRUD local)
│   ├── relatorio.tsx        → Relatório mensal com exportação em PDF
│   ├── perfil.tsx           → Edição de perfil (nome, foto, bio)
│   ├── sincronizar.tsx      → Sincronização com o servidor (timeline visual)
│   ├── cadastros.tsx        → Hub de cadastros
│   └── corrida.tsx          → Rota de corrida (redirect)
├── components/
│   ├── Button.tsx           → Botão reutilizável
│   ├── Header.tsx           → Cabeçalho de tela
│   ├── Input.tsx            → Campo de texto reutilizável
│   └── Select.tsx           → Seletor (Picker) reutilizável
├── constants/
│   └── theme.ts             → Cores, espaçamentos e border-radius globais
├── contexts/
│   └── AuthContext.tsx      → Estado de autenticação global (JWT + usuário)
├── server/
│   └── api.ts               → Instância do Axios configurada para o backend
├── services/
│   └── db.ts                → Serviço SQLite: tabelas, CRUD de corridas, empresas e configurações
└── utils/
    └── jwt.ts               → Utilitário para decode do JWT

Telas

Arquivo Rota Descrição
src/app/index.tsx / Login
src/app/signup.tsx /signup Cadastro de conta
src/app/home.tsx /home Dashboard com resumo do dia
src/app/lancamento.tsx /lancamento Lançar nova corrida
src/app/historico.tsx /historico Histórico completo de corridas
src/app/empresas.tsx /empresas CRUD de empresas locais
src/app/relatorio.tsx /relatorio Relatório mensal + exportar PDF
src/app/perfil.tsx /perfil Edição de nome, foto e bio
src/app/sincronizar.tsx /sincronizar Sincronização com o servidor
src/app/cadastros.tsx /cadastros Hub de cadastros

Banco de dados local (SQLite)

O app usa expo-sqlite para persistência offline. Ao iniciar, o _layout.tsx chama initDB() que cria as seguintes tabelas:

Tabela Descrição
users Dados do usuário logado e token de acesso
rides Corridas com flag synced (0 = pendente, 1 = sincronizado)
companies Empresas cadastradas localmente
settings Configurações chave-valor (ex: foto de perfil, bio)

Sincronização

A tela /sincronizar executa três etapas em sequência com feedback visual em timeline:

  1. Upload de empresas — envia empresas locais para o servidor via POST /sync/companies
  2. Download de empresas — baixa empresas do servidor se o cadastro local estiver vazio
  3. Upload de corridas — compara IDs locais com os do servidor e envia apenas as corridas ausentes via POST /sync/rides

Execução

cd toptran-app

# Instalar dependências
npm install

# Iniciar o servidor Expo
npm start

# Build e abrir no Android
npm run android

# Build e abrir no iOS
npm run ios

Conectar ao dispositivo Android via AVD na VM

  1. Ligue o dispositivo Android (AVD)
  2. Rodar o comando
    adb tcpip 5555
    
  3. No terminal local, crie o túnel SSH reverso:
    ssh -R 5555:localhost:5555 dev@175.15.15.93
    
  4. Conecte o ADB ao dispositivo tunelado:
    adb connect localhost:5555