feat: add full project - backend, frontend, docker, specs and configs

This commit is contained in:
MatheusAlves96 2026-04-20 23:59:45 -03:00
parent b77c7d5a01
commit e6cb06255b
24489 changed files with 61341 additions and 36 deletions

View file

@ -0,0 +1,116 @@
# Data Model — Melhorias UX/UI (023)
> Nenhuma migration de banco necessária. Todos os campos utilizados já existem no modelo `Property`.
---
## Entidades Existentes (campos utilizados nesta feature)
### `Property` (`backend/app/models/property.py`)
| Campo | Tipo SQLAlchemy | Tipo Python | Sprint | Utilização |
|---|---|---|---|---|
| `title` | `VARCHAR(200)` | `str` | 1 | Busca textual `q` (ILIKE) |
| `address` | `VARCHAR(300)` | `str \| None` | 1 | Busca textual `q` (ILIKE) |
| `code` | `VARCHAR(30)` | `str \| None` | 1 | Busca textual `q` (ILIKE) |
| `neighborhood_id` | `INTEGER FK → neighborhoods.id` | `int \| None` | 1 | Join para busca `q` em `Neighborhood.name` |
| `price` | `NUMERIC(12,2)` | `Decimal` | 2 | Ordenação `price_asc` / `price_desc` |
| `area_m2` | `INTEGER` | `int` | 2 | Ordenação `area_desc` |
| `created_at` | `DATETIME` | `datetime` | 2/3 | Ordenação `newest`; badge "Novo" (frontend) |
| `is_featured` | `BOOLEAN` | `bool` | 3 | Badge "Destaque" no card |
### `Neighborhood` (`backend/app/models/location.py`)
| Campo | Tipo SQLAlchemy | Utilização |
|---|---|---|
| `id` | `INTEGER PK` | Join com `Property.neighborhood_id` |
| `name` | `VARCHAR` | Busca textual `q` (ILIKE) |
---
## Tipos Frontend Adicionados (`frontend/src/services/properties.ts`)
### `PropertyFilters` — campos novos
```ts
// Adição aos campos existentes:
q?: string // busca textual livre
sort?: SortOption
```
### `SortOption` (novo tipo)
```ts
type SortOption =
| 'relevance' // default — equivale a created_at DESC no backend
| 'price_asc'
| 'price_desc'
| 'area_desc'
| 'newest'
```
### `ViewMode` (novo tipo local — apenas frontend)
```ts
type ViewMode = 'list' | 'grid'
// Persiste em localStorage com key 'imoveis_view_mode'
```
---
## Entidades de UI (apenas frontend — sem persistência no banco)
### `ActiveFilterChip`
Tipo derivado calculado a partir de `PropertyFilters` + dados do catálogo:
```ts
interface ActiveFilterChip {
key: string // identificador único (ex: 'city_id', 'q', 'bedrooms_min')
label: string // texto exibido no chip (ex: 'São Paulo', 'Busca: "Jardins"')
onRemove: () => void // callback que remove este filtro específico
}
```
### `EmptyStateSuggestion`
```ts
interface EmptyStateSuggestion {
label: string // ex: 'Remover filtro de bairro'
relaxedFilters: PropertyFilters
count: number // total de imóveis com o filtro relaxado
}
```
---
## Validação de Entrada — Backend
O parâmetro `q` não é validado via Pydantic (é query param de GET, sem body).
Sanitização aplicada diretamente na rota:
```python
q = args.get("q", "").strip()
# Comprimento máximo razoável (evitar payloads abusivos)
if len(q) > 200:
q = q[:200]
```
O parâmetro `sort` é validado via whitelist implícita no `sort_map.get(sort, default)`.
---
## Diagrama de Relacionamentos (campos relevantes)
```
Property
├── title (VARCHAR 200) ─── ILIKE com q
├── address (VARCHAR 300) ─── ILIKE com q
├── code (VARCHAR 30) ─── ILIKE com q
├── neighborhood_id (FK) ─┐
│ ├── JOIN → Neighborhood.name ─── ILIKE com q
├── price (NUMERIC 12,2) ─── ORDER BY price_asc/desc
├── area_m2 (INTEGER) ─── ORDER BY area_desc
├── created_at (DATETIME) ─── ORDER BY newest; badge "Novo" no frontend
└── is_featured (BOOLEAN) ─── badge "Destaque" no frontend
```