yeah
All checks were successful
Deploy App / build (push) Successful in 10s
Deploy App / deploy (push) Successful in 11s

This commit is contained in:
valere
2026-02-02 21:00:28 +01:00
parent e257e076c4
commit b9a3d0184f
98 changed files with 5068 additions and 3713 deletions

View File

@@ -0,0 +1,58 @@
import { eq, notInArray } from 'drizzle-orm'
import { useDB, schema } from '../db'
import { scanMusicFolder } from '../utils/fileScanner'
const { cards } = schema
export async function syncCardsWithDatabase(folderPath: string) {
const db = useDB()
const scannedCards = await scanMusicFolder(folderPath)
console.log(`📁 ${scannedCards.length} cards trouvées dans le dossier`)
// 1. Récupérer les cards existantes en DB
const existingCards = await db.select().from(cards).all()
const existingEsids = new Set(existingCards.map((t) => t.esid))
// 2. Identifier les nouvelles cards à ajouter
const cardsToInsert = scannedCards.filter((card) => !existingEsids.has(card.esid))
// 3. Identifier les cards à supprimer
const scannedEsids = new Set(scannedCards.map((t) => t.esid))
const cardsToDelete = existingCards.filter((t) => !scannedEsids.has(t.esid))
// 4. Insérer les nouvelles cards
if (cardsToInsert.length > 0) {
// Dans la fonction syncCardsWithDatabase
await db.insert(cards).values(
cardsToInsert.map((card) => ({
url_audio: card.url_audio,
url_image: card.url_image,
year: card.year,
month: card.month,
day: card.day,
hour: card.hour,
artist: card.artist,
title: card.title,
esid: card.esid,
slug: card.slug,
createdAt: card.createdAt,
suit: card.suit,
rank: card.rank
}))
)
console.log(`${cardsToInsert.length} cards ajoutées`)
}
// 5. Supprimer les cards obsolètes avec une requête distincte pour chaque esid
for (const cardToDelete of cardsToDelete) {
await db.delete(cards).where(eq(cards.esid, cardToDelete.esid))
console.log(`🗑️ ${cardsToDelete.length} cards supprimées`)
}
return {
added: cardsToInsert.length,
deleted: cardsToDelete.length,
total: scannedCards.length
}
}

View File

@@ -1,163 +0,0 @@
import fs from 'fs'
import path from 'path'
import { Database } from 'sqlite'
import { fileURLToPath } from 'url'
import chokidar from 'chokidar'
import { db } from '../database'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const PLAYLISTS_DIR = path.join(process.cwd(), 'public/ESPLAYLISTS')
const AUDIO_EXTENSIONS = ['.mp3', '.wav', '.ogg', '.m4a', '.flac']
export class PlaylistSyncService {
private watcher: chokidar.FSWatcher | null = null
constructor(private db: Database) {}
async initialize() {
await this.scanPlaylists()
this.setupWatcher()
}
private async scanPlaylists() {
try {
if (!fs.existsSync(PLAYLISTS_DIR)) {
console.warn(`Playlists directory not found: ${PLAYLISTS_DIR}`)
return
}
const playlists = fs
.readdirSync(PLAYLISTS_DIR, { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name)
for (const playlistName of playlists) {
await this.syncPlaylist(playlistName)
}
} catch (error) {
console.error('Error scanning playlists:', error)
}
}
private async syncPlaylist(playlistName: string) {
const playlistPath = path.join(PLAYLISTS_DIR, playlistName)
try {
const stats = fs.statSync(playlistPath)
// Vérifier ou créer la playlist dans la base de données
let playlist = await this.db.get('SELECT * FROM playlists WHERE name = ?', [playlistName])
if (!playlist) {
const result = await this.db.run(
'INSERT INTO playlists (name, path, last_modified) VALUES (?, ?, ?)',
[playlistName, playlistPath, new Date(stats.mtime).toISOString()]
)
playlist = await this.db.get('SELECT * FROM playlists WHERE id = ?', [result.lastID])
}
// Récupérer les pistes actuelles
const currentTracks = fs
.readdirSync(playlistPath)
.filter((file) => {
const ext = path.extname(file).toLowerCase()
return AUDIO_EXTENSIONS.includes(ext)
})
.map((file, index) => ({
path: path.join(playlistName, file),
order: index
}))
// Mettre à jour les pistes dans la base de données
await this.db.run('BEGIN TRANSACTION')
try {
// Supprimer les anciennes entrées
await this.db.run('DELETE FROM playlist_tracks WHERE playlist_id = ?', [playlist.id])
// Ajouter les nouvelles pistes
for (const track of currentTracks) {
await this.db.run(
'INSERT INTO playlist_tracks (playlist_id, track_path, track_order) VALUES (?, ?, ?)',
[playlist.id, track.path, track.order]
)
}
// Mettre à jour la date de modification
await this.db.run('UPDATE playlists SET last_modified = ? WHERE id = ?', [
new Date().toISOString(),
playlist.id
])
await this.db.run('COMMIT')
console.log(`Playlist "${playlistName}" synchronized with ${currentTracks.length} tracks`)
} catch (error) {
await this.db.run('ROLLBACK')
console.error(`Error syncing playlist ${playlistName}:`, error)
}
} catch (error) {
console.error(`Error accessing playlist directory ${playlistPath}:`, error)
}
}
private setupWatcher() {
if (!fs.existsSync(PLAYLISTS_DIR)) {
console.warn(`Playlists directory not found, watcher not started: ${PLAYLISTS_DIR}`)
return
}
this.watcher = chokidar.watch(PLAYLISTS_DIR, {
ignored: /(^|[\/\\])\../, // ignore les fichiers cachés
persistent: true,
ignoreInitial: true,
depth: 2 // surveille un seul niveau de sous-dossiers
})
this.watcher
.on('add', (filePath) => this.handleFileChange('add', filePath))
.on('change', (filePath) => this.handleFileChange('change', filePath))
.on('unlink', (filePath) => this.handleFileChange('unlink', filePath))
.on('addDir', (dirPath) => this.handleDirChange('addDir', dirPath))
.on('unlinkDir', (dirPath) => this.handleDirChange('unlinkDir', dirPath))
.on('error', (error) => console.error('Watcher error:', error))
}
private async handleFileChange(event: string, filePath: string) {
const relativePath = path.relative(PLAYLISTS_DIR, filePath)
const playlistName = path.dirname(relativePath)
const ext = path.extname(filePath).toLowerCase()
// Ignorer les fichiers qui ne sont pas des fichiers audio
if (playlistName === '.' || !AUDIO_EXTENSIONS.includes(ext)) {
return
}
console.log(`File ${event}: ${relativePath}`)
await this.syncPlaylist(playlistName)
}
private async handleDirChange(event: string, dirPath: string) {
const relativePath = path.relative(PLAYLISTS_DIR, dirPath)
if (relativePath === '') return // Ignorer le dossier racine
console.log(`Directory ${event}: ${relativePath}`)
if (event === 'addDir') {
await this.syncPlaylist(relativePath)
} else if (event === 'unlinkDir') {
// Supprimer la playlist de la base de données
await this.db.run('DELETE FROM playlists WHERE name = ?', [relativePath])
console.log(`Removed playlist from database: ${relativePath}`)
}
}
close() {
if (this.watcher) {
return this.watcher.close()
}
return Promise.resolve()
}
}
// Singleton instance
export const playlistSyncService = new PlaylistSyncService(db)