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

94
server/routes/book.ts Normal file
View File

@@ -0,0 +1,94 @@
import ReadarrAPI from '@server/api/servarr/readarr';
import { getSettings } from '@server/lib/settings';
import logger from '@server/logger';
import { Router } from 'express';
const bookRoutes = Router();
bookRoutes.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 readarrSettings = settings.readarr.find((r) => r.isDefault);
if (!readarrSettings) {
return res.status(404).json({ error: 'No default Readarr server configured' });
}
const readarr = new ReadarrAPI({
apiKey: readarrSettings.apiKey,
url: ReadarrAPI.buildUrl(readarrSettings, '/api/v1'),
});
const books = await readarr.searchBook(query);
const results = books.slice(0, 20).map((book) => ({
id: book.foreignBookId,
mediaType: 'book',
title: book.title,
overview: book.overview,
releaseDate: book.releaseDate,
images: book.images,
genres: book.genres,
pageCount: book.pageCount,
author: book.author ? {
id: book.author.foreignAuthorId,
name: book.author.authorName,
} : null,
foreignBookId: book.foreignBookId,
}));
return res.status(200).json({
results,
totalResults: results.length,
});
} catch (e) {
logger.error('Failed to search books', {
label: 'Book API',
message: e.message,
});
next({ status: 500, message: 'Failed to search books' });
}
});
bookRoutes.get('/author/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 readarrSettings = settings.readarr.find((r) => r.isDefault);
if (!readarrSettings) {
return res.status(404).json({ error: 'No default Readarr server configured' });
}
const readarr = new ReadarrAPI({
apiKey: readarrSettings.apiKey,
url: ReadarrAPI.buildUrl(readarrSettings, '/api/v1'),
});
const authors = await readarr.searchAuthor(query);
return res.status(200).json({
results: authors.slice(0, 20),
totalResults: authors.length,
});
} catch (e) {
logger.error('Failed to search authors', {
label: 'Book API',
message: e.message,
});
next({ status: 500, message: 'Failed to search authors' });
}
});
export default bookRoutes;