From e1a1f71fbd58fdf2a29d1c2abde902f98e5c0998 Mon Sep 17 00:00:00 2001 From: MatheusAlves96 Date: Wed, 22 Apr 2026 23:57:50 -0300 Subject: [PATCH] chore: seed sample video data, add spec 033 and update instructions --- .github/copilot-instructions.md | 4 +- .specify/feature.json | 2 +- backend/seeds/seed.py | 6 + .../checklists/requirements.md | 37 + .../contracts/api.md | 146 +++ .../data-model.md | 95 ++ specs/033-video-apresentacao-imovel/plan.md | 352 +++++++ .../quickstart.md | 110 ++ .../033-video-apresentacao-imovel/research.md | 44 + specs/033-video-apresentacao-imovel/spec.md | 144 +++ specs/033-video-apresentacao-imovel/tasks.md | 973 ++++++++++++++++++ 11 files changed, 1911 insertions(+), 2 deletions(-) create mode 100644 specs/033-video-apresentacao-imovel/checklists/requirements.md create mode 100644 specs/033-video-apresentacao-imovel/contracts/api.md create mode 100644 specs/033-video-apresentacao-imovel/data-model.md create mode 100644 specs/033-video-apresentacao-imovel/plan.md create mode 100644 specs/033-video-apresentacao-imovel/quickstart.md create mode 100644 specs/033-video-apresentacao-imovel/research.md create mode 100644 specs/033-video-apresentacao-imovel/spec.md create mode 100644 specs/033-video-apresentacao-imovel/tasks.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 0dd2244..ba80170 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -21,6 +21,8 @@ Auto-generated from all feature plans. Last updated: 2026-04-22 - PostgreSQL 16 — sem novas tabelas; property_count calculado via outerjoin COUNT (feature 024) - TypeScript 5.5 (frontend principal) / Python 3.12 existente sem mudança funcional + React 18, react-router-dom v6, Tailwind CSS 3.4, Axios (indireto via autenticação), contexto próprio `useAuth`, `useFavorites`, `ThemeToggle` (030-navbar-topo-ux) - N/A para persistência nova; sessão e token continuam em `localStorage` via `AuthContext` (030-navbar-topo-ux) +- Python 3.12 (backend) · TypeScript 5.5 (frontend) + Flask 3.x, SQLAlchemy 2.x, Pydantic v2, Alembic (backend) · React 18, Tailwind CSS 3.4 (frontend) — **sem novas dependências** (main) +- PostgreSQL 16 — tabela `properties` recebe `video_url VARCHAR(512) NULL` e `video_position VARCHAR(20) NOT NULL DEFAULT 'section'` (main) - Python 3.12 / TypeScript 5.5 + Flask 3.x, SQLAlchemy 2.x, Pydantic v2 (backend) · React 18, Tailwind CSS 3.4, react-router-dom v6, Axios (frontend) (master) @@ -41,9 +43,9 @@ cd src; pytest; ruff check . Python 3.12 / TypeScript 5.5: Follow standard conventions ## Recent Changes +- main: Added Python 3.12 (backend) · TypeScript 5.5 (frontend) + Flask 3.x, SQLAlchemy 2.x, Pydantic v2, Alembic (backend) · React 18, Tailwind CSS 3.4 (frontend) — **sem novas dependências** - 030-navbar-topo-ux: Added TypeScript 5.5 (frontend principal) / Python 3.12 existente sem mudança funcional + React 18, react-router-dom v6, Tailwind CSS 3.4, Axios (indireto via autenticação), contexto próprio `useAuth`, `useFavorites`, `ThemeToggle` - master: Added Python 3.12 + Flask SQLAlchemy func.count subquery · React 18 FilterSidebar com busca cross-categoria, controlled accordion, truncamento top-5 (feature 024) -- master: Added Python 3.12 (backend) · TypeScript 5.5 (frontend) + Flask 3.x, SQLAlchemy 2.x, Pydantic v2 (backend) · React 18, Tailwind CSS 3.4, react-router-dom v6, Axios (frontend) diff --git a/.specify/feature.json b/.specify/feature.json index 35b474e..eb894da 100644 --- a/.specify/feature.json +++ b/.specify/feature.json @@ -1,3 +1,3 @@ { - "feature_directory": "specs/029-ux-area-do-cliente" + "feature_directory": "specs/033-video-apresentacao-imovel" } diff --git a/backend/seeds/seed.py b/backend/seeds/seed.py index 54e57b5..ffaf551 100644 --- a/backend/seeds/seed.py +++ b/backend/seeds/seed.py @@ -316,6 +316,8 @@ SAMPLE_PROPERTIES = [ "area_m2": 98, "is_featured": True, "is_active": True, + "video_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", + "video_position": "section", "amenity_slugs": [ "ar-condicionado", "armario-embutido", @@ -346,6 +348,8 @@ SAMPLE_PROPERTIES = [ "area_m2": 220, "is_featured": True, "is_active": True, + "video_url": "https://www.youtube.com/watch?v=ysz5S6PUM-U", + "video_position": "carousel", "amenity_slugs": ["piscina", "churrasqueira", "jardim", "quintal"], "photos": [ { @@ -396,6 +400,8 @@ SAMPLE_PROPERTIES = [ "area_m2": 380, "is_featured": True, "is_active": True, + "video_url": "https://vimeo.com/76979871", + "video_position": "section", "amenity_slugs": [ "piscina", "vista-panoramica", diff --git a/specs/033-video-apresentacao-imovel/checklists/requirements.md b/specs/033-video-apresentacao-imovel/checklists/requirements.md new file mode 100644 index 0000000..b7beb29 --- /dev/null +++ b/specs/033-video-apresentacao-imovel/checklists/requirements.md @@ -0,0 +1,37 @@ +# Specification Quality Checklist: Vídeo de Apresentação do Imóvel + +**Purpose**: Validar completude e qualidade da especificação antes de avançar para o planejamento +**Created**: 2026-04-22 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- Spec está pronta para avançar para `/speckit.plan` +- Escopo claramente delimitado: 1 vídeo por imóvel, sem upload direto, sem múltiplos vídeos +- Posição padrão documentada como "seção exclusiva" nas Assumptions +- Comportamento de edge cases (URL inválida, vídeo removido externamente) está coberto diff --git a/specs/033-video-apresentacao-imovel/contracts/api.md b/specs/033-video-apresentacao-imovel/contracts/api.md new file mode 100644 index 0000000..51527b3 --- /dev/null +++ b/specs/033-video-apresentacao-imovel/contracts/api.md @@ -0,0 +1,146 @@ +# 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 + +```json +{ + "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 + +```json +{ + "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` + +```json +{ + "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` + +```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` + +```ts +interface VideoPlayerProps { + url: string + className?: string +} +``` + +### Comportamento de renderização + +| `type` retornado por `getEmbedUrl` | Renderização | +|------------------------------------|-------------| +| `youtube` \| `vimeo` | `