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:
root
2026-04-03 21:05:21 -05:00
parent dc40ca413c
commit 1cf0d541d6
11 changed files with 985 additions and 0 deletions

View File

@@ -0,0 +1,106 @@
import LidarrAPI from '@server/api/servarr/lidarr';
import type { LidarrSettings } from '@server/lib/settings';
import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import { Router } from 'express';
const lidarrRoutes = Router();
lidarrRoutes.get('/', (_req, res) => {
const settings = getSettings();
res.status(200).json(settings.lidarr);
});
lidarrRoutes.post('/', async (req, res) => {
const settings = getSettings();
const newLidarr = req.body as LidarrSettings;
const lastItem = settings.lidarr[settings.lidarr.length - 1];
newLidarr.id = lastItem ? lastItem.id + 1 : 0;
if (req.body.isDefault) {
settings.lidarr.forEach((instance) => {
instance.isDefault = false;
});
}
settings.lidarr = [...settings.lidarr, newLidarr];
await settings.save();
return res.status(201).json(newLidarr);
});
lidarrRoutes.post<
undefined,
Record<string, unknown>,
LidarrSettings
>('/test', async (req, res, next) => {
try {
const lidarr = new LidarrAPI({
apiKey: req.body.apiKey,
url: LidarrAPI.buildUrl(req.body, '/api/v1'),
});
const profiles = await lidarr.getProfiles();
const folders = await lidarr.getRootFolders();
const tags = await lidarr.getTags();
const metadataProfiles = await lidarr.getMetadataProfiles();
return res.status(200).json({
profiles,
rootFolders: folders.map((folder) => ({
id: folder.id,
path: folder.path,
})),
tags,
metadataProfiles,
});
} catch (e) {
logger.error('Failed to test Lidarr', {
label: 'Lidarr',
message: e.message,
});
next({ status: 500, message: 'Failed to connect to Lidarr' });
}
});
lidarrRoutes.put<{ id: string }>('/:id', async (req, res, next) => {
const settings = getSettings();
const lidarrIndex = settings.lidarr.findIndex(
(r) => r.id === Number(req.params.id)
);
if (lidarrIndex === -1) {
return next({ status: 404, message: 'Lidarr server not found.' });
}
if (req.body.isDefault) {
settings.lidarr.forEach((instance) => {
instance.isDefault = false;
});
}
settings.lidarr[lidarrIndex] = {
...settings.lidarr[lidarrIndex],
...req.body,
id: Number(req.params.id),
} as LidarrSettings;
await settings.save();
return res.status(200).json(settings.lidarr[lidarrIndex]);
});
lidarrRoutes.delete<{ id: string }>('/:id', async (req, res, next) => {
const settings = getSettings();
const lidarrIndex = settings.lidarr.findIndex(
(r) => r.id === Number(req.params.id)
);
if (lidarrIndex === -1) {
return next({ status: 404, message: 'Lidarr server not found.' });
}
const removed = settings.lidarr.splice(lidarrIndex, 1);
await settings.save();
return res.status(200).json(removed[0]);
});
export default lidarrRoutes;