Acerto app.json
This commit is contained in:
parent
745c17e52a
commit
c9e948b8dd
1 changed files with 253 additions and 31 deletions
284
README.md
284
README.md
|
|
@ -1,6 +1,6 @@
|
||||||
# Toptran
|
# 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/
|
toptran/
|
||||||
├── backend/ → API REST (Node.js, Express, Prisma, PostgreSQL)
|
├── 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/
|
src/
|
||||||
├── server.ts → Inicialização do Express e registro das rotas
|
├── server.ts → Inicialização do Express e registro das rotas
|
||||||
├── lib/
|
├── lib/
|
||||||
│ └── prisma.ts → Singleton do PrismaClient com adapter PG
|
│ └── prisma.ts → Singleton do PrismaClient com adapter PG
|
||||||
├── repositories/
|
├── repositories/
|
||||||
│ ├── users.repository.ts → Queries de usuários no Prisma
|
│ ├── users.repository.ts → Queries de usuários no Prisma
|
||||||
│ └── tokens.repository.ts → Queries de tokens 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/
|
├── services/
|
||||||
│ ├── auth.service.ts → Lógica de registro, login, logout e JWT
|
│ ├── 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
|
│ ├── 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/
|
├── controllers/
|
||||||
│ ├── auth.controller.ts → Recebe requests e chama auth.service
|
│ ├── auth.controller.ts → Recebe requests e chama auth.service
|
||||||
│ └── users.controller.ts → Recebe requests e chama users.service
|
│ ├── users.controller.ts → Recebe requests e chama users.service
|
||||||
|
│ └── sync.controller.ts → Recebe requests e chama sync.service
|
||||||
├── middlewares/
|
├── middlewares/
|
||||||
│ └── auth.middleware.ts → Valida o Bearer token nas rotas protegidas
|
│ └── auth.middleware.ts → Valida o Bearer token nas rotas protegidas
|
||||||
└── routes/
|
└── routes/
|
||||||
├── auth.routes.ts → Rotas públicas de autenticação
|
├── auth.routes.ts → Rotas públicas de autenticação
|
||||||
└── users.routes.ts → Rotas protegidas de usuário
|
├── users.routes.ts → Rotas protegidas de usuário
|
||||||
|
└── sync.routes.ts → Rotas protegidas de sincronização
|
||||||
```
|
```
|
||||||
|
|
||||||
**Fluxo de uma requisição:**
|
**Fluxo de uma requisição:**
|
||||||
|
|
@ -63,13 +68,16 @@ Request → routes → middleware (se protegida) → controller → service →
|
||||||
|
|
||||||
```prisma
|
```prisma
|
||||||
model users {
|
model users {
|
||||||
id String @id
|
id String @id
|
||||||
name String
|
name String
|
||||||
email String @unique
|
email String @unique
|
||||||
password String
|
password String
|
||||||
createdAt DateTime @default(now())
|
profilePhoto String? @default("")
|
||||||
updatedAt DateTime
|
bio String? @default("")
|
||||||
tokens tokens[]
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime
|
||||||
|
rides rides[]
|
||||||
|
tokens tokens[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model tokens {
|
model tokens {
|
||||||
|
|
@ -82,6 +90,29 @@ model tokens {
|
||||||
users users @relation(fields: [userId], references: [id], onDelete: Cascade)
|
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 {
|
enum TokenType {
|
||||||
REFRESH
|
REFRESH
|
||||||
}
|
}
|
||||||
|
|
@ -197,7 +228,7 @@ Revoga o refresh token.
|
||||||
|
|
||||||
**GET `/users/me`**
|
**GET `/users/me`**
|
||||||
|
|
||||||
Retorna os dados do usuário autenticado.
|
Retorna os dados básicos do usuário autenticado.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
// Response 200
|
// 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`**
|
**PUT `/users/me`**
|
||||||
|
|
||||||
Atualiza nome, e-mail e/ou senha. Todos os campos são opcionais.
|
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`**
|
**DELETE `/users/me`**
|
||||||
|
|
||||||
Remove a conta do usuário autenticado.
|
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 <accessToken>`.
|
||||||
|
|
||||||
|
**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
|
#### Respostas de erro
|
||||||
|
|
||||||
| Status | Situação |
|
| Status | Situação |
|
||||||
|
|
@ -251,6 +402,7 @@ Remove a conta do usuário autenticado.
|
||||||
| 401 | Token ausente, inválido ou expirado |
|
| 401 | Token ausente, inválido ou expirado |
|
||||||
| 404 | Recurso não encontrado |
|
| 404 | Recurso não encontrado |
|
||||||
| 409 | E-mail já cadastrado |
|
| 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 |
|
| Pacote | Versão | Função |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| React Native | 0.81.5 | Framework mobile |
|
| React Native | 0.83.6 | Framework mobile |
|
||||||
| Expo | 54 | Plataforma e toolchain |
|
| Expo | 55 | Plataforma e toolchain |
|
||||||
| Expo Router | 6 | Navegação baseada em arquivos |
|
| 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 |
|
| Axios | 1 | Cliente HTTP |
|
||||||
| TypeScript | 5 | Tipagem |
|
| 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
|
### Telas
|
||||||
|
|
||||||
| Arquivo | Rota | Descrição |
|
| Arquivo | Rota | Descrição |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `src/app/index.tsx` | `/` | Tela de login |
|
| `src/app/index.tsx` | `/` | Login |
|
||||||
| `src/app/signup.tsx` | `/signup` | Tela de cadastro |
|
| `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
|
### Execução
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd mobile
|
cd toptran-app
|
||||||
|
|
||||||
# Instalar dependências
|
# Instalar dependências
|
||||||
npm install
|
npm install
|
||||||
|
|
@ -300,21 +518,25 @@ npm install
|
||||||
# Iniciar o servidor Expo
|
# Iniciar o servidor Expo
|
||||||
npm start
|
npm start
|
||||||
|
|
||||||
# Abrir no Android
|
# Build e abrir no Android
|
||||||
npm run android
|
npm run android
|
||||||
|
|
||||||
# Abrir no iOS
|
# Build e abrir no iOS
|
||||||
npm run ios
|
npm run ios
|
||||||
```
|
```
|
||||||
|
|
||||||
### Conectar ao dispositivo Android via AVD na VM
|
### Conectar ao dispositivo Android via AVD na VM
|
||||||
|
|
||||||
1. Ligue o dispositivo Android (AVD)
|
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
|
```bash
|
||||||
ssh -R 5555:localhost:5555 dev@175.15.15.93
|
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
|
```bash
|
||||||
adb connect localhost:5555
|
adb connect localhost:5555
|
||||||
```
|
```
|
||||||
Loading…
Add table
Reference in a new issue