yeah
This commit is contained in:
@@ -1,23 +0,0 @@
|
||||
import { eventHandler } from 'h3'
|
||||
import { getDatabase } from '../utils/database'
|
||||
|
||||
export default eventHandler(() => {
|
||||
const db = getDatabase()
|
||||
|
||||
const artists = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT id, name, url, cover_id
|
||||
FROM artists
|
||||
ORDER BY id
|
||||
`
|
||||
)
|
||||
.all()
|
||||
|
||||
return artists.map((artist: any) => ({
|
||||
id: artist.id,
|
||||
name: artist.name,
|
||||
url: artist.url,
|
||||
coverId: `https://f4.bcbits.com/img/${artist.cover_id}_4.jpg`
|
||||
}))
|
||||
})
|
||||
@@ -1,61 +0,0 @@
|
||||
import { eventHandler } from 'h3'
|
||||
import { getDatabase } from '../utils/database'
|
||||
|
||||
export default eventHandler(() => {
|
||||
const db = getDatabase()
|
||||
|
||||
// Récupérer les boxes
|
||||
const boxes = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT id, type, name, description, state, duration, active_side, color1, color2
|
||||
FROM boxes
|
||||
ORDER BY id
|
||||
`
|
||||
)
|
||||
.all()
|
||||
|
||||
// Récupérer les sides pour chaque box
|
||||
const sides = db
|
||||
.prepare(
|
||||
`
|
||||
SELECT box_id, side, name, description, duration, color1, color2
|
||||
FROM sides
|
||||
`
|
||||
)
|
||||
.all()
|
||||
|
||||
// Grouper les sides par box_id
|
||||
const sidesByBoxId: Record<string, any> = {}
|
||||
for (const side of sides) {
|
||||
if (!sidesByBoxId[side.box_id]) {
|
||||
sidesByBoxId[side.box_id] = {}
|
||||
}
|
||||
sidesByBoxId[side.box_id][side.side] = {
|
||||
name: side.name,
|
||||
description: side.description,
|
||||
duration: side.duration,
|
||||
color1: side.color1,
|
||||
color2: side.color2
|
||||
}
|
||||
}
|
||||
|
||||
// Formater les résultats
|
||||
return boxes.map((box: any) => ({
|
||||
id: box.id,
|
||||
type: box.type,
|
||||
name: box.name,
|
||||
description: box.description,
|
||||
state: box.state,
|
||||
duration: box.duration,
|
||||
activeSide: box.active_side,
|
||||
...(box.type === 'compilation'
|
||||
? {
|
||||
sides: sidesByBoxId[box.id] || {}
|
||||
}
|
||||
: {
|
||||
color1: box.color1,
|
||||
color2: box.color2
|
||||
})
|
||||
}))
|
||||
})
|
||||
41
server/api/card/[slug].ts
Normal file
41
server/api/card/[slug].ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { useDB, schema } from '../../db'
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const slug = getRouterParam(event, 'slug')
|
||||
|
||||
if (!slug) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'ESID manquant dans la requête'
|
||||
})
|
||||
}
|
||||
|
||||
const db = useDB()
|
||||
const card = await db.select().from(schema.cards).where(eq(schema.cards.slug, slug)).get()
|
||||
|
||||
if (!card) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Morceau non trouvé'
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
id: card.id,
|
||||
esid: card.esid,
|
||||
title: card.title,
|
||||
artist: card.artist,
|
||||
url_audio: card.url_audio,
|
||||
url_image: card.url_image,
|
||||
year: card.year,
|
||||
month: card.month,
|
||||
day: card.day,
|
||||
hour: card.hour,
|
||||
slug: card.slug,
|
||||
suit: card.suit,
|
||||
rank: card.rank,
|
||||
createdAt: card.createdAt,
|
||||
updatedAt: card.updatedAt
|
||||
}
|
||||
})
|
||||
41
server/api/cards/[esid].ts
Normal file
41
server/api/cards/[esid].ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { useDB, schema } from '../../db'
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const esid = getRouterParam(event, 'esid')
|
||||
|
||||
if (!esid) {
|
||||
throw createError({
|
||||
statusCode: 400,
|
||||
statusMessage: 'ESID manquant dans la requête'
|
||||
})
|
||||
}
|
||||
|
||||
const db = useDB()
|
||||
const card = await db.select().from(schema.cards).where(eq(schema.cards.esid, esid)).get()
|
||||
|
||||
if (!card) {
|
||||
throw createError({
|
||||
statusCode: 404,
|
||||
statusMessage: 'Morceau non trouvé'
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
id: card.id,
|
||||
esid: card.esid,
|
||||
title: card.title,
|
||||
artist: card.artist,
|
||||
url_audio: card.url_audio,
|
||||
url_image: card.url_image,
|
||||
year: card.year,
|
||||
month: card.month,
|
||||
day: card.day,
|
||||
hour: card.hour,
|
||||
slug: card.slug,
|
||||
suit: card.suit,
|
||||
rank: card.rank,
|
||||
createdAt: card.createdAt,
|
||||
updatedAt: card.updatedAt
|
||||
}
|
||||
})
|
||||
84
server/api/cards/index.ts
Normal file
84
server/api/cards/index.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { and, eq, ilike, or, sql } from 'drizzle-orm'
|
||||
import { useDB, schema } from '../../db'
|
||||
|
||||
const PAGE_SIZE = 20 // Nombre d'éléments par page
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const query = getQuery(event)
|
||||
const page = Number(query.page) || 1
|
||||
const search = query.search?.toString()
|
||||
const cardRank = query.rank?.toString()
|
||||
const cardSuit = query.suit?.toString()
|
||||
const year = query.year?.toString()
|
||||
|
||||
const db = useDB()
|
||||
const offset = (page - 1) * PAGE_SIZE
|
||||
|
||||
// Log pour débogage
|
||||
console.log('Requête avec paramètres:', { search, cardRank, cardSuit, year })
|
||||
console.log('Schéma des cards:', Object.keys(schema.cards))
|
||||
|
||||
// Construction des conditions de filtrage
|
||||
const conditions = []
|
||||
|
||||
if (search) {
|
||||
const searchTerm = `%${search}%`
|
||||
conditions.push(
|
||||
or(ilike(schema.cards.title, searchTerm), ilike(schema.cards.artist, searchTerm))
|
||||
)
|
||||
}
|
||||
|
||||
if (cardRank) {
|
||||
conditions.push(eq(schema.cards.rank, cardRank))
|
||||
}
|
||||
|
||||
if (cardSuit) {
|
||||
conditions.push(eq(schema.cards.suit, cardSuit))
|
||||
}
|
||||
|
||||
if (year) {
|
||||
conditions.push(eq(schema.cards.year, year))
|
||||
}
|
||||
|
||||
// Requête pour le comptage total
|
||||
const countQuery = db
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(schema.cards)
|
||||
.$dynamic()
|
||||
|
||||
// Log pour débogage SQL
|
||||
console.log('Requête de comptage SQL:', countQuery.toSQL())
|
||||
|
||||
// Requête pour les données paginées
|
||||
const cardsQuery = db
|
||||
.select()
|
||||
.from(schema.cards)
|
||||
.$dynamic()
|
||||
.limit(PAGE_SIZE)
|
||||
.offset(offset)
|
||||
.orderBy(schema.cards.title)
|
||||
|
||||
// Application des conditions si elles existent
|
||||
if (conditions.length > 0) {
|
||||
const where = and(...conditions)
|
||||
countQuery.where(where)
|
||||
cardsQuery.where(where)
|
||||
}
|
||||
|
||||
const [countResult, cards] = await Promise.all([countQuery, cardsQuery])
|
||||
|
||||
const totalItems = countResult[0]?.count || 0
|
||||
const totalPages = Math.ceil(totalItems / PAGE_SIZE)
|
||||
|
||||
return {
|
||||
data: cards,
|
||||
pagination: {
|
||||
currentPage: page,
|
||||
pageSize: PAGE_SIZE,
|
||||
totalItems,
|
||||
totalPages,
|
||||
hasNextPage: page < totalPages,
|
||||
hasPreviousPage: page > 1
|
||||
}
|
||||
}
|
||||
})
|
||||
27
server/api/sync-cards.post.ts
Normal file
27
server/api/sync-cards.post.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { syncCardsWithDatabase } from '../services/cardSync.service'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const folderPath = config.pathFiles || process.env.PATH_FILES
|
||||
|
||||
if (!folderPath) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: 'PATH_FILES not configured'
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await syncCardsWithDatabase(folderPath)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
...result
|
||||
}
|
||||
} catch (error: any) {
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
message: error.message
|
||||
})
|
||||
}
|
||||
})
|
||||
21
server/api/test/test-db-sync.post.ts
Normal file
21
server/api/test/test-db-sync.post.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { syncCardsWithDatabase } from '../../services/cardSync.service'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const folderPath = config.pathFiles || process.env.PATH_FILES || 'mnt/media/files/music'
|
||||
|
||||
try {
|
||||
const result = await syncCardsWithDatabase(folderPath)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
...result
|
||||
}
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
}
|
||||
}
|
||||
})
|
||||
29
server/api/test/test-scanner.get.ts
Normal file
29
server/api/test/test-scanner.get.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { scanMusicFolder } from '../../utils/fileScanner'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const config = useRuntimeConfig()
|
||||
const folderPath = config.pathFiles || process.env.PATH_FILES || 'mnt/media/files/music'
|
||||
|
||||
try {
|
||||
// Test 1: Vérifier que le dossier existe
|
||||
const { access } = await import('node:fs/promises')
|
||||
await access(folderPath)
|
||||
|
||||
// Test 2: Scanner le dossier
|
||||
const cards = await scanMusicFolder(folderPath)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
folderPath,
|
||||
cardsFound: cards.length,
|
||||
cards: cards.slice(0, 5), // Afficher seulement les 5 premiers
|
||||
sample: cards[0] // Un exemple complet
|
||||
}
|
||||
} catch (error: any) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
folderPath
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,93 +0,0 @@
|
||||
import { eventHandler, getQuery } from 'h3'
|
||||
import { getDatabase } from '../../utils/database'
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const db = getDatabase()
|
||||
const query = getQuery(event)
|
||||
|
||||
// Paramètres de pagination
|
||||
const page = parseInt(query.page as string) || 1
|
||||
const limit = parseInt(query.limit as string) || 50
|
||||
const offset = (page - 1) * limit
|
||||
|
||||
// Filtres optionnels
|
||||
const boxId = query.boxId as string | undefined
|
||||
const side = query.side as string | undefined
|
||||
|
||||
// Construction de la requête
|
||||
let sql = `
|
||||
SELECT
|
||||
t.id,
|
||||
t.box_id,
|
||||
t.side,
|
||||
t.track_order,
|
||||
t.title,
|
||||
t.artist_id,
|
||||
t.start,
|
||||
t.link,
|
||||
t.cover_id,
|
||||
t.url,
|
||||
t.type,
|
||||
a.name as artist_name
|
||||
FROM tracks t
|
||||
LEFT JOIN artists a ON t.artist_id = a.id
|
||||
WHERE t.type = 'compilation'
|
||||
`
|
||||
|
||||
const params: any[] = []
|
||||
|
||||
if (boxId) {
|
||||
sql += ' AND t.box_id = ?'
|
||||
params.push(boxId)
|
||||
}
|
||||
|
||||
if (side) {
|
||||
sql += ' AND t.side = ?'
|
||||
params.push(side)
|
||||
}
|
||||
|
||||
sql += ' ORDER BY t.box_id, t.side, t.track_order'
|
||||
sql += ' LIMIT ? OFFSET ?'
|
||||
params.push(limit, offset)
|
||||
|
||||
const tracks = db.prepare(sql).all(...params)
|
||||
|
||||
// Compter le total pour la pagination
|
||||
let countSql = "SELECT COUNT(*) as total FROM tracks WHERE type = 'compilation'"
|
||||
const countParams: any[] = []
|
||||
|
||||
if (boxId) {
|
||||
countSql += ' AND box_id = ?'
|
||||
countParams.push(boxId)
|
||||
}
|
||||
|
||||
if (side) {
|
||||
countSql += ' AND side = ?'
|
||||
countParams.push(side)
|
||||
}
|
||||
|
||||
const { total } = db.prepare(countSql).get(...countParams) as { total: number }
|
||||
|
||||
return {
|
||||
tracks: tracks.map((track: any) => ({
|
||||
id: track.id,
|
||||
boxId: track.box_id,
|
||||
side: track.side,
|
||||
order: track.track_order,
|
||||
title: track.title,
|
||||
artist: track.artist_id,
|
||||
artistName: track.artist_name,
|
||||
start: track.start,
|
||||
link: track.link,
|
||||
coverId: track.cover_id,
|
||||
url: track.url,
|
||||
type: track.type
|
||||
})),
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit)
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,93 +0,0 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
import { eventHandler } from 'h3'
|
||||
import { getCardFromDate } from '../../../utils/cards'
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const dirPath = path.join(process.cwd(), '/mnt/media/files/music')
|
||||
const urlPrefix = `https://files.erudi.fr/music`
|
||||
|
||||
try {
|
||||
let allTracks: any[] = []
|
||||
|
||||
const items = await fs.promises.readdir(dirPath, { withFileTypes: true })
|
||||
|
||||
// Process files
|
||||
const files = items
|
||||
.filter((item) => item.isFile() && !item.name.startsWith('.') && !item.name.endsWith('.jpg'))
|
||||
.map((item) => item.name)
|
||||
|
||||
// Process folders
|
||||
const folders = items
|
||||
.filter((item) => item.isDirectory() && !item.name.startsWith('.'))
|
||||
.map((folder, index) => ({
|
||||
id: `folder-${index}`,
|
||||
boxId: 'ESFOLDER',
|
||||
title: folder.name.replace(/_/g, ' ').trim(),
|
||||
type: 'folder',
|
||||
order: 0,
|
||||
date: new Date(),
|
||||
card: getCardFromDate(new Date())
|
||||
}))
|
||||
|
||||
const tracks = files.map((file, index) => {
|
||||
const EXT_RE = /\.(mp3|flac|wav|opus)$/i
|
||||
const nameWithoutExt = file.replace(EXT_RE, '')
|
||||
|
||||
// On split sur __
|
||||
const parts = nameWithoutExt.split('__')
|
||||
let stamp = parts[0] || ''
|
||||
let artist = parts[1] || ''
|
||||
let title = parts[2] || ''
|
||||
|
||||
title = title.replaceAll('_', ' ')
|
||||
artist = artist.replaceAll('_', ' ')
|
||||
|
||||
// Parser la date depuis le stamp
|
||||
let year = 2020,
|
||||
month = 1,
|
||||
day = 1,
|
||||
hour = 0
|
||||
if (stamp.length === 10) {
|
||||
year = Number(stamp.slice(0, 4))
|
||||
month = Number(stamp.slice(4, 6))
|
||||
day = Number(stamp.slice(6, 8))
|
||||
hour = Number(stamp.slice(8, 10))
|
||||
}
|
||||
|
||||
const date = new Date(year, month - 1, day, hour)
|
||||
const card = getCardFromDate(date)
|
||||
const url = `${urlPrefix}/${encodeURIComponent(file)}`
|
||||
const coverId = `${urlPrefix}/cover/${encodeURIComponent(file).replace(EXT_RE, '.jpg')}`
|
||||
|
||||
return {
|
||||
id: Number(`${year}${index + 1}`),
|
||||
boxId: `ESPLAYLIST`,
|
||||
year,
|
||||
date,
|
||||
title: title.trim(),
|
||||
artist: artist.trim(),
|
||||
url,
|
||||
coverId,
|
||||
card,
|
||||
order: 0,
|
||||
type: 'playlist'
|
||||
}
|
||||
})
|
||||
|
||||
tracks.sort((a, b) => b.date.getTime() - a.date.getTime())
|
||||
// Combine folders and tracks
|
||||
const allItems = [...folders, ...tracks]
|
||||
|
||||
// Sort by date (newest first) and assign order
|
||||
allItems.sort((a, b) => b.date.getTime() - a.date.getTime())
|
||||
allItems.forEach((item, i) => (item.order = i + 1))
|
||||
|
||||
return allItems
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: (error as Error).message
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,91 +0,0 @@
|
||||
import { eventHandler, getQuery } from 'h3'
|
||||
import { getDatabase } from '../../utils/database'
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const db = getDatabase()
|
||||
const query = getQuery(event)
|
||||
|
||||
const search = (query.search as string) || ''
|
||||
const page = parseInt(query.page as string) || 1
|
||||
const limit = parseInt(query.limit as string) || 50
|
||||
const offset = (page - 1) * limit
|
||||
|
||||
// Construction de la requête de recherche
|
||||
let sql = `
|
||||
SELECT
|
||||
t.id,
|
||||
t.box_id,
|
||||
t.side,
|
||||
t.track_order,
|
||||
t.title,
|
||||
t.artist_id,
|
||||
t.start,
|
||||
t.link,
|
||||
t.cover_id,
|
||||
t.url,
|
||||
t.type,
|
||||
a.name as artist_name,
|
||||
a.url as artist_url
|
||||
FROM tracks t
|
||||
LEFT JOIN artists a ON t.artist_id = a.id
|
||||
WHERE 1=1
|
||||
`
|
||||
|
||||
const params: any[] = []
|
||||
|
||||
// Recherche par titre ou artiste
|
||||
if (search) {
|
||||
sql += ` AND (t.title LIKE ? OR a.name LIKE ?)`
|
||||
const searchPattern = `%${search}%`
|
||||
params.push(searchPattern, searchPattern)
|
||||
}
|
||||
|
||||
sql += ' ORDER BY t.box_id, t.track_order'
|
||||
sql += ' LIMIT ? OFFSET ?'
|
||||
params.push(limit, offset)
|
||||
|
||||
const tracks = db.prepare(sql).all(...params)
|
||||
|
||||
// Compter le total
|
||||
let countSql = `
|
||||
SELECT COUNT(*) as total
|
||||
FROM tracks t
|
||||
LEFT JOIN artists a ON t.artist_id = a.id
|
||||
WHERE 1=1
|
||||
`
|
||||
|
||||
const countParams: any[] = []
|
||||
|
||||
if (search) {
|
||||
countSql += ` AND (t.title LIKE ? OR a.name LIKE ?)`
|
||||
const searchPattern = `%${search}%`
|
||||
countParams.push(searchPattern, searchPattern)
|
||||
}
|
||||
|
||||
const { total } = db.prepare(countSql).get(...countParams) as { total: number }
|
||||
|
||||
return {
|
||||
tracks: tracks.map((track: any) => ({
|
||||
id: track.id,
|
||||
boxId: track.box_id,
|
||||
side: track.side,
|
||||
order: track.track_order,
|
||||
title: track.title,
|
||||
artist: track.artist_id,
|
||||
artistName: track.artist_name,
|
||||
artistUrl: track.artist_url,
|
||||
start: track.start,
|
||||
link: track.link,
|
||||
coverId: track.cover_id,
|
||||
url: track.url,
|
||||
type: track.type
|
||||
})),
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit)
|
||||
},
|
||||
search
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user