chore: seed sample video data, add spec 033 and update instructions
This commit is contained in:
parent
2e9f903d06
commit
e1a1f71fbd
11 changed files with 1911 additions and 2 deletions
95
specs/033-video-apresentacao-imovel/data-model.md
Normal file
95
specs/033-video-apresentacao-imovel/data-model.md
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# 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'
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue