Millora de la Importació de Balls - LINE DANCE PLATFORM
Context del Projecte
Treballo amb line-dance-platform, una aplicació Spring Boot + React per gestionar balls de line dance.
Estat actual BD (14/03/2026):
- 4.132 balls (tots PUBLISHED)
- 1.142 coreògrafs (555 amb país = 48.6%)
- 3.913 cançons
- 6.273 links
Repositori: Susisco/line-dance-platform (branca feature/linedance-poc)
🎯 OBJECTIU
Millorar la funcionalitat d'importació de balls des de YouTube (/admin/import/youtube) amb dues noves funcionalitats:
- Registre d'importacions per canal: Guardar l'última data d'importació de cada canal YouTube
- Importació massiva incremental: Botó per importar automàticament tots els vídeos nous d'un canal des de l'última importació
📦 MODEL DE DADES ACTUAL
1. Dance (Ball)
@Entity @Table(name = "dances")
public class Dance {
Long id;
String name; // Nom del ball (obligatori)
Level level; // FK a levels (UNKNOWN, ABSOLUTE_BEGINNER, BEGINNER, INTERMEDIATE, ADVANCED, EXPERT)
Integer counts; // Passos (32, 64, 48...)
Integer walls; // Parets (2, 4...)
String description; // Descripció lliure
String restartInfo; // Info de restarts
String tagInfo; // Info de tags
Integer originYear; // Any d'origen
String premiereInfo; // Lloc i data d'estrena (text lliure)
String[] aliases; // Noms alternatius
String tags; // JSON ["country", "pop"]
// Relacions
Set<Choreographer> choreographers; // M:N amb coreògrafs
Set<DanceSong> danceSongs; // 1:N amb cançons (via join)
Location originLocation; // FK opcional
Venue originVenue; // FK opcional
Event premiereEvent; // FK opcional
User owner; // Propietari (obligatori)
DanceListingStatus listingStatus; // DRAFT, REVIEW, PUBLISHED, ARCHIVED
// Auditoria
Instant createdAt, updatedAt;
String createdBy, lastModifiedBy;
}
2. Choreographer (Coreògraf)
@Entity @Table(name = "choreographers")
public class Choreographer {
Long id;
String name; // Nom (obligatori)
Boolean isGroup; // Grup o individual
String country; // País
String bio; // Biografia
Person person; // FK opcional (persona real)
User owner; // FK opcional (propietari verificat)
ListingStatus listingStatus; // INFORMATIVE, VERIFIED, CLAIMED
}
3. Song (Cançó)
@Entity @Table(name = "songs")
public class Song {
Long id;
String title; // Títol (obligatori)
String artist; // Artista (obligatori)
Integer year;
String album;
String isrc; // Codi ISRC
Integer durationSeconds;
Integer bpm;
Intensity intensity; // LOW, MEDIUM, HIGH
String spotifyId; // ID Spotify
BigDecimal spotifyEnergy; // Energia (0.0-1.0)
String previewUrl; // URL preview
String albumArtUrl; // URL caràtula
String[] genres; // Gèneres
User owner; // Propietari (obligatori)
}
4. Link (Enllaç)
@Entity @Table(name = "links")
public class Link {
Long id;
LinkKind kind; // YOUTUBE_DANCE, YOUTUBE_TEACH, YOUTUBE_MUSIC, SPOTIFY, APPLE, STEPSHEET, WEB, FACEBOOK, INSTAGRAM
String url; // URL completa
String title; // Títol (professor - lloc - data)
String language; // Idioma (ca, es, en...)
String videoId; // YouTube video ID (11 chars)
LinkRole role; // PRIMARY, SECONDARY
Dance dance; // FK (ball)
Song song; // FK (cançó)
}
5. DanceSong (Relació Ball-Cançó)
@Entity @Table(name = "dance_song")
public class DanceSong {
Long id;
Dance dance; // FK obligatori
Song song; // FK obligatori
DanceSongRole role; // PRIMARY, ALTERNATIVE
Integer sortOrder;
String notes;
}
6. LineDanceSpec (Especificació tècnica)
@Entity @Table(name = "linedance_specs")
public class LineDanceSpec {
Long id; // Mateixa ID que el dance (@MapsId)
Dance dance; // FK
String importedStepsheet; // Stepsheet importat (text)
LineDanceSpecStatus status; // DRAFT, REVIEW, PUBLISHED
// Components, seqüències, etc.
}
🔧 FUNCIONALITAT ACTUAL D'IMPORTACIÓ
Pàgina: /admin/import/youtube (YouTubeImportPage.tsx)
Flux actual (un a un):
- L'usuari enganxa una URL de YouTube
- Backend analitza amb YouTube API + Gemini AI (via OpenRouter)
- Extreu: danceName, choreographers[], songTitle, songArtist, counts, walls, level, techSpec, premiereInfo, linkTitle, linkLanguage
- Detecta duplicats (per nom) i coreògrafs existents (per nom)
- L'usuari revisa i corregeix les dades
- L'usuari importa la cançó des de iTunes/Spotify (MusicImportModal)
- Confirmació: crea Dance, Choreographer (si nou), Link, DanceSong, LineDanceSpec
Endpoints actuals:
GET /api/admin/dance-import/status→ Comprova si YouTube API disponiblePOST /api/admin/dance-import/analyze→ Analitza URL/descripcióPOST /api/admin/dance-import/confirm→ Crea el ball
Serveis implicats:
DanceImportController.java→ Controller RESTDanceImportService.java→ analyze() + confirm()YouTubeService.java→ getVideoMetadata(), extractVideoId()GeminiAiService.java→ parseVideoMetadata() via OpenRouterMusicController.java→/api/music/search,/api/music/importITunesMusicProvider.java,SpotifyMusicProvider.java
📺 CANALS YOUTUBE IMPORTATS
| Canal | Channel ID | Última importació | Vídeos | Rang |
|---|---|---|---|---|
| Catalan Honky Tonk Friends | UCi-EHWXp4Dh2RhE3krdbUew | 16/02/2026 | 868 | 20/12/2025 - 02/02/2026 |
Fitxer TSV original: Line_Dance_Youtube_Extraction - Hoja 1.tsv (arrel del projecte)
⚠️ Per noves importacions CHTF: Vídeos publicats després de 2026-02-02T10:34:51Z
🆕 NOVES FUNCIONALITATS REQUERIDES
1. Nova taula: youtube_channel_imports
-- V58__youtube_channel_imports.sql
CREATE TABLE youtube_channel_imports (
id SERIAL PRIMARY KEY,
channel_id VARCHAR(50) NOT NULL UNIQUE, -- UCi-EHWXp4Dh2RhE3krdbUew
channel_name VARCHAR(200) NOT NULL, -- Catalan Honky Tonk Friends
last_video_published_at TIMESTAMPTZ, -- Data publicació del vídeo més recent importat
last_import_at TIMESTAMPTZ, -- Data de l'última execució d'importació
videos_imported_count INTEGER DEFAULT 0, -- Total vídeos importats d'aquest canal
import_notes TEXT, -- Notes (opcional)
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Registre inicial per Catalan Honky Tonk Friends
INSERT INTO youtube_channel_imports
(channel_id, channel_name, last_video_published_at, last_import_at, videos_imported_count)
VALUES
('UCi-EHWXp4Dh2RhE3krdbUew', 'Catalan Honky Tonk Friends',
'2026-02-02T10:34:51Z', '2026-02-16T23:08:16Z', 868);
2. Entitat JPA: YouTubeChannelImport.java
@Entity
@Table(name = "youtube_channel_imports")
public class YouTubeChannelImport {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "channel_id", nullable = false, unique = true, length = 50)
private String channelId;
@Column(name = "channel_name", nullable = false, length = 200)
private String channelName;
@Column(name = "last_video_published_at")
private Instant lastVideoPublishedAt;
@Column(name = "last_import_at")
private Instant lastImportAt;
@Column(name = "videos_imported_count")
private Integer videosImportedCount = 0;
// ... timestamps
}
3. Nous endpoints
GET /api/admin/dance-import/channels
→ Llista tots els canals registrats
POST /api/admin/dance-import/channels
→ Registra un nou canal (channelId, channelName)
GET /api/admin/dance-import/channels/{channelId}/new-videos
→ Llista vídeos del canal publicats després de lastVideoPublishedAt
POST /api/admin/dance-import/channels/{channelId}/import-batch
→ Importa N vídeos nous (amb feedback de progrés)
4. Mètode YouTube API: Llistar vídeos d'un canal
// YouTubeService.java
public List<VideoMetadata> getChannelVideos(String channelId, Instant publishedAfter, int maxResults) {
// GET https://www.googleapis.com/youtube/v3/search
// ?channelId=UCi-EHWXp4Dh2RhE3krdbUew
// &publishedAfter=2026-02-02T10:34:51Z
// &type=video
// &order=date
// &maxResults=50
// &key=API_KEY
}
5. UI: Secció de canals a YouTubeImportPage.tsx
- Mostrar targetes amb canals registrats
- Info: nom, última importació, vídeos pendents
- Botó "Veure nous vídeos" → mostra llista
- Botó "Importar seleccionats" → processa un a un amb progress bar
⚙️ CONFIGURACIÓ
application-local.yml:
app:
youtube:
api-key: AIzaSy... # Ja configurat i funcionant
enabled: true
ai:
api-key: sk-or... # OpenRouter key
model: openai/gpt-3.5-turbo
enabled: true
📁 FITXERS RELLEVANTS
Backend
| Fitxer | Ubicació | Descripció |
|---|---|---|
| DanceImportController.java | api/controller/ | Endpoints importació |
| DanceImportService.java | service/ | Lògica analyze/confirm |
| YouTubeService.java | service/ | YouTube Data API |
| GeminiAiService.java | service/ | Parsing IA (OpenRouter) |
| MusicController.java | api/ | Cerca/importa música |
| Dance.java | domain/ | Entitat ball |
| Choreographer.java | domain/ | Entitat coreògraf |
| Song.java | domain/ | Entitat cançó |
| Link.java | domain/ | Entitat enllaç |
| DanceSong.java | domain/ | Relació ball-cançó |
Frontend
| Fitxer | Ubicació | Descripció |
|---|---|---|
| YouTubeImportPage.tsx | pages/ | UI importació |
| danceImport.ts | api/ | Types i funcions API |
| MusicImportModal.tsx | components/songs/ | Modal cerca iTunes/Spotify |
| ChoreographerSelector.tsx | components/ | Selector coreògrafs |
DTOs importació
| DTO | Descripció |
|---|---|
| DanceImportParseRequest | Request per analitzar URL |
| DanceImportAnalysisDto | Resposta amb draft i duplicats |
| DanceImportDraftDto | Dades extretes del vídeo |
| DanceImportConfirmRequest | Request per crear ball |
| DanceImportResultDto | Resposta amb IDs creats |
| ChoreographerDraftDto | Coreògraf detectat amb candidats |
| ChoreographerImportDto | Coreògraf a crear/seleccionar |
✅ TASQUES A IMPLEMENTAR
Backend
- Crear migració
V58__youtube_channel_imports.sql - Crear entitat
YouTubeChannelImport.java - Crear
YouTubeChannelImportRepository.java - Afegir a
YouTubeService:getChannelVideos(channelId, publishedAfter, maxResults) - Crear
YouTubeChannelService.java(o ampliarDanceImportService) - Ampliar
DanceImportControlleramb endpoints de canals
Frontend
- Afegir secció "Canals registrats" a
YouTubeImportPage.tsx - Crear component
ChannelCard.tsxper mostrar info d'un canal - Crear component
NewVideosModal.tsxper seleccionar vídeos a importar - Implementar importació en batch amb progress feedback
📝 NOTES ADDICIONALS
- Nivells disponibles: UNKNOWN, ABSOLUTE_BEGINNER, BEGINNER, INTERMEDIATE, ADVANCED, EXPERT
- LinkKind per vídeos: YOUTUBE_DANCE (demo), YOUTUBE_TEACH (tutorial), YOUTUBE_MUSIC (àudio)
- Detecció duplicats: Per
videoIdal camplinks.video_id- ja implementat - AI parsing: Gemini/OpenRouter extreu molt bé les dades, però cal supervisió humana
- Música: L'usuari ha de seleccionar manualment la cançó via iTunes/Spotify
Exemple de resposta YouTube API Search
{
"items": [
{
"id": { "videoId": "EXw1pZz_zX8" },
"snippet": {
"publishedAt": "2026-02-02T10:34:51Z",
"channelId": "UCi-EHWXp4Dh2RhE3krdbUew",
"title": "Heart Land",
"description": "Neus Lloveras El Barn d'en Greg...",
"channelTitle": "Catalan Honky Tonk Friends"
}
}
]
}