mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-02 20:30:10 +00:00
5a0cce7985
Introduces granular toggles for Anna's Archive and Indexer Search as ebook sources, updates settings UI to a three-section layout, and documents the new configuration. Adds per-indexer category configuration with separate tabs for audiobooks and ebooks, updates API routes and types for new settings, and ensures legacy config migration. Indexer grouping and file organization logic now support the new category structure and ebook source toggles.
165 lines
5.9 KiB
TypeScript
165 lines
5.9 KiB
TypeScript
/**
|
|
* Component: Fetch E-book API
|
|
* Documentation: documentation/integrations/ebook-sidecar.md
|
|
*
|
|
* Creates an ebook request for a completed audiobook request
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
|
import { prisma } from '@/lib/db';
|
|
import { getJobQueueService } from '@/lib/services/job-queue.service';
|
|
import { RMABLogger } from '@/lib/utils/logger';
|
|
|
|
const logger = RMABLogger.create('API.FetchEbook');
|
|
|
|
export async function POST(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
|
return requireAdmin(req, async () => {
|
|
try {
|
|
const { id: parentRequestId } = await params;
|
|
|
|
// Check which ebook sources are enabled
|
|
const [annasArchiveConfig, indexerSearchConfig, legacyConfig] = await Promise.all([
|
|
prisma.configuration.findUnique({ where: { key: 'ebook_annas_archive_enabled' } }),
|
|
prisma.configuration.findUnique({ where: { key: 'ebook_indexer_search_enabled' } }),
|
|
prisma.configuration.findUnique({ where: { key: 'ebook_sidecar_enabled' } }),
|
|
]);
|
|
|
|
// Legacy migration: check old key if new keys don't exist
|
|
const isAnnasArchiveEnabled = annasArchiveConfig?.value === 'true' ||
|
|
(annasArchiveConfig === null && legacyConfig?.value === 'true');
|
|
const isIndexerSearchEnabled = indexerSearchConfig?.value === 'true';
|
|
|
|
// If no sources are enabled, return error
|
|
if (!isAnnasArchiveEnabled && !isIndexerSearchEnabled) {
|
|
return NextResponse.json(
|
|
{ error: 'E-book sidecar feature is not enabled (no sources configured)' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// If only indexer search is enabled (not yet implemented), return error
|
|
if (!isAnnasArchiveEnabled && isIndexerSearchEnabled) {
|
|
return NextResponse.json(
|
|
{ error: 'E-book indexer search is not yet implemented. Enable Anna\'s Archive to fetch e-books.' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Get the parent request with audiobook data
|
|
const parentRequest = await prisma.request.findUnique({
|
|
where: { id: parentRequestId },
|
|
include: {
|
|
audiobook: true,
|
|
},
|
|
});
|
|
|
|
if (!parentRequest) {
|
|
return NextResponse.json(
|
|
{ error: 'Request not found' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
// Check if parent request is in completed state
|
|
if (!['downloaded', 'available'].includes(parentRequest.status)) {
|
|
return NextResponse.json(
|
|
{ error: `Cannot fetch e-book for request in ${parentRequest.status} status` },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
|
|
// Check if an ebook request already exists for this parent
|
|
const existingEbookRequest = await prisma.request.findFirst({
|
|
where: {
|
|
parentRequestId,
|
|
type: 'ebook',
|
|
deletedAt: null,
|
|
},
|
|
});
|
|
|
|
if (existingEbookRequest) {
|
|
// Check status - if failed/pending, we can retry
|
|
if (['failed', 'awaiting_search'].includes(existingEbookRequest.status)) {
|
|
// Reset and retry
|
|
await prisma.request.update({
|
|
where: { id: existingEbookRequest.id },
|
|
data: {
|
|
status: 'pending',
|
|
progress: 0,
|
|
errorMessage: null,
|
|
updatedAt: new Date(),
|
|
},
|
|
});
|
|
|
|
// Trigger search job
|
|
const jobQueue = getJobQueueService();
|
|
await jobQueue.addSearchEbookJob(existingEbookRequest.id, {
|
|
id: parentRequest.audiobook.id,
|
|
title: parentRequest.audiobook.title,
|
|
author: parentRequest.audiobook.author,
|
|
asin: parentRequest.audiobook.audibleAsin || undefined,
|
|
});
|
|
|
|
logger.info(`Retrying ebook request ${existingEbookRequest.id} for "${parentRequest.audiobook.title}"`);
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: 'E-book search retried',
|
|
requestId: existingEbookRequest.id,
|
|
});
|
|
}
|
|
|
|
// Already exists and not in a retryable state
|
|
return NextResponse.json({
|
|
success: false,
|
|
message: `E-book request already exists (status: ${existingEbookRequest.status})`,
|
|
requestId: existingEbookRequest.id,
|
|
});
|
|
}
|
|
|
|
// Create new ebook request
|
|
const ebookRequest = await prisma.request.create({
|
|
data: {
|
|
userId: parentRequest.userId,
|
|
audiobookId: parentRequest.audiobookId,
|
|
type: 'ebook',
|
|
parentRequestId,
|
|
status: 'pending',
|
|
progress: 0,
|
|
},
|
|
});
|
|
|
|
logger.info(`Created ebook request ${ebookRequest.id} for "${parentRequest.audiobook.title}"`);
|
|
|
|
// Trigger ebook search job
|
|
const jobQueue = getJobQueueService();
|
|
await jobQueue.addSearchEbookJob(ebookRequest.id, {
|
|
id: parentRequest.audiobook.id,
|
|
title: parentRequest.audiobook.title,
|
|
author: parentRequest.audiobook.author,
|
|
asin: parentRequest.audiobook.audibleAsin || undefined,
|
|
});
|
|
|
|
logger.info(`Triggered search_ebook job for request ${ebookRequest.id}`);
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
message: 'E-book request created and search started',
|
|
requestId: ebookRequest.id,
|
|
});
|
|
} catch (error) {
|
|
logger.error('Unexpected error', { error: error instanceof Error ? error.message : String(error) });
|
|
return NextResponse.json(
|
|
{ error: error instanceof Error ? error.message : 'Internal server error' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
});
|
|
});
|
|
}
|