mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 12:50:09 +00:00
Add ebook-sidecar APIs and UI integration
Introduce ebook-sidecar support: add new API routes for ebook workflows (ebook-status, fetch-ebook, interactive-search-ebook, select-ebook) that handle searching, selection, request creation, approval, and download routing (Anna's Archive direct downloads vs indexer downloads). Update admin approval flow to understand request.type (audiobook | ebook), handle pre-selected ebook torrents (including special handling for Anna's Archive with direct download jobs and download history), and enqueue ebook-specific search/download jobs. Frontend changes: show request type badge in admin pending approvals and augment AudiobookDetailsModal to query ebook status, start fetch/interactive ebook searches, and surface toast notifications. Also include new request lifecycle handling (retryable/active statuses, approval logic, creating audiobook records for Plex-imported books) and ranking/normalization logic for interactive ebook search results. Other: various plumbing to integrate config checks, job queue calls, and download history storage for ebook downloads.
This commit is contained in:
@@ -76,26 +76,67 @@ export async function POST(
|
||||
// Update request based on action
|
||||
if (action === 'approve') {
|
||||
const jobQueue = getJobQueueService();
|
||||
const isEbookRequest = existingRequest.type === 'ebook';
|
||||
|
||||
// Check if request has a pre-selected torrent (from interactive search)
|
||||
if (existingRequest.selectedTorrent) {
|
||||
const selectedTorrent = existingRequest.selectedTorrent as any;
|
||||
|
||||
// User pre-selected a specific torrent - download that torrent directly
|
||||
logger.info(`Request ${id} has pre-selected torrent, starting download`, {
|
||||
requestId: id,
|
||||
userId: existingRequest.userId,
|
||||
adminId: req.user.sub,
|
||||
type: existingRequest.type,
|
||||
source: selectedTorrent.source,
|
||||
});
|
||||
|
||||
// Trigger download job with pre-selected torrent
|
||||
await jobQueue.addDownloadJob(
|
||||
existingRequest.id,
|
||||
{
|
||||
id: existingRequest.audiobook.id,
|
||||
title: existingRequest.audiobook.title,
|
||||
author: existingRequest.audiobook.author,
|
||||
},
|
||||
existingRequest.selectedTorrent as any
|
||||
);
|
||||
// Handle ebook requests with Anna's Archive source differently
|
||||
if (isEbookRequest && selectedTorrent.source === 'annas_archive') {
|
||||
// Create download history record for Anna's Archive
|
||||
const downloadHistory = await prisma.downloadHistory.create({
|
||||
data: {
|
||||
requestId: existingRequest.id,
|
||||
indexerName: "Anna's Archive",
|
||||
torrentName: `${existingRequest.audiobook.title} - ${existingRequest.audiobook.author}.${selectedTorrent.format || 'epub'}`,
|
||||
torrentSizeBytes: null,
|
||||
qualityScore: selectedTorrent.score || 100,
|
||||
selected: true,
|
||||
downloadClient: 'direct',
|
||||
downloadStatus: 'queued',
|
||||
},
|
||||
});
|
||||
|
||||
// Store all download URLs for retry purposes
|
||||
if (selectedTorrent.downloadUrls && selectedTorrent.downloadUrls.length > 0) {
|
||||
await prisma.downloadHistory.update({
|
||||
where: { id: downloadHistory.id },
|
||||
data: {
|
||||
torrentUrl: JSON.stringify(selectedTorrent.downloadUrls),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Trigger direct download job for Anna's Archive
|
||||
await jobQueue.addStartDirectDownloadJob(
|
||||
existingRequest.id,
|
||||
downloadHistory.id,
|
||||
selectedTorrent.downloadUrl,
|
||||
`${existingRequest.audiobook.title} - ${existingRequest.audiobook.author}.${selectedTorrent.format || 'epub'}`,
|
||||
undefined
|
||||
);
|
||||
} else {
|
||||
// Trigger download job with pre-selected torrent (audiobook or indexer ebook)
|
||||
await jobQueue.addDownloadJob(
|
||||
existingRequest.id,
|
||||
{
|
||||
id: existingRequest.audiobook.id,
|
||||
title: existingRequest.audiobook.title,
|
||||
author: existingRequest.audiobook.author,
|
||||
},
|
||||
selectedTorrent
|
||||
);
|
||||
}
|
||||
|
||||
// Update status to 'downloading' and clear selectedTorrent
|
||||
const updatedRequest = await prisma.request.update({
|
||||
@@ -119,7 +160,7 @@ export async function POST(
|
||||
await jobQueue.addNotificationJob(
|
||||
'request_approved',
|
||||
updatedRequest.id,
|
||||
existingRequest.audiobook.title,
|
||||
isEbookRequest ? `${existingRequest.audiobook.title} (Ebook)` : existingRequest.audiobook.title,
|
||||
existingRequest.audiobook.author,
|
||||
existingRequest.user.plexUsername || 'Unknown User'
|
||||
).catch((error) => {
|
||||
@@ -131,6 +172,7 @@ export async function POST(
|
||||
userId: updatedRequest.userId,
|
||||
audiobookTitle: existingRequest.audiobook.title,
|
||||
adminId: req.user.sub,
|
||||
type: existingRequest.type,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
@@ -144,6 +186,7 @@ export async function POST(
|
||||
requestId: id,
|
||||
userId: existingRequest.userId,
|
||||
adminId: req.user.sub,
|
||||
type: existingRequest.type,
|
||||
});
|
||||
|
||||
const updatedRequest = await prisma.request.update({
|
||||
@@ -160,19 +203,28 @@ export async function POST(
|
||||
},
|
||||
});
|
||||
|
||||
// Trigger search job
|
||||
await jobQueue.addSearchJob(updatedRequest.id, {
|
||||
id: updatedRequest.audiobook.id,
|
||||
title: updatedRequest.audiobook.title,
|
||||
author: updatedRequest.audiobook.author,
|
||||
asin: updatedRequest.audiobook.audibleAsin || undefined,
|
||||
});
|
||||
// Trigger appropriate search job based on request type
|
||||
if (isEbookRequest) {
|
||||
await jobQueue.addSearchEbookJob(updatedRequest.id, {
|
||||
id: updatedRequest.audiobook.id,
|
||||
title: updatedRequest.audiobook.title,
|
||||
author: updatedRequest.audiobook.author,
|
||||
asin: updatedRequest.audiobook.audibleAsin || undefined,
|
||||
});
|
||||
} else {
|
||||
await jobQueue.addSearchJob(updatedRequest.id, {
|
||||
id: updatedRequest.audiobook.id,
|
||||
title: updatedRequest.audiobook.title,
|
||||
author: updatedRequest.audiobook.author,
|
||||
asin: updatedRequest.audiobook.audibleAsin || undefined,
|
||||
});
|
||||
}
|
||||
|
||||
// Send notification for manual approval
|
||||
await jobQueue.addNotificationJob(
|
||||
'request_approved',
|
||||
updatedRequest.id,
|
||||
updatedRequest.audiobook.title,
|
||||
isEbookRequest ? `${updatedRequest.audiobook.title} (Ebook)` : updatedRequest.audiobook.title,
|
||||
updatedRequest.audiobook.author,
|
||||
updatedRequest.user.plexUsername || 'Unknown User'
|
||||
).catch((error) => {
|
||||
@@ -184,11 +236,14 @@ export async function POST(
|
||||
userId: updatedRequest.userId,
|
||||
audiobookTitle: updatedRequest.audiobook.title,
|
||||
adminId: req.user.sub,
|
||||
type: existingRequest.type,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: 'Request approved and search job triggered',
|
||||
message: isEbookRequest
|
||||
? 'Ebook request approved and ebook search job triggered'
|
||||
: 'Request approved and search job triggered',
|
||||
request: updatedRequest,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user