sass-imobiliaria/specs/033-video-apresentacao-imovel/contracts/api.md
MatheusAlves96 e1a1f71fbd
Some checks failed
CI/CD → Deploy via SSH / Build & Push Docker Images (push) Successful in 1m6s
CI/CD → Deploy via SSH / Deploy via SSH (push) Successful in 4m18s
CI/CD → Deploy via SSH / Validate HTTPS & Endpoints (push) Has been cancelled
chore: seed sample video data, add spec 033 and update instructions
2026-04-22 23:57:50 -03:00

4.2 KiB

Contracts: Vídeo de Apresentação do Imóvel (033)

Gerado por: /speckit.plan
Data: 2026-04-22


Endpoint Afetado: PUT /api/admin/properties/:id

Autenticação: JWT admin obrigatório (Bearer token)

Request Body — Campos adicionados

{
  "video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "video_position": "section"
}
Campo Tipo Obrigatório Valores Aceitos Default
video_url string | null Não Qualquer string válida ou null Campo não enviado = não alterado
video_position string Não "carousel" | "section" "section"

Comportamento especial:

  • video_url: "" (string vazia) → equivalente a null (remove o vídeo)
  • video_url com espaços → sanitizado via .strip() antes de persistir
  • Omitir video_url e video_position → campos não são alterados (comportamento PUT parcial existente)

Response Body — Campos adicionados

{
  "id": "uuid",
  "title": "...",
  "video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "video_position": "section",
  "..."
}

Endpoint Afetado: GET /api/properties/:slug

Response Body — Campos adicionados em PropertyDetailOut

{
  "id": "uuid",
  "slug": "apartamento-centro",
  "title": "...",
  "video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "video_position": "section",
  "photos": [...],
  "..."
}
Campo Tipo Nullable Descrição
video_url string | null Sim URL do vídeo ou null se não configurado
video_position "carousel" | "section" Não Posição de exibição; default "section"

Contrato Frontend — Utilitário getEmbedUrl

Arquivo: frontend/src/utils/getEmbedUrl.ts

export type VideoType = 'youtube' | 'vimeo' | 'direct' | 'unknown'

export interface EmbedResult {
  type: VideoType
  embedUrl: string | null
}

export function getEmbedUrl(url: string): EmbedResult

Tabela de transformação

URL de entrada type embedUrl
https://www.youtube.com/watch?v=VIDEO_ID youtube https://www.youtube.com/embed/VIDEO_ID
https://youtu.be/VIDEO_ID youtube https://www.youtube.com/embed/VIDEO_ID
https://www.youtube.com/embed/VIDEO_ID youtube URL original (já é embed)
https://vimeo.com/VIDEO_ID vimeo https://player.vimeo.com/video/VIDEO_ID
https://player.vimeo.com/video/VIDEO_ID vimeo URL original (já é embed)
https://example.com/video.mp4 direct URL original
https://example.com/video.webm direct URL original
Qualquer outro formato unknown null

Contrato Frontend — Componente VideoPlayer

Arquivo: frontend/src/components/PropertyDetail/VideoPlayer.tsx

interface VideoPlayerProps {
  url: string
  className?: string
}

Comportamento de renderização

type retornado por getEmbedUrl Renderização
youtube | vimeo <iframe src={embedUrl} allowFullScreen loading="lazy" /> dentro de wrapper 16:9
direct <video src={url} controls /> dentro de wrapper 16:9
unknown null (nada renderizado)

Atributos iframe obrigatórios

allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
loading="lazy"
title="Vídeo de apresentação do imóvel"

Contrato Frontend — Modificação PhotoCarousel

Arquivo: frontend/src/components/PropertyDetail/PhotoCarousel.tsx

interface PhotoCarouselProps {
  photos: PropertyPhoto[]
  videoUrl?: string | null   // NOVO — opcional
}

Comportamento quando videoUrl está presente

  • totalSlides = photos.length + 1 (slide 0 = vídeo)
  • Slide 0: renderiza <VideoPlayer url={videoUrl} />
  • Slides 1..N: renderizam fotos normalmente
  • Contador: {activeIndex + 1} / {totalSlides} (ex.: 1 / 5 para o vídeo)
  • Thumbnail de vídeo no strip: ícone de play com aria-label="Ver vídeo"

Comportamento quando videoUrl é null/undefined

  • Comportamento idêntico ao atual (sem alteração)