feat: add full project - backend, frontend, docker, specs and configs
This commit is contained in:
parent
b77c7d5a01
commit
e6cb06255b
24489 changed files with 61341 additions and 36 deletions
150
.specify/features/004-property-detail-page/research.md
Normal file
150
.specify/features/004-property-detail-page/research.md
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
# Research: Property Detail Page (004)
|
||||
|
||||
**Feature**: `004-property-detail-page`
|
||||
**Date**: 2026-04-13
|
||||
**Status**: Complete — todos os NEEDS CLARIFICATION resolvidos
|
||||
|
||||
---
|
||||
|
||||
## R-01 — Campos `code` e `description` ausentes no modelo `Property`
|
||||
|
||||
**Pergunta**: Os campos `code` e `description` devem ser adicionados nesta feature ou numa feature separada?
|
||||
|
||||
**Evidência**: O contrato da API em `spec.md` inclui explicitamente `"code": "AP-00042"` e `"description": "..."` na resposta de `GET /api/v1/properties/<slug>`. A Constituição (Princípio III) define que a spec é a fonte de verdade.
|
||||
|
||||
**Decisão**: Adicionar ambos os campos ao modelo `Property` nesta feature. A migration que cria `contact_leads` também incluirá as novas colunas.
|
||||
|
||||
**Campos a adicionar**:
|
||||
- `code`: `VARCHAR(30)`, `UNIQUE`, `nullable=True` (nullable para não quebrar registros existentes sem migration data)
|
||||
- `description`: `TEXT`, `nullable=True`
|
||||
|
||||
**Rationale**: Agrupar numa única migration evita fragmentação de DDL. Os campos são necessários para o contrato da API desta feature.
|
||||
|
||||
**Alternativa rejeitada**: Feature separada só para `code`/`description` — overhead desnecessário para dois campos simples.
|
||||
|
||||
---
|
||||
|
||||
## R-02 — Campo `address` ausente em `PropertyOut`
|
||||
|
||||
**Pergunta**: O campo `address` existe no modelo mas não está em `PropertyOut`. Como tratar?
|
||||
|
||||
**Evidência**: `Property.address = db.Column(db.String(300), nullable=True)` existe no modelo. `PropertyOut` não inclui `address`. O contrato da spec exige `address` na resposta de detalhe.
|
||||
|
||||
**Decisão**: Adicionar `address: str | None` ao `PropertyOut` existente. É um campo geral do imóvel (não exclusivo da tela de detalhe) e sua ausência no schema era uma omissão.
|
||||
|
||||
**Impacto nos consumers existentes**: O endpoint `GET /api/v1/properties` (list) passará a incluir `address` na resposta. Isso é retrocompatível — campos adicionais em JSON não quebram consumers que não os leem.
|
||||
|
||||
**Alternativa rejeitada**: `PropertyDetailOut` separado apenas para `address` — over-engineering para um campo que logicamente pertence ao schema base.
|
||||
|
||||
---
|
||||
|
||||
## R-03 — `type` vs `listing_type` no contrato da API
|
||||
|
||||
**Pergunta**: O contrato da spec documenta `listing_type` mas o schema e o modelo usam `type`. O que usar na implementação?
|
||||
|
||||
**Evidência**:
|
||||
- Model: `type = db.Column(db.Enum("venda", "aluguel", name="property_type"))`
|
||||
- `PropertyOut`: `type: Literal["venda", "aluguel"]`
|
||||
- Spec API contract: `"listing_type": "venda"`
|
||||
|
||||
**Decisão**: Manter `type` no schema e na serialização JSON. O contrato da spec usa `listing_type` como nome descritivo na documentação, mas o campo JSON emitido pelo backend será `type` (consistente com o endpoint de listagem já em produção). A spec documenta o _significado_ do campo, não exige renaming.
|
||||
|
||||
**Rationale**: Renomear para `listing_type` quebraria o endpoint de listagem que já retorna `type`. Backward compatibility supera a preferência de nomenclatura da spec, especialmente porque o frontend já consome `type`.
|
||||
|
||||
**Alternativa rejeitada**: Alias Pydantic `listing_type` via `Field(alias="type")` — introduziria inconsistência entre list e detail sem benefício real no MVP.
|
||||
|
||||
---
|
||||
|
||||
## R-04 — Carousel: biblioteca externa vs handlers nativos
|
||||
|
||||
**Pergunta**: Usar Embla Carousel, Swiper.js ou implementar com React state + handlers nativos?
|
||||
|
||||
**Análise**:
|
||||
| Opção | Tamanho bundle | Complexidade | Justificativa |
|
||||
|-------|---------------|--------------|---------------|
|
||||
| Embla Carousel | ~7 KB gzip | baixa (API simples) | overkill para carousel básico |
|
||||
| Swiper.js | ~35 KB gzip | média-alta | excesso de features desnecessárias |
|
||||
| React state nativo | 0 KB extra | baixa-média | suficiente para os requisitos da spec |
|
||||
|
||||
**Requisitos da spec**: navegação por teclado (←/→), swipe touch, thumbnail strip com estado ativo. Tudo implementável com:
|
||||
- `useState` para índice ativo
|
||||
- `onKeyDown` no container (tabIndex=0)
|
||||
- `onTouchStart`/`onTouchEnd` para detectar swipe horizontal
|
||||
- CSS `transition` para animação suave
|
||||
|
||||
**Decisão**: Implementar com React state + handlers nativos. Zero nova dependência npm (alinhamento com Princípio VI).
|
||||
|
||||
**Rationale**: Os requisitos são exatos e limitados. Uma lib traz overhead de API para aprender, bundle weight extra e potencial conflito com o design system customizado.
|
||||
|
||||
---
|
||||
|
||||
## R-05 — Mapa (US3 P3): Google Maps Embed
|
||||
|
||||
**Pergunta**: Qual serviço de mapa usar para US3? Chave de API necessária?
|
||||
|
||||
**Análise**:
|
||||
- US3 é P3 (prioridade mais baixa) — não bloqueia o MVP funcional
|
||||
- Google Maps Embed API: iframe simples, sem SDK JS, sem package npm
|
||||
- URL: `https://www.google.com/maps/embed/v1/place?key=API_KEY&q=ENDEREÇO_CODIFICADO`
|
||||
- Requer chave de API com "Maps Embed API" habilitada
|
||||
- OpenStreetMap via `iframe` Nominatim: gratuito, sem chave, mas qualidade variável
|
||||
|
||||
**Decisão**: Google Maps Embed via `<iframe>` simples quando `VITE_GOOGLE_MAPS_API_KEY` estiver definido. Se a variável não existir ou o endereço for nulo, a seção de mapa é silenciosamente omitida `(null render)`.
|
||||
|
||||
**Configuração necessária**:
|
||||
- Env var frontend: `VITE_GOOGLE_MAPS_API_KEY` (opcional)
|
||||
- Sem nova dependência npm
|
||||
|
||||
**Alternativa rejeitada**: Leaflet + react-leaflet — adiciona ~40 KB ao bundle para uma feature P3 que pode ser omitida no MVP.
|
||||
|
||||
---
|
||||
|
||||
## R-06 — Schema de detalhe: `PropertyDetailOut` vs extensão de `PropertyOut`
|
||||
|
||||
**Pergunta**: Criar `PropertyDetailOut(PropertyOut)` ou adicionar campos diretamente a `PropertyOut`?
|
||||
|
||||
**Evidência e raciocínio**:
|
||||
- `code` e `description` são campos de detalhe narrativo — não fazem sentido no card da listagem (espaço limitado)
|
||||
- `PropertyOut` é usado por dois endpoints: list (`GET /api/v1/properties`) e futuramente featured
|
||||
- Adicionar `code`/`description` a `PropertyOut` polui a resposta da listagem
|
||||
|
||||
**Decisão**: Criar `PropertyDetailOut(PropertyOut)` com os campos adicionais:
|
||||
```python
|
||||
class PropertyDetailOut(PropertyOut):
|
||||
address: str | None
|
||||
code: str | None
|
||||
description: str | None
|
||||
```
|
||||
`address` vai em `PropertyDetailOut` — em `PropertyOut` base o campo não é adicionado para não expor informação de endereço completo na listagem paginada.
|
||||
|
||||
**Wait — revisão**: O R-02 decidiu adicionar `address` a `PropertyOut`. Reconsiderando com R-06: manter `address` apenas em `PropertyDetailOut` é mais conservador e evita expor endereços na listagem. **Decisão final**: `address`, `code`, `description` em `PropertyDetailOut` somente.
|
||||
|
||||
---
|
||||
|
||||
## R-07 — Validação de `ContactLeadIn`: comprimentos de campo
|
||||
|
||||
**Pergunta**: Quais validações Pydantic usar em `ContactLeadIn`?
|
||||
|
||||
**Spec definiu**:
|
||||
- `name`: obrigatório, 2–150 chars
|
||||
- `email`: obrigatório, EmailStr
|
||||
- `phone`: opcional, max 20 chars
|
||||
- `message`: obrigatório, 10–2000 chars
|
||||
|
||||
**Decisão**: Usar `pydantic.EmailStr` (requer `email-validator` no pyproject.toml — já presente como dependência do projeto). Usar `Annotated[str, Field(min_length=..., max_length=...)]` para os demais.
|
||||
|
||||
**Verificar**: `email-validator` já está em `pyproject.toml` antes de implementar. Se não estiver, adicionar com `uv add email-validator`.
|
||||
|
||||
---
|
||||
|
||||
## Resumo das Decisões
|
||||
|
||||
| ID | Decisão |
|
||||
|----|---------|
|
||||
| R-01 | `code` e `description` adicionados em `Property` + na migration desta feature |
|
||||
| R-02 | `address` vai em `PropertyDetailOut` (não em `PropertyOut` base) |
|
||||
| R-03 | Manter `type` no JSON (não renomear para `listing_type`) |
|
||||
| R-04 | Carousel: React state + handlers nativos, zero nova lib |
|
||||
| R-05 | Google Maps Embed via iframe, env var opcional, omissão silenciosa se ausente |
|
||||
| R-06 | `PropertyDetailOut(PropertyOut)` com `address`, `code`, `description` |
|
||||
| R-07 | Pydantic `EmailStr` + `Field(min_length, max_length)` para ContactLeadIn |
|
||||
Loading…
Add table
Add a link
Reference in a new issue