# 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)` para `video_url`: URLs YouTube/Vimeo raramente ultrapassam 100 chars, mas URLs diretas de CDN podem ser longas; 512 é conservador sem overhead relevante. - `VARCHAR(20)` para `video_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` ```python # 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) ```python video_url: str | None = None video_position: Literal['carousel', 'section'] = 'section' ``` ### `PropertyAdminOut` (backend/app/routes/admin.py) ```python 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) ```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' ```