Files
Felipe 34cbd4a861 feat: implement Google OAuth support for Android and iOS
- Added support for Google OAuth with separate client IDs for Android and iOS.
- Updated `verify_google_id_token` to validate `aud` against both client IDs and check `email_verified`.
- Modified `google_oauth_handler` to accept and process the new client IDs.
- Enhanced security by enforcing explicit JWT algorithm validation.
- Updated mobile app to handle Google OAuth flow using `expo-auth-session`.
- Fixed API request to send `id_token` in snake_case as expected by the backend.
- Added necessary environment variables for Google client IDs in mobile app.
- Implemented intent filter for Google OAuth redirect in AndroidManifest.xml.
2026-03-19 15:24:08 -03:00

26 KiB
Raw Permalink Blame History

MeowSpool — Documento de Referência do Projeto

Canvas de Design: https://app.paper.design/file/01KKM0HA6NBV44VCQDFQHC2MRY


1. Visão Geral

MeowSpool é uma solução para entusiastas e profissionais de impressão 3D gerenciarem seu estoque de filamentos. O diferencial está na precisão do cálculo de peso líquido (descontando o peso do carretel via presets), na identidade visual inspirada em gatos siameses, e na integração com etiquetas físicas exportáveis (SVG/PDF/QR Code/NFC).

Foco do desenvolvimento atual: App Mobile (React Native + Expo) + Backend em Rust. A interface Web é planejada para uma fase posterior, após a estabilização do mobile e da API.


2. Objetivos

  • Precisão: Eliminar o "chute" de quanto filamento resta no rolo.
  • Agilidade: Facilitar o cadastro e a consulta via dispositivos móveis, mesmo offline.
  • Organização: Centralizar parâmetros técnicos (temperatura/fluxo) para consulta rápida.

3. Público-Alvo

  • Hobbistas de impressão 3D.
  • Donos de "Print Farms" (fazendas de impressão).
  • Projetistas que trabalham com diversos materiais (PLA, ABS, PETG, etc).

4. Identidade Visual

4.1 Nome e Conceito

MeowSpool — fusão de "Meow" (gato) e "Spool" (carretel). A identidade visual é inspirada no gato siamês: tons quentes e escuros com detalhes em azul-aço que remetem aos olhos característicos da raça.

App é dark mode exclusivo — nenhum suporte a light mode.

4.2 Paleta de Cores

Token Hex Função na Interface
bgBase #1E1B18 Fundo geral
bgSurface #2A2622 Cards, contêineres, widgets
bgHover #332F2B Estados de hover em cards e itens de lista
textPrimary #F5EEDC Títulos, peso líquido (texto importante)
textSecondary #C9C1B0 Descrições, labels (material, marca)
accent #38BCC2 Botões de ação, elementos ativos
accentMuted #38BCC226 Backgrounds de badges e status (10% opacidade)
stockLow #FF6B6B ≤ 15% — vermelho
stockMedium #FF9F43 ≤ 35% — laranja
stockOk #38BCC2 > 35% — accent
border #3D3830 Bordas sutis
error #FF6B6B Mensagens de erro

4.3 Tipografia

  • UI geral: Inter
  • Slugs, hex, URLs, código: JetBrains Mono

5. Requisitos Funcionais (MVP)

5.1 Autenticação

  • Métodos suportados: Email/senha e OAuth via Google.
  • Verificação de e-mail obrigatória antes do primeiro login.
  • Sessão: Token JWT com refresh token. O app mobile persiste a sessão localmente para acesso offline.
  • Fluxos: Login, Cadastro, Verificar E-mail, Reenviar Verificação, Esqueci a Senha, Redefinir Senha.

5.2 Gestão de Inventário

  • Cadastro de Filamento: Material, Marca, Modelo, Cor (hex), Temperatura Hotend, Temperatura Mesa, Fator de Fluxo/Extrusão, Notas.
  • Calculadora de Peso Líquido: Peso_Líquido = Peso_Total - Peso_Carretel (via preset selecionado).
  • Filtros: Material, marca, busca textual, nível de estoque (low/medium/ok).
  • Ordenação: Peso líquido asc/desc, criado em desc (padrão).

5.3 Presets de Carretéis

Presets do Sistema (built-in):

Nome do Preset Peso do Carretel
Bambu Lab (Plástico) 250g
Elegoo (Papelão) 200g
Creality (Plástico) 230g
Prusament (Plástico) 201g
Sunlu (Papelão) 200g
Polymaker (Plástico) 220g
Genérico Papelão 1kg 200g
Genérico Plástico 1kg 250g

