117 lines
3.9 KiB
JavaScript
117 lines
3.9 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const axios = require('axios');
|
|
const db = require('../db');
|
|
|
|
// GET /api/search?q=query
|
|
router.get('/', async (req, res) => {
|
|
const query = req.query.q;
|
|
if (!query || !query.trim()) {
|
|
return res.json({ status: 'success', data: [] });
|
|
}
|
|
|
|
const cleanQuery = query.trim();
|
|
|
|
try {
|
|
// 1. Search local PostgreSQL database first
|
|
const localTracks = await db.searchTracks(cleanQuery, 10);
|
|
const localMap = new Map();
|
|
localTracks.forEach(t => {
|
|
const key = `${t.artist.toLowerCase()} - ${t.title.toLowerCase()}`;
|
|
localMap.set(key, t);
|
|
});
|
|
|
|
// 2. Query Deezer public search API (no API key needed, high quality covers & metadata)
|
|
let onlineTracks = [];
|
|
try {
|
|
const deezerRes = await axios.get(`https://api.deezer.com/search?q=${encodeURIComponent(cleanQuery)}&limit=15`, {
|
|
timeout: 4000
|
|
});
|
|
|
|
if (deezerRes.data && Array.isArray(deezerRes.data.data)) {
|
|
onlineTracks = deezerRes.data.data.map(item => ({
|
|
title: item.title,
|
|
artist: item.artist ? item.artist.name : 'Unknown Artist',
|
|
cover_url: item.album && item.album.cover_medium ? item.album.cover_medium : (item.album ? item.album.cover : null),
|
|
preview_url: item.preview || null,
|
|
duration_sec: item.duration || 210,
|
|
deezer_id: item.id,
|
|
deezer_link: item.link || (item.id ? `https://www.deezer.com/track/${item.id}` : null)
|
|
}));
|
|
}
|
|
} catch (e) {
|
|
console.warn('[Search] Deezer search error:', e.message);
|
|
}
|
|
|
|
// 3. Merge results: Local tracks first, then online tracks not yet in local
|
|
const merged = [];
|
|
const seen = new Set();
|
|
|
|
// Add local tracks first
|
|
for (const lt of localTracks) {
|
|
const key = `${lt.artist.toLowerCase()} - ${lt.title.toLowerCase()}`;
|
|
seen.add(key);
|
|
merged.push({
|
|
id: lt.id,
|
|
title: lt.title,
|
|
artist: lt.artist,
|
|
camelot_key: lt.camelot_key,
|
|
bpm: lt.bpm,
|
|
music_key: lt.music_key,
|
|
cover_url: lt.cover_url,
|
|
preview_url: lt.preview_url,
|
|
duration_sec: lt.duration_sec,
|
|
energy: lt.energy,
|
|
danceability: lt.danceability,
|
|
happiness: lt.happiness,
|
|
deezer_id: lt.deezer_id || null,
|
|
deezer_link: lt.deezer_link || (lt.deezer_id ? `https://www.deezer.com/track/${lt.deezer_id}` : null),
|
|
is_local: true,
|
|
is_corrected: lt.is_corrected
|
|
});
|
|
}
|
|
|
|
// Add online tracks, enriching with local data if available
|
|
for (const ot of onlineTracks) {
|
|
const key = `${ot.artist.toLowerCase()} - ${ot.title.toLowerCase()}`;
|
|
if (seen.has(key)) continue;
|
|
seen.add(key);
|
|
|
|
let existingLocal = localMap.get(key);
|
|
if (!existingLocal) {
|
|
existingLocal = await db.findTrack(ot.artist, ot.title);
|
|
}
|
|
merged.push({
|
|
title: ot.title,
|
|
artist: ot.artist,
|
|
cover_url: ot.cover_url,
|
|
preview_url: ot.preview_url,
|
|
duration_sec: ot.duration_sec,
|
|
deezer_id: ot.deezer_id,
|
|
deezer_link: ot.deezer_link,
|
|
camelot_key: existingLocal ? existingLocal.camelot_key : null,
|
|
bpm: existingLocal ? existingLocal.bpm : null,
|
|
music_key: existingLocal ? existingLocal.music_key : null,
|
|
energy: existingLocal ? existingLocal.energy : null,
|
|
danceability: existingLocal ? existingLocal.danceability : null,
|
|
happiness: existingLocal ? existingLocal.happiness : null,
|
|
is_local: !!existingLocal,
|
|
is_corrected: existingLocal ? existingLocal.is_corrected : false
|
|
});
|
|
|
|
if (merged.length >= 15) break;
|
|
}
|
|
|
|
res.json({
|
|
status: 'success',
|
|
data: merged
|
|
});
|
|
|
|
} catch (err) {
|
|
console.error('[Search] General search error:', err.message);
|
|
res.status(500).json({ error: 'Search failed' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|