diff --git a/README.md b/README.md index 5308a61..8cca035 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 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. +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. --- @@ -9,7 +9,7 @@ Aplicação full-stack composta por um backend REST em Node.js/Express com auten ``` toptran/ ├── backend/ → API REST (Node.js, Express, Prisma, PostgreSQL) -└── mobile/ → App mobile (React Native, Expo) +└── toptran-app/ → App mobile (React Native, Expo) ``` --- @@ -34,23 +34,28 @@ toptran/ ``` src/ -├── server.ts → Inicialização do Express e registro das rotas +├── server.ts → Inicialização do Express e registro das rotas ├── lib/ -│ └── prisma.ts → Singleton do PrismaClient com adapter PG +│ └── 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 +│ ├── 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 +│ ├── 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 +│ ├── 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 +│ └── 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 + ├── 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:** @@ -63,13 +68,16 @@ Request → routes → middleware (se protegida) → controller → service → ```prisma model users { - id String @id - name String - email String @unique - password String - createdAt DateTime @default(now()) - updatedAt DateTime - tokens tokens[] + 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 { @@ -82,6 +90,29 @@ model tokens { 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 } @@ -197,7 +228,7 @@ Revoga o refresh token. **GET `/users/me`** -Retorna os dados do usuário autenticado. +Retorna os dados básicos do usuário autenticado. ```json // Response 200 @@ -211,6 +242,24 @@ Retorna os dados do usuário autenticado. --- +**GET `/users/profile`** + +Retorna o perfil completo do usuário, incluindo foto e bio. + +```json +// 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. @@ -233,6 +282,28 @@ Atualiza nome, e-mail e/ou senha. Todos os campos são opcionais. --- +**PATCH `/users/profile`** + +Atualiza foto de perfil e/ou bio. Todos os campos são opcionais. + +```json +// 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. @@ -243,6 +314,86 @@ Remove a conta do usuário autenticado. --- +#### Sincronização — `/sync` + +> Rotas protegidas. Enviar o header `Authorization: Bearer `. + +**GET `/sync/companies`** + +Retorna todas as empresas cadastradas no servidor. + +```json +// 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. + +```json +// 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. + +```json +// 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. + +```json +// 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 | @@ -251,6 +402,7 @@ Remove a conta do usuário autenticado. | 401 | Token ausente, inválido ou expirado | | 404 | Recurso não encontrado | | 409 | E-mail já cadastrado | +| 500 | Erro interno no servidor | --- @@ -276,23 +428,89 @@ O container expõe a porta `3000` e o serviço é reiniciado automaticamente em | Pacote | Versão | Função | |---|---|---| -| React Native | 0.81.5 | Framework mobile | -| Expo | 54 | Plataforma e toolchain | -| Expo Router | 6 | Navegação baseada em arquivos | +| 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` | `/` | Tela de login | -| `src/app/signup.tsx` | `/signup` | Tela de cadastro | +| `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 ```bash -cd mobile +cd toptran-app # Instalar dependências npm install @@ -300,21 +518,25 @@ npm install # Iniciar o servidor Expo npm start -# Abrir no Android +# Build e abrir no Android npm run android -# Abrir no iOS +# Build e abrir no iOS npm run ios ``` ### Conectar ao dispositivo Android via AVD na VM 1. Ligue o dispositivo Android (AVD) -2. No terminal local, crie o túnel SSH reverso: +2. Rodar o comando + ```bash + adb tcpip 5555 + ``` +3. No terminal local, crie o túnel SSH reverso: ```bash ssh -R 5555:localhost:5555 dev@175.15.15.93 ``` -3. Conecte o ADB ao dispositivo tunelado: +4. Conecte o ADB ao dispositivo tunelado: ```bash adb connect localhost:5555 ``` \ No newline at end of file