Presets Customizados: O usuário pode criar, editar e excluir seus próprios presets. Presets do sistema são somente leitura.

5.4 Identificação e Etiquetas

  • QR Code: Cada filamento gera QR Code com deep link meowspool://filament/<id>.
  • Etiqueta SVG: GET /filaments/:id/label.svg — parâmetros width_mm, height_mm, fields, dpi.
  • Etiqueta PDF: GET /filaments/:id/label.pdf — mesmos parâmetros.
  • Impressoras suportadas: Padrão (50×30mm), Niimbot D11/D110 (22×14mm, dpi=203), Brother/Dymo (dpi=300).
  • Campos selecionáveis: color, name, material_brand, net_weight, print_temp, qrcode.
  • NFC: Leitura e gravação de tags NTAG215 com URI meowspool://filament/<id>.

5.5 Dashboard

  • Total de estoque em kg.
  • Contagem de filamentos com estoque baixo.
  • Resumo por material (contagem + peso total).
  • Filamentos com estoque baixo (≤15%).
  • Filamentos adicionados recentemente.

6. Arquitetura do Sistema

Ambos os lados seguem Arquitetura Hexagonal (Ports & Adapters):

adapters/inbound (HTTP / Tela)
        |
        v
  application (Use Cases / Services)
        |
        v
    domain (Entidades puras)
        |
        ^
    ports (Interfaces/traits)
        |
        ^
adapters/outbound (PostgreSQL / SQLite / NFC / HTTP)

Regra fundamental: O domain e os ports nunca importam de adapters ou application.


7. Backend (Rust)

7.1 Stack

Componente Biblioteca
HTTP Framework axum 0.7
Async Runtime tokio (full)
ORM / Query sqlx (postgres, uuid, time, macros)
Autenticação JWT jsonwebtoken 9.x
Hash de senha argon2 0.5 (Argon2id)
OAuth Google oauth2 4.x
Serialização serde, serde_json
Erros thiserror
Env vars dotenvy
Logging tracing, tracing-subscriber
Validação validator
HTTP Client reqwest
Email (SMTP) lettre (STARTTLS)
Geração QR Code qrcode + image
Geração PDF printpdf 0.7

7.2 Estrutura de Pastas

backend/
├── Cargo.toml
├── migrations/
│   ├── 20240101000001_create_users.sql
│   ├── 20240101000002_create_spool_presets.sql
│   ├── 20240101000003_create_filaments.sql
│   └── 20240101000004_create_token_tables.sql   ← password_reset_tokens + email_verification_tokens
└── src/
    ├── main.rs                   ← entry point: config, DB, router, servidor
    ├── config.rs                 ← struct Config lida de variáveis de ambiente
    ├── error.rs                  ← AppError unificado com IntoResponse
    ├── router.rs                 ← composição de todas as rotas Axum
    ├── domain/                   ← entidades puras (User, Filament, SpoolPreset)
    ├── ports/                    ← traits: UserRepository, FilamentRepository, SpoolPresetRepository
    ├── application/
    │   ├── auth_service.rs       ← login, register, OAuth, refresh, logout
    │   ├── email_token_service.rs← forgot_password, verify_email, reset_password
    │   ├── filament_service.rs   ← CRUD, peso líquido, QR Code, SVG, PDF
    │   └── spool_preset_service.rs
    ├── infrastructure/
    │   └── email_service.rs      ← SMTP via lettre
    └── adapters/
        ├── inbound/              ← handlers Axum + middleware JWT
        └── outbound/             ← implementações PostgreSQL dos ports

7.3 Rotas da API

Base: /api/v1

Auth — /api/v1/auth

Método Rota Acesso
POST /register Público — retorna 201 sem token
POST /login Público
POST /oauth/google Público
POST /refresh Público (requer refresh token)
POST /logout Autenticado
POST /resend-verification Público — sempre retorna 200
POST /forgot-password Público
POST /verify-email Público
POST /reset-password Público (requer token)

Redirects para Deep Links

Método Rota Descrição
GET /verify-email Redireciona 302 → {APP_SCHEME}://verify-email?token=xxx
GET /reset-password Redireciona 302 → {APP_SCHEME}://reset-password?token=xxx

Filaments — /api/v1/filaments

Método Rota Acesso
GET / Autenticado
POST / Autenticado
GET /:id Autenticado
PUT /:id Autenticado
DELETE /:id Autenticado
GET /:id/qrcode Autenticado
GET /:id/label.svg Autenticado
GET /:id/label.pdf Autenticado

Spool Presets — /api/v1/spool-presets

