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

5.1 KiB
Raw Blame History

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

{
  "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.

{ "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:

{
  "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

{
  "id": 88,
  "message": "Mensagem enviada com sucesso!"
}

Response 404 Not Found

Slug não encontrado ou imóvel inativo.

{ "error": "Imóvel não encontrado" }

Response 422 Unprocessable Entity

Falha de validação Pydantic.

{
  "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>

// 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 }>

// 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 }
  • 422AxiosError com response.data.details disponível para mapear erros por campo
  • 5xx → exibir mensagem genérica sem apagar dados do formulário