sass-imobiliaria/.specify/features/004-property-detail-page/contracts/rest.md

189 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# REST API Contracts: Property Detail Page (004)
**Feature**: `004-property-detail-page`
**Date**: 2026-04-13
**Base URL**: `/api/v1`
---
## GET /properties/{slug}
Retorna o detalhe completo de um imóvel ativo pelo slug.
### Request
```
GET /api/v1/properties/{slug}
Content-Type: application/json
Auth: não requerida
```
**Path parameters**:
| Param | Tipo | Descrição |
|-------|------|-----------|
| `slug` | string `[a-z0-9-]+` | Slug único do imóvel |
### Response 200 OK
```json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Apartamento 3 quartos no Centro",
"slug": "apartamento-3-quartos-centro-123",
"code": "AP-00042",
"description": "Excelente apartamento com vista para o jardim...",
"address": "Rua das Flores, 100, Centro",
"price": "850000.00",
"condo_fee": "650.00",
"type": "venda",
"bedrooms": 3,
"bathrooms": 2,
"parking_spots": 2,
"area_m2": 95,
"is_featured": true,
"subtype": { "id": 10, "name": "Apartamento", "slug": "apartamento" },
"city": { "id": 5, "name": "Franca", "slug": "franca", "state": "SP" },
"neighborhood": { "id": 12, "name": "Jardim São Luiz", "slug": "jardim-sao-luiz" },
"photos": [
{ "url": "https://...", "alt_text": "Sala de estar", "display_order": 0 },
{ "url": "https://...", "alt_text": "Quarto principal", "display_order": 1 }
],
"amenities": [
{ "id": 3, "name": "Aceita animais", "slug": "aceita-animais", "group": "caracteristica" },
{ "id": 7, "name": "Piscina", "slug": "piscina", "group": "lazer" }
]
}
```
**Notas do schema**:
- `price` e `condo_fee` são strings de decimal (ex: `"850000.00"`) — use `parseFloat()` no frontend
- `type` é o campo canônico (equivale a `listing_type` na documentação da spec)
- `photos` ordenadas por `display_order` ASC (índice 0 = foto principal do carrossel)
- `code` e `description` são `null` se não preenchidos no cadastro
- `address` é `null` se não preenchido
### Response 404 Not Found
Imóvel inexistente **ou** `is_active = false`.
```json
{ "error": "Imóvel não encontrado" }
```
> Retornar 404 (não 403) para imóveis inativos evita vazar informação sobre existência.
---
## POST /properties/{slug}/contact
Registra um lead de contato vinculado ao imóvel identificado pelo slug.
### Request
```
POST /api/v1/properties/{slug}/contact
Content-Type: application/json
Auth: não requerida
```
**Path parameters**:
| Param | Tipo | Descrição |
|-------|------|-----------|
| `slug` | string | Slug do imóvel para o qual o contato é enviado |
**Request body**:
```json
{
"name": "João Silva",
"email": "joao@email.com",
"phone": "(16) 99999-0000",
"message": "Tenho interesse no imóvel, gostaria de agendar uma visita."
}
```
**Campo** | **Tipo** | **Obrigatório** | **Restrições**
----------|----------|-----------------|---------------
`name` | string | sim | 2150 caracteres
`email` | string | sim | formato e-mail válido (RFC 5322)
`phone` | string | não | máximo 20 caracteres
`message` | string | sim | 102000 caracteres
### Response 201 Created
```json
{
"id": 88,
"message": "Mensagem enviada com sucesso!"
}
```
### Response 404 Not Found
Slug não encontrado ou imóvel inativo.
```json
{ "error": "Imóvel não encontrado" }
```
### Response 422 Unprocessable Entity
Falha de validação Pydantic.
```json
{
"error": "Dados inválidos",
"details": {
"email": ["E-mail inválido"],
"message": ["Campo obrigatório"]
}
}
```
---
## Comportamentos Implícitos
| Situação | Comportamento |
|----------|--------------|
| `slug` com caracteres inválidos (maiúsculas, /, etc.) | 404 (SQLAlchemy não encontra correspondência) |
| `property_id` na tabela `contact_leads` quando imóvel é deletado | SET NULL (lead preservado) |
| Duplo envio de formulário | Dois registros criados (UI bloqueia durante `submitting`) |
| `photos` vazia | Array `[]` retornado; frontend exibe placeholder |
| `amenities` vazia | Array `[]` retornado; frontend omite seção de amenidades |
---
## Integração Frontend
### `getProperty(slug: string): Promise<PropertyDetail>`
```typescript
// services/properties.ts
export async function getProperty(slug: string): Promise<PropertyDetail> {
const response = await api.get<PropertyDetail>(`/properties/${slug}`)
return response.data
}
```
- `404` → Axios lança `AxiosError` com `status 404` → frontend exibe estado "não encontrado"
- Outros erros → propagar para tratamento genérico
### `submitContactForm(slug: string, data: ContactFormData): Promise<{ id: number; message: string }>`
```typescript
// services/properties.ts
export async function submitContactForm(
slug: string,
data: ContactFormData,
): Promise<{ id: number; message: string }> {
const response = await api.post<{ id: number; message: string }>(
`/properties/${slug}/contact`,
data,
)
return response.data
}
```
- `201` → retorna `{ id, message }`
- `422``AxiosError` com `response.data.details` disponível para mapear erros por campo
- `5xx` → exibir mensagem genérica sem apagar dados do formulário