feat: enhance filament management and UI improvements
- Update SpoolPresetRepository to include user_id in the update query. - Expand .gitignore to include various environment and temporary files. - Revise agent documentation for better clarity and formatting. - Implement pull-to-refresh functionality in the inventory list. - Integrate API calls for deleting and updating filaments, ensuring state synchronization. - Add custom color picker for filament color selection with hex validation. - Update AndroidManifest and Gradle files for improved configuration and permissions. - Refactor MainActivity and MainApplication for better splash screen handling. - Update styles and colors for a cohesive UI experience. - Replace splash screen logos and icons with new assets.
This commit is contained in:
+81
-45
@@ -8,22 +8,22 @@ Backend da aplicação MeowSpool escrito em **Rust**, utilizando **Axum** como f
|
||||
|
||||
## Stack
|
||||
|
||||
| Componente | Biblioteca | Versão mínima |
|
||||
|-------------------|-----------------------------------------------|---------------|
|
||||
| HTTP Framework | `axum` | 0.7 |
|
||||
| Async Runtime | `tokio` (full) | 1.x |
|
||||
| ORM / Query | `sqlx` (postgres, uuid, time, macros) | 0.8 |
|
||||
| Autenticação JWT | `jsonwebtoken` | 9.x |
|
||||
| Hash de senha | `argon2` | 0.5 |
|
||||
| OAuth Google | `oauth2` | 4.x |
|
||||
| UUID | `uuid` (v4, serde) | 1.x |
|
||||
| Serialização | `serde`, `serde_json` | 1.x |
|
||||
| Erros | `thiserror` | 1.x |
|
||||
| Env vars | `dotenvy` | 0.15 |
|
||||
| Logging | `tracing`, `tracing-subscriber` | 0.1 |
|
||||
| Validação | `validator` | 0.18 |
|
||||
| HTTP Client | `reqwest` (json, rustls-tls) | 0.12 |
|
||||
| Geração QR Code | `qrcode` | 0.14 |
|
||||
| Componente | Biblioteca | Versão mínima |
|
||||
| ---------------- | ------------------------------------- | ------------- |
|
||||
| HTTP Framework | `axum` | 0.7 |
|
||||
| Async Runtime | `tokio` (full) | 1.x |
|
||||
| ORM / Query | `sqlx` (postgres, uuid, time, macros) | 0.8 |
|
||||
| Autenticação JWT | `jsonwebtoken` | 9.x |
|
||||
| Hash de senha | `argon2` | 0.5 |
|
||||
| OAuth Google | `oauth2` | 4.x |
|
||||
| UUID | `uuid` (v4, serde) | 1.x |
|
||||
| Serialização | `serde`, `serde_json` | 1.x |
|
||||
| Erros | `thiserror` | 1.x |
|
||||
| Env vars | `dotenvy` | 0.15 |
|
||||
| Logging | `tracing`, `tracing-subscriber` | 0.1 |
|
||||
| Validação | `validator` | 0.18 |
|
||||
| HTTP Client | `reqwest` (json, rustls-tls) | 0.12 |
|
||||
| Geração QR Code | `qrcode` | 0.14 |
|
||||
|
||||
---
|
||||
|
||||
@@ -109,37 +109,38 @@ Todas as rotas são prefixadas com `/api/v1`.
|
||||
|
||||
### Auth — `/api/v1/auth`
|
||||
|
||||
| Método | Rota | Handler | Acesso |
|
||||
|--------|-----------------------|---------------------------|--------------------------------|
|
||||
| POST | `/register` | `register_handler` | Público |
|
||||
| POST | `/login` | `login_handler` | Público |
|
||||
| POST | `/oauth/google` | `google_oauth_handler` | Público |
|
||||
| POST | `/refresh` | `refresh_token_handler` | Público (requer refresh token) |
|
||||
| POST | `/logout` | `logout_handler` | Autenticado |
|
||||
| POST | `/forgot-password` | `forgot_password_handler` | Público |
|
||||
| POST | `/verify-email` | `verify_email_handler` | Público |
|
||||
| POST | `/reset-password` | `reset_password_handler` | Público (requer token de reset)|
|
||||
| Método | Rota | Handler | Acesso |
|
||||
| ------ | ------------------ | ------------------------- | ------------------------------- |
|
||||
| POST | `/register` | `register_handler` | Público |
|
||||
| POST | `/login` | `login_handler` | Público |
|
||||
| POST | `/oauth/google` | `google_oauth_handler` | Público |
|
||||
| POST | `/refresh` | `refresh_token_handler` | Público (requer refresh token) |
|
||||
| POST | `/logout` | `logout_handler` | Autenticado |
|
||||
| POST | `/forgot-password` | `forgot_password_handler` | Público |
|
||||
| POST | `/verify-email` | `verify_email_handler` | Público |
|
||||
| POST | `/reset-password` | `reset_password_handler` | Público (requer token de reset) |
|
||||
|
||||
### Users — `/api/v1/users`
|
||||
|
||||
| Método | Rota | Handler | Acesso |
|
||||
|--------|-------|---------------------|-------------|
|
||||
| ------ | ----- | ------------------- | ----------- |
|
||||
| GET | `/me` | `get_me_handler` | Autenticado |
|
||||
| PUT | `/me` | `update_me_handler` | Autenticado |
|
||||
|
||||
### Filaments — `/api/v1/filaments`
|
||||
|
||||
| Método | Rota | Handler | Acesso |
|
||||
|--------|-------------------|----------------------------|-------------|
|
||||
| GET | `/` | `list_filaments_handler` | Autenticado |
|
||||
| POST | `/` | `create_filament_handler` | Autenticado |
|
||||
| GET | `/:id` | `get_filament_handler` | Autenticado |
|
||||
| PUT | `/:id` | `update_filament_handler` | Autenticado |
|
||||
| DELETE | `/:id` | `delete_filament_handler` | Autenticado |
|
||||
| GET | `/:id/qrcode` | `get_qrcode_handler` | Autenticado |
|
||||
| GET | `/:id/label.svg` | `export_label_handler` | Autenticado |
|
||||
| Método | Rota | Handler | Acesso |
|
||||
| ------ | ---------------- | ------------------------- | ----------- |
|
||||
| GET | `/` | `list_filaments_handler` | Autenticado |
|
||||
| POST | `/` | `create_filament_handler` | Autenticado |
|
||||
| GET | `/:id` | `get_filament_handler` | Autenticado |
|
||||
| PUT | `/:id` | `update_filament_handler` | Autenticado |
|
||||
| DELETE | `/:id` | `delete_filament_handler` | Autenticado |
|
||||
| GET | `/:id/qrcode` | `get_qrcode_handler` | Autenticado |
|
||||
| GET | `/:id/label.svg` | `export_label_handler` | Autenticado |
|
||||
|
||||
**Query params de listagem (`GET /filaments`):**
|
||||
|
||||
- `material` — filtra por tipo (PLA, ABS, PETG, TPU, ASA, PA, PC...)
|
||||
- `brand` — filtra por marca
|
||||
- `search` — busca em marca, modelo e notas
|
||||
@@ -149,20 +150,21 @@ Todas as rotas são prefixadas com `/api/v1`.
|
||||
|
||||
### Spool Presets — `/api/v1/spool-presets`
|
||||
|
||||
| Método | Rota | Handler | Acesso |
|
||||
|--------|----------|--------------------------|-------------------------------------|
|
||||
| GET | `/` | `list_presets_handler` | Autenticado |
|
||||
| POST | `/` | `create_preset_handler` | Autenticado |
|
||||
| PUT | `/:id` | `update_preset_handler` | Autenticado (apenas presets do user)|
|
||||
| DELETE | `/:id` | `delete_preset_handler` | Autenticado (apenas presets do user)|
|
||||
| Método | Rota | Handler | Acesso |
|
||||
| ------ | ------ | ----------------------- | ------------------------------------ |
|
||||
| GET | `/` | `list_presets_handler` | Autenticado |
|
||||
| POST | `/` | `create_preset_handler` | Autenticado |
|
||||
| PUT | `/:id` | `update_preset_handler` | Autenticado (apenas presets do user) |
|
||||
| DELETE | `/:id` | `delete_preset_handler` | Autenticado (apenas presets do user) |
|
||||
|
||||
### Dashboard — `/api/v1/dashboard`
|
||||
|
||||
| Método | Rota | Handler | Acesso |
|
||||
|--------|------|----------------------|-------------|
|
||||
| GET | `/` | `dashboard_handler` | Autenticado |
|
||||
| Método | Rota | Handler | Acesso |
|
||||
| ------ | ---- | ------------------- | ----------- |
|
||||
| GET | `/` | `dashboard_handler` | Autenticado |
|
||||
|
||||
**Resposta do dashboard:**
|
||||
|
||||
```json
|
||||
{
|
||||
"total_stock_kg": 14.2,
|
||||
@@ -361,11 +363,13 @@ O backend implementa **last-write-wins com timestamp**:
|
||||
## Geração de QR Code e Etiqueta SVG
|
||||
|
||||
### QR Code (`GET /filaments/:id/qrcode`)
|
||||
|
||||
- Gera QR Code com deep link: `meowspool://filament/:id` (singular)
|
||||
- Retorna PNG (`image/png`) por padrão, ou SVG com `?format=svg`
|
||||
- Biblioteca: crate `qrcode`
|
||||
|
||||
### Etiqueta SVG (`GET /filaments/:id/label.svg`)
|
||||
|
||||
- Query params: `width_mm` (padrão: 50), `height_mm` (padrão: 30)
|
||||
- Retorna SVG com: cor visual, modelo, material, marca, peso líquido e QR Code embutido
|
||||
- `Content-Type: image/svg+xml`
|
||||
@@ -381,3 +385,35 @@ O backend implementa **last-write-wins com timestamp**:
|
||||
- Queries usam **bind parameters** do SQLx — nunca interpolação de string em SQL.
|
||||
- `user_id` é sempre extraído do token JWT, nunca aceito como parâmetro de URL ou body.
|
||||
- Presets do sistema (`is_system = true`) são protegidos no nível de serviço: edição ou deleção retorna `403 Forbidden`.
|
||||
|
||||
---
|
||||
|
||||
## Mudanças Recentes (14/03/2026)
|
||||
|
||||
### ✅ Correção: PUT Spool Preset retornava FORBIDDEN
|
||||
|
||||
**Local:** `src/adapters/outbound/postgres_spool_preset_repo.rs` — método `update()`
|
||||
|
||||
**Problema:**
|
||||
O SQL UPDATE não validava o `user_id` na cláusula WHERE. Qualquer usuário poderia tentar editar presets de outros usuários ou presets do sistema.
|
||||
|
||||
```rust
|
||||
// ❌ ANTES (inseguro)
|
||||
UPDATE spool_presets
|
||||
SET name = $2, spool_weight_g = $3
|
||||
WHERE id = $1 AND is_system = false
|
||||
RETURNING ...
|
||||
```
|
||||
|
||||
**Solução:**
|
||||
Adicionado `AND user_id = $4` para validar propriedade antes de atualizar:
|
||||
|
||||
```rust
|
||||
// ✅ DEPOIS (seguro)
|
||||
UPDATE spool_presets
|
||||
SET name = $2, spool_weight_g = $3
|
||||
WHERE id = $1 AND is_system = false AND user_id = $4
|
||||
RETURNING ...
|
||||
```
|
||||
|
||||
Agora o repositório valida que o preset pertence ao usuário autenticado (extraído do JWT). Tentativas de editar presets de outro usuário ou do sistema recebem `404 Not Found` (sem vazar que o preset existe).
|
||||
|
||||
@@ -5,6 +5,6 @@ variables:
|
||||
- secret: true
|
||||
name: refresh_token
|
||||
- name: preset_id
|
||||
value: 38e439ba-0289-4bf0-9f4b-f9cb12991167
|
||||
value: 88eab1b9-e979-46db-940f-59cac97df60b
|
||||
- name: filament_id
|
||||
value: 0b371f84-706a-4eaa-bff0-33556fa833b4
|
||||
value: da5e431c-bc2e-4075-aa1c-ce82ee9567b4
|
||||
|
||||
@@ -21,7 +21,7 @@ CREATE INDEX idx_spool_presets_is_system ON spool_presets (is_system);
|
||||
-- Presets built-in do sistema (somente leitura para usuários)
|
||||
INSERT INTO spool_presets (name, spool_weight_g, is_system) VALUES
|
||||
('Bambu Lab (Plástico)', 250, TRUE),
|
||||
('Elegoo (Papelão)', 200, TRUE),
|
||||
('Elegoo (Papelão)', 156, TRUE),
|
||||
('Creality (Plástico)', 230, TRUE),
|
||||
('Prusament (Plástico)', 201, TRUE),
|
||||
('Sunlu (Papelão)', 200, TRUE),
|
||||
|
||||
@@ -69,12 +69,13 @@ impl SpoolPresetRepository for PostgresSpoolPresetRepository {
|
||||
let row = sqlx::query_as::<_, SpoolPresetRow>(
|
||||
r#"UPDATE spool_presets
|
||||
SET name = $2, spool_weight_g = $3
|
||||
WHERE id = $1 AND is_system = false
|
||||
WHERE id = $1 AND is_system = false AND user_id = $4
|
||||
RETURNING id, name, spool_weight_g, is_system, user_id, created_at"#,
|
||||
)
|
||||
.bind(preset.id)
|
||||
.bind(&preset.name)
|
||||
.bind(preset.spool_weight_g)
|
||||
.bind(preset.user_id)
|
||||
.fetch_one(self.db.as_ref())
|
||||
.await?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user