Método Rota Acesso
GET / Autenticado
POST / Autenticado
PUT /:id Autenticado (apenas presets do user)
DELETE /:id Autenticado (apenas presets do user)

Outros

Método Rota Acesso
GET /dashboard Autenticado
GET /users/me Autenticado
PUT /users/me Autenticado

7.4 Padrão de Erros

{ "error": "not found", "code": "NOT_FOUND" }
Variante AppError HTTP Code
NotFound 404 NOT_FOUND
Unauthorized 401 UNAUTHORIZED
Forbidden 403 FORBIDDEN
EmailNotVerified 403 EMAIL_NOT_VERIFIED
Conflict 409 CONFLICT
BadRequest 400 BAD_REQUEST
Validation 422 VALIDATION_ERROR
Internal 500 INTERNAL_ERROR

7.5 Variáveis de Ambiente

Variável Obrigatória Padrão
DATABASE_URL
JWT_SECRET
JWT_EXPIRY_SECS 3600
JWT_REFRESH_EXPIRY_SECS 2592000
GOOGLE_CLIENT_ID
GOOGLE_CLIENT_SECRET
HOST / PORT 0.0.0.0 / 8080
SMTP_HOST / SMTP_PORT smtp.gmail.com / 587
SMTP_USER / SMTP_PASS
EMAIL_FROM noreply@meowspool.app
APP_BASE_URL http://localhost:8080
APP_SCHEME meowspool

Em produção: APP_BASE_URL=https://meowspool.felipecncloud.com/api/v1

7.6 Como Rodar Localmente

cd backend/
cp .env.example .env

# PostgreSQL via Docker
docker run -d --name meowspool-db \
  -e POSTGRES_USER=meowspool \
  -e POSTGRES_PASSWORD=meowspool \
  -e POSTGRES_DB=meowspool \
  -p 5432:5432 postgres:16-alpine

sqlx migrate run
cargo watch -x run

8. Mobile (React Native + Expo)

8.1 Stack

Componente Biblioteca
Framework expo ~54 (Bare Workflow)
Navigation expo-router ~4 (file-system)
Linguagem TypeScript 5.x (strict)
Node Version 22 LTS (via mise)
Java Version 17 (obrigatório — Java 24 quebra CMake)
Forms react-hook-form + zod
Estado global zustand
HTTP axios (interceptor Bearer + refresh)
DB local expo-sqlite + SQLCipher
Auth persistência expo-secure-store
UI React Native + Ionicons
QR Code render react-native-qrcode-svg
SVG inline react-native-svg (SvgXml)
Câmera / Scanner expo-camera ~17 (ML Kit)
NFC react-native-nfc-manager
File system expo-file-system/legacy
Compartilhamento expo-sharing (iOS)
Abrir arquivo expo-intent-launcher (Android)
Animações react-native-reanimated

Bare Workflow: o diretório android/ é comitado. Qualquer pacote com módulo nativo exige npx expo prebuild --platform android + rebuild do APK.

8.2 Estrutura de Pastas

