Skip to main content

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:

  1. Registre d'importacions per canal: Guardar l'última data d'importació de cada canal YouTube
  2. 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)
}
@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):

  1. L'usuari enganxa una URL de YouTube
  2. Backend analitza amb YouTube API + Gemini AI (via OpenRouter)
  3. Extreu: danceName, choreographers[], songTitle, songArtist, counts, walls, level, techSpec, premiereInfo, linkTitle, linkLanguage
  4. Detecta duplicats (per nom) i coreògrafs existents (per nom)
  5. L'usuari revisa i corregeix les dades
  6. L'usuari importa la cançó des de iTunes/Spotify (MusicImportModal)
  7. Confirmació: crea Dance, Choreographer (si nou), Link, DanceSong, LineDanceSpec

Endpoints actuals:

  • GET /api/admin/dance-import/status → Comprova si YouTube API disponible
  • POST /api/admin/dance-import/analyze → Analitza URL/descripció
  • POST /api/admin/dance-import/confirm → Crea el ball

Serveis implicats:

  • DanceImportController.java → Controller REST
  • DanceImportService.java → analyze() + confirm()
  • YouTubeService.java → getVideoMetadata(), extractVideoId()
  • GeminiAiService.java → parseVideoMetadata() via OpenRouter
  • MusicController.java/api/music/search, /api/music/import
  • ITunesMusicProvider.java, SpotifyMusicProvider.java

📺 CANALS YOUTUBE IMPORTATS

CanalChannel IDÚltima importacióVídeosRang
Catalan Honky Tonk FriendsUCi-EHWXp4Dh2RhE3krdbUew16/02/202686820/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

FitxerUbicacióDescripció
DanceImportController.javaapi/controller/Endpoints importació
DanceImportService.javaservice/Lògica analyze/confirm
YouTubeService.javaservice/YouTube Data API
GeminiAiService.javaservice/Parsing IA (OpenRouter)
MusicController.javaapi/Cerca/importa música
Dance.javadomain/Entitat ball
Choreographer.javadomain/Entitat coreògraf
Song.javadomain/Entitat cançó
Link.javadomain/Entitat enllaç
DanceSong.javadomain/Relació ball-cançó

Frontend

FitxerUbicacióDescripció
YouTubeImportPage.tsxpages/UI importació
danceImport.tsapi/Types i funcions API
MusicImportModal.tsxcomponents/songs/Modal cerca iTunes/Spotify
ChoreographerSelector.tsxcomponents/Selector coreògrafs

DTOs importació

DTODescripció
DanceImportParseRequestRequest per analitzar URL
DanceImportAnalysisDtoResposta amb draft i duplicats
DanceImportDraftDtoDades extretes del vídeo
DanceImportConfirmRequestRequest per crear ball
DanceImportResultDtoResposta amb IDs creats
ChoreographerDraftDtoCoreògraf detectat amb candidats
ChoreographerImportDtoCoreògraf a crear/seleccionar

✅ TASQUES A IMPLEMENTAR

Backend

  1. Crear migració V58__youtube_channel_imports.sql
  2. Crear entitat YouTubeChannelImport.java
  3. Crear YouTubeChannelImportRepository.java
  4. Afegir a YouTubeService: getChannelVideos(channelId, publishedAfter, maxResults)
  5. Crear YouTubeChannelService.java (o ampliar DanceImportService)
  6. Ampliar DanceImportController amb endpoints de canals

Frontend

  1. Afegir secció "Canals registrats" a YouTubeImportPage.tsx
  2. Crear component ChannelCard.tsx per mostrar info d'un canal
  3. Crear component NewVideosModal.tsx per seleccionar vídeos a importar
  4. 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 videoId al camp links.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

{
"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"
}
}
]
}