Add Lidarr/Readarr backend support
- Add MUSIC and BOOK to MediaType enum - Add permission flags for music/book requests - Create Lidarr API adapter (artist/album search, add, remove) - Create Readarr API adapter (book/author search, add, remove) - Add Lidarr/Readarr settings interfaces and routes - Add music and book API routes for search/detail - Register all new routes in main router and settings router
This commit is contained in:
135
server/routes/music.ts
Normal file
135
server/routes/music.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import LidarrAPI from '@server/api/servarr/lidarr';
|
||||
import { getSettings } from '@server/lib/settings';
|
||||
import logger from '@server/logger';
|
||||
import { Router } from 'express';
|
||||
|
||||
const musicRoutes = Router();
|
||||
|
||||
musicRoutes.get('/search', async (req, res, next) => {
|
||||
const { query } = req.query;
|
||||
|
||||
if (!query || typeof query !== 'string') {
|
||||
return res.status(400).json({ error: 'Query parameter is required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const settings = getSettings();
|
||||
const lidarrSettings = settings.lidarr.find((l) => l.isDefault);
|
||||
|
||||
if (!lidarrSettings) {
|
||||
return res.status(404).json({ error: 'No default Lidarr server configured' });
|
||||
}
|
||||
|
||||
const lidarr = new LidarrAPI({
|
||||
apiKey: lidarrSettings.apiKey,
|
||||
url: LidarrAPI.buildUrl(lidarrSettings, '/api/v1'),
|
||||
});
|
||||
|
||||
const artists = await lidarr.searchArtist(query);
|
||||
|
||||
const results = artists.slice(0, 20).map((artist) => ({
|
||||
id: artist.foreignArtistId,
|
||||
mediaType: 'music',
|
||||
name: artist.artistName,
|
||||
artistType: artist.artistType,
|
||||
disambiguation: artist.disambiguation,
|
||||
status: artist.status,
|
||||
images: artist.images,
|
||||
genres: artist.genres,
|
||||
foreignArtistId: artist.foreignArtistId,
|
||||
statistics: artist.statistics,
|
||||
inLibrary: !!artist.id && artist.id > 0,
|
||||
}));
|
||||
|
||||
return res.status(200).json({
|
||||
results,
|
||||
totalResults: results.length,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('Failed to search music', {
|
||||
label: 'Music API',
|
||||
message: e.message,
|
||||
});
|
||||
next({ status: 500, message: 'Failed to search music' });
|
||||
}
|
||||
});
|
||||
|
||||
musicRoutes.get('/artist/:mbId', async (req, res, next) => {
|
||||
try {
|
||||
const settings = getSettings();
|
||||
const lidarrSettings = settings.lidarr.find((l) => l.isDefault);
|
||||
|
||||
if (!lidarrSettings) {
|
||||
return res.status(404).json({ error: 'No default Lidarr server configured' });
|
||||
}
|
||||
|
||||
const lidarr = new LidarrAPI({
|
||||
apiKey: lidarrSettings.apiKey,
|
||||
url: LidarrAPI.buildUrl(lidarrSettings, '/api/v1'),
|
||||
});
|
||||
|
||||
// Search by MusicBrainz ID
|
||||
const artists = await lidarr.searchArtist(`lidarr:${req.params.mbId}`);
|
||||
const artist = artists[0];
|
||||
|
||||
if (!artist) {
|
||||
return res.status(404).json({ error: 'Artist not found' });
|
||||
}
|
||||
|
||||
// Get albums if artist is in library
|
||||
let albums: any[] = [];
|
||||
const existingArtist = await lidarr.getArtistByMbId(req.params.mbId);
|
||||
if (existingArtist) {
|
||||
albums = await lidarr.getAlbums(existingArtist.id);
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
...artist,
|
||||
albums,
|
||||
inLibrary: !!existingArtist,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('Failed to get artist details', {
|
||||
label: 'Music API',
|
||||
message: e.message,
|
||||
});
|
||||
next({ status: 500, message: 'Failed to get artist details' });
|
||||
}
|
||||
});
|
||||
|
||||
musicRoutes.get('/album/search', async (req, res, next) => {
|
||||
const { query } = req.query;
|
||||
|
||||
if (!query || typeof query !== 'string') {
|
||||
return res.status(400).json({ error: 'Query parameter is required' });
|
||||
}
|
||||
|
||||
try {
|
||||
const settings = getSettings();
|
||||
const lidarrSettings = settings.lidarr.find((l) => l.isDefault);
|
||||
|
||||
if (!lidarrSettings) {
|
||||
return res.status(404).json({ error: 'No default Lidarr server configured' });
|
||||
}
|
||||
|
||||
const lidarr = new LidarrAPI({
|
||||
apiKey: lidarrSettings.apiKey,
|
||||
url: LidarrAPI.buildUrl(lidarrSettings, '/api/v1'),
|
||||
});
|
||||
|
||||
const albums = await lidarr.searchAlbum(query);
|
||||
|
||||
return res.status(200).json({
|
||||
results: albums.slice(0, 20),
|
||||
totalResults: albums.length,
|
||||
});
|
||||
} catch (e) {
|
||||
logger.error('Failed to search albums', {
|
||||
label: 'Music API',
|
||||
message: e.message,
|
||||
});
|
||||
next({ status: 500, message: 'Failed to search albums' });
|
||||
}
|
||||
});
|
||||
|
||||
export default musicRoutes;
|
||||
Reference in New Issue
Block a user