mobile/
├── .mise.toml                          ← node 22 LTS, java 17
├── app.json                            ← Expo config, scheme "meowspool"
├── app/                                ← expo-router (file-system routes)
│   ├── _layout.tsx                     ← Root layout (auth guard + sessão)
│   ├── (auth)/
│   │   ├── login.tsx                   ← G3-0
│   │   ├── register.tsx                ← IX-0
│   │   ├── forgot-password.tsx         ← L2-0
│   │   ├── check-email.tsx             ← RU-0 (pós-cadastro / e-mail não verificado)
│   │   ├── reset-password.tsx          ← 117-0
│   │   └── password-reset-done.tsx     ← 135-0
│   ├── filament/[id].tsx               ← Deep link: meowspool://filament/<id> → /(app)/inventory/<id>
│   ├── verify-email.tsx                ← Deep link: meowspool://verify-email?token=xxx
│   └── (app)/
│       ├── (tabs)/
│       │   ├── home.tsx                ← 1-0   Dashboard
│       │   ├── inventory.tsx           ← CW-0  Inventário
│       │   ├── add.tsx                 ← FAB → /inventory/new
│       │   ├── config.tsx              ← M4-0  Presets de Carretéis
│       │   └── profile.tsx             ← 1LQ-0 Perfil
│       ├── inventory/
│       │   ├── new.tsx                 ← 2X-0  Cadastro
│       │   ├── filters.tsx             ← 17H-0 Filtros (bottom sheet)
│       │   ├── [id].tsx                ← 6L-0  Detalhe
│       │   └── [id]/edit.tsx           ← 13O-0 Editar
│       ├── config/presets/
│       │   ├── new.tsx                 ← QM-0  Novo Preset
│       │   └── [id]/edit.tsx           ← 1KD-0 Editar Preset
│       ├── scanner.tsx                 ← Scanner QR Code (ML Kit)
│       ├── nfc-reader.tsx              ← 1RY-0 Leitura NFC (bottom sheet modal)
│       └── filaments/[id]/
│           ├── qrcode.tsx              ← Ver QR Code
│           ├── label.tsx               ← Exportar Etiqueta (PDF/SVG)
│           └── write-nfc.tsx           ← Gravar tag NTAG215
└── src/
    ├── domain/          ← Entidades puras (Filament, SpoolPreset, User, Dashboard)
    ├── ports/           ← Interfaces (IFilamentRepository, ISpoolPresetRepository, IAuthRepository, INFCRepository)
    ├── application/     ← Use Cases (filament/, preset/, auth/, nfc/)
    ├── adapters/
    │   ├── remote/      ← Axios → API Rust (ApiFilamentRepository, etc.)
    │   ├── local/       ← SQLite (LocalFilamentRepository, LocalSpoolPresetRepository)
    │   └── nfc/         ← react-native-nfc-manager (NFCManagerRepository)
    ├── store/           ← Zustand: authStore, filamentStore, presetStore
    ├── shared/          ← theme.ts, constants.ts, utils/
    └── presentation/components/
        ├── ui/          ← Button, Input, Card, Badge
        ├── layout/      ← Screen, Header
        └── filament/    ← StockBar, ColorSwatch, FilamentCard
  • Scheme: meowspool:// (configurado em app.json)
  • Grupos: (auth) — sem sessão | (app) — autenticado | (app)/(tabs) — bottom tabs
Deep Link Handler Ação
meowspool://filament/<id> app/filament/[id].tsx Redireciona para /(app)/inventory/<id>
meowspool://verify-email?token=<tok> app/verify-email.tsx Verifica e-mail via POST /auth/verify-email
meowspool://reset-password?token=<tok> app/(auth)/reset-password.tsx Exibe formulário de nova senha

Links de e-mail: o backend gera https://meowspool.felipecncloud.com/api/v1/verify-email?token=xxx (aceito por clientes de e-mail). O backend redireciona 302 → meowspool://.... O OS abre o app.

8.4 Estratégia Offline-First

  1. Escrita: toda mutação persiste primeiro no SQLite (synced = 0) e enfileira em sync_queue.
  2. Leitura: sempre do SQLite; a API é usada apenas para sync.
  3. Sync: background job consome sync_queue e envia para o backend.
  4. Conflito: last-write-wins via updatedAt. O backend é a fonte de verdade.
  5. Auth offline: token no expo-secure-store; refresh automático via interceptor Axios.

Schema SQLite:

-- filaments
id TEXT PRIMARY KEY, user_id TEXT, material TEXT, brand TEXT, model TEXT,
color_hex TEXT, spool_preset_id TEXT, total_weight_g REAL, net_weight_g REAL,
temp_hotend_c REAL, temp_bed_c REAL, flow_factor_pct REAL, notes TEXT,
synced INTEGER DEFAULT 0, updated_at TEXT, created_at TEXT

-- spool_presets
id TEXT PRIMARY KEY, user_id TEXT, name TEXT, spool_weight_g REAL,
is_system INTEGER DEFAULT 0, synced INTEGER DEFAULT 0, created_at TEXT

-- sync_queue
id INTEGER PRIMARY KEY AUTOINCREMENT, entity_type TEXT, entity_id TEXT,
operation TEXT, payload TEXT, created_at TEXT

8.5 Como Rodar Localmente

cd mobile/
mise install          # garante Node 22 e Java 17
npm install

# Criar .env
echo "EXPO_PUBLIC_API_URL=http://localhost:3000/api/v1" > .env

# Dev (Expo Go — sem câmera ML Kit / NFC)
npx expo start

# Build nativo Android (necessário para câmera ML Kit e NFC)
mise exec -- npx expo run:android

Atenção ao build Android: o projeto usa namespace "com.meowspool" no build.gradle, mas o código fonte fica em com.meowspool.app. Use sempre nomes totalmente qualificados no AndroidManifest.xml.


9. Estrutura de Dados (Entidades Principais)

users

Campo Tipo Descrição
id UUID Identificador único
email string Email do usuário
password_hash string? Hash Argon2id (nulo se OAuth)
google_id string? ID OAuth Google (opcional)
email_verified boolean Obrigatório para login
created_at timestamp

spool_presets

