2.9 KiB
2.9 KiB
Data Model: Vídeo de Apresentação do Imóvel (033)
Gerado por: /speckit.plan
Data: 2026-04-22
Entidade Afetada: Property (tabela properties)
Novos Campos
| Coluna | Tipo SQL | Nullable | Default | Restrições |
|---|---|---|---|---|
video_url |
VARCHAR(512) |
✅ NULL | NULL |
— |
video_position |
VARCHAR(20) |
❌ NOT NULL | 'section' |
valores válidos: 'carousel' | 'section' (enforced no Pydantic) |
Justificativa de tipos
VARCHAR(512)paravideo_url: URLs YouTube/Vimeo raramente ultrapassam 100 chars, mas URLs diretas de CDN podem ser longas; 512 é conservador sem overhead relevante.VARCHAR(20)paravideo_position: valores'carousel'(8 chars) e'section'(7 chars) cabem confortavelmente; sem ENUM SQL para simplificar migrations.
Migration Alembic
Arquivo: backend/migrations/versions/k3l4m5n6o7p8_add_video_to_properties.py
Antecessora: j2k3l4m5n6o7_add_homepage_hero_theme_images.py
# upgrade
op.add_column('properties', sa.Column('video_url', sa.String(512), nullable=True))
op.add_column('properties', sa.Column('video_position', sa.String(20), nullable=False, server_default='section'))
# downgrade
op.drop_column('properties', 'video_position')
op.drop_column('properties', 'video_url')
Schema Pydantic — Campos Adicionados
PropertyDetailOut (backend/app/schemas/property.py)
video_url: str | None = None
video_position: Literal['carousel', 'section'] = 'section'
PropertyAdminOut (backend/app/routes/admin.py)
video_url: str | None = None
video_position: Literal['carousel', 'section'] = 'section'
_SCALAR_FIELDS no admin_update_property
Adicionar 'video_url' e 'video_position' à tupla _SCALAR_FIELDS.
Sanitização: se 'video_url' estiver presente no body, aplicar .strip() e tratar string vazia como None.
Tipos TypeScript
PropertyDetail (frontend/src/types/property.ts)
video_url: string | null
video_position: 'carousel' | 'section'
Validação de Regras
| Regra | Onde Validado |
|---|---|
video_position só aceita 'carousel' ou 'section' |
Pydantic Literal (backend) |
video_url string vazia → None |
handler admin_update_property (backend) |
video_url sem espaços |
.strip() antes de persistir (backend) |
| URL inválida (domínio não suportado) → sem exibição | getEmbedUrl retorna type: 'unknown' (frontend) |
Estado de Transição
Property sem vídeo (video_url = NULL)
→ Nenhuma alteração visual na página de detalhe
Property com video_position = 'section'
→ Seção "Vídeo de Apresentação" renderizada após carrossel
Property com video_position = 'carousel'
→ VideoPlayer como índice 0 no PhotoCarousel
Remover vídeo (admin)
→ video_url = NULL, video_position retorna ao default 'section'