4.2 KiB
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 anull(remove o vídeo)video_urlcom espaços → sanitizado via.strip()antes de persistir- Omitir
video_urlevideo_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 / 5para 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)