Campo Tipo Descrição
id UUID Identificador único
name string Nome do preset
spool_weight_g integer Peso do carretel vazio em gramas
is_system boolean true = built-in (read-only para usuários)
user_id UUID? Nulo para presets do sistema
created_at timestamp

filaments

Campo Tipo Descrição
id UUID Identificador único
user_id UUID Dono do filamento
material string PLA, ABS, PETG, TPU, ASA, PA, PC…
brand string Marca
model string? Modelo/linha
color_hex string Cor em hex (#FF5733)
spool_preset_id UUID Preset de carretel usado
total_weight_g real Peso total medido na balança (gramas)
net_weight_g real Calculado: total peso do carretel
temp_hotend_c real? Temperatura do hotend (°C)
temp_bed_c real? Temperatura da mesa (°C)
flow_factor_pct real? Fator de fluxo (%)
notes text? Observações livres
updated_at timestamp Usado para resolução de conflitos sync
created_at timestamp

password_reset_tokens / email_verification_tokens

Campo Tipo Descrição
id UUID PK
token TEXT UNIQUE Token único (indexado)
user_id UUID FK para users
expires_at TIMESTAMPTZ TTL: 1h (reset) / 24h (verificação)
used_at TIMESTAMPTZ NULL = não usado; preenchido ao usar

10. Fluxo do Usuário

Cadastro e Login

  1. Usuário se cadastra → backend envia e-mail de verificação (background).
  2. Usuário clica no link https://... → backend redireciona 302 → deep link → app verifica.
  3. Login com e-mail não verificado → 403 EMAIL_NOT_VERIFIED → app sugere reenvio.
  4. Login com e-mail verificado → tokens JWT emitidos → sessão persistida no SecureStore.

Cadastro de Filamento

  1. Usuário abre o App (autenticado) → "Novo Filamento".
  2. Preenche marca, material, modelo, seleciona cor hex.
  3. Seleciona preset de carretel ou cria um novo.
  4. Digita o peso total (ex: 850g) → app calcula: 600g disponível.
  5. Salva localmente (offline-first) e sincroniza quando online.
  6. QR Code e etiqueta ficam disponíveis imediatamente.

Identificação via QR Code / NFC

  1. Usuário aponta câmera para QR Code ou aproxima tag NFC do carretel.
  2. App lê meowspool://filament/<id> → navega direto para o detalhe do filamento.

11. Artboards de Design

ID Tela Seção
G3-0 Login 5.1
IX-0 Cadastro 5.1
L2-0 Recuperação de Senha 5.1
RU-0 Verificação de Email 5.1
117-0 Redefinir Senha 5.1
135-0 Senha Redefinida 5.1
1-0 Home / Dashboard 5.5
CW-0 Inventário 5.2
17H-0 Filtros (bottom sheet) 5.2
2X-0 Cadastro de Filamento 5.2
13O-0 Editar Filamento 5.2
6L-0 Detalhe do Filamento 5.2
M4-0 Presets de Carretéis 5.3
QM-0 Novo Preset 5.3
1KD-0 Editar Preset 5.3
1CF-0 Ver QR Code 5.4
1FZ-0 Exportar Etiqueta 5.4
1RY-0 Leitura NFC (modal) 5.4
1LQ-0 Perfil / Conta 5.1

12. Roadmap

Pendente — Mobile

  • Sync background com sync_queue SQLite → API
  • Google OAuth com expo-auth-session
  • NFC suporte iOS (entitlement com.apple.developer.nfc.readwrite)
  • Expo Notifications para alertas de estoque baixo
  • Testes de integração com Jest + Testing Library

Pendente — Infraestrutura

  • DI container formal (instanciar e injetar repositórios concretos)

Fase 2 — Web (planejado)

  • Front-end Web com Next.js (dashboard + inventário)
  • Cache com Redis / HTTP Cache
  • Histórico de uso por projeto/impressão
  • Custo por grama
  • Importação em lote (CSV/JSON)
  • Pool público de presets compartilhados pela comunidade

Concluído

  • CRUD de filamentos (mobile + API)
  • CRUD de presets (mobile + API)
  • Dashboard (mobile + API)
  • QR Code geração e scanner (ML Kit)
  • Exportação de etiqueta SVG e PDF
  • Suporte Niimbot D11/D110 (22×14mm) com layout adaptativo
  • NFC leitura e gravação NTAG215 (Android)
  • Deep links (filamento, verificação de e-mail, reset de senha)
  • Verificação de e-mail obrigatória + reenvio
  • Fluxo completo de reset de senha
  • Redirect HTTP → deep link (compatibilidade com clientes de e-mail)