diff --git a/src/app/api/audio/convert/chapter/route.ts b/src/app/api/audiobook/chapter/route.ts similarity index 100% rename from src/app/api/audio/convert/chapter/route.ts rename to src/app/api/audiobook/chapter/route.ts diff --git a/src/app/api/audio/convert/route.ts b/src/app/api/audiobook/route.ts similarity index 91% rename from src/app/api/audio/convert/route.ts rename to src/app/api/audiobook/route.ts index 4c4a3f3..b37dfae 100644 --- a/src/app/api/audio/convert/route.ts +++ b/src/app/api/audiobook/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { spawn } from 'child_process'; -import { writeFile, readFile, mkdir, unlink, readdir } from 'fs/promises'; +import { writeFile, readFile, mkdir, unlink, readdir, rm } from 'fs/promises'; import { existsSync, createReadStream } from 'fs'; import { join } from 'path'; import { randomUUID } from 'crypto'; @@ -378,4 +378,31 @@ function streamFile(filePath: string, format: string) { 'Cache-Control': 'no-cache', }, }); +} +export async function DELETE(request: NextRequest) { + try { + const bookId = request.nextUrl.searchParams.get('bookId'); + if (!bookId) { + return NextResponse.json({ error: 'Missing bookId parameter' }, { status: 400 }); + } + + const docstoreDir = join(process.cwd(), 'docstore'); + const intermediateDir = join(docstoreDir, `${bookId}-audiobook`); + + // If directory doesn't exist, consider it already reset + if (!existsSync(intermediateDir)) { + return NextResponse.json({ success: true, existed: false }); + } + + // Recursively delete the entire audiobook directory + await rm(intermediateDir, { recursive: true, force: true }); + + return NextResponse.json({ success: true, existed: true }); + } catch (error) { + console.error('Error resetting audiobook:', error); + return NextResponse.json( + { error: 'Failed to reset audiobook' }, + { status: 500 } + ); + } } \ No newline at end of file diff --git a/src/app/api/audio/convert/chapters/route.ts b/src/app/api/audiobook/status/route.ts similarity index 67% rename from src/app/api/audio/convert/chapters/route.ts rename to src/app/api/audiobook/status/route.ts index 76efd83..d682a77 100644 --- a/src/app/api/audio/convert/chapters/route.ts +++ b/src/app/api/audiobook/status/route.ts @@ -1,5 +1,5 @@ import { NextRequest, NextResponse } from 'next/server'; -import { readdir, readFile, rm } from 'fs/promises'; +import { readdir, readFile } from 'fs/promises'; import { existsSync } from 'fs'; import { join } from 'path'; @@ -69,30 +69,4 @@ export async function GET(request: NextRequest) { } } -export async function DELETE(request: NextRequest) { - try { - const bookId = request.nextUrl.searchParams.get('bookId'); - if (!bookId) { - return NextResponse.json({ error: 'Missing bookId parameter' }, { status: 400 }); - } - const docstoreDir = join(process.cwd(), 'docstore'); - const intermediateDir = join(docstoreDir, `${bookId}-audiobook`); - - // If directory doesn't exist, consider it already reset - if (!existsSync(intermediateDir)) { - return NextResponse.json({ success: true, existed: false }); - } - - // Recursively delete the entire audiobook directory - await rm(intermediateDir, { recursive: true, force: true }); - - return NextResponse.json({ success: true, existed: true }); - } catch (error) { - console.error('Error resetting audiobook:', error); - return NextResponse.json( - { error: 'Failed to reset audiobook' }, - { status: 500 } - ); - } -} diff --git a/src/components/AudiobookExportModal.tsx b/src/components/AudiobookExportModal.tsx index 51fc052..c9a022c 100644 --- a/src/components/AudiobookExportModal.tsx +++ b/src/components/AudiobookExportModal.tsx @@ -61,7 +61,7 @@ export function AudiobookExportModal({ setIsLoadingExisting(true); } try { - const response = await fetch(`/api/audio/convert/chapters?bookId=${documentId}`); + const response = await fetch(`/api/audiobook/status?bookId=${documentId}`); if (response.ok) { const data = await response.json(); if (data.exists && data.chapters.length > 0) { @@ -241,7 +241,7 @@ export function AudiobookExportModal({ const performDeleteChapter = useCallback(async () => { if (!bookId || !pendingDeleteChapter) return; try { - const response = await fetch(`/api/audio/convert/chapter?bookId=${bookId}&chapterIndex=${pendingDeleteChapter.index}`, { + const response = await fetch(`/api/audiobook/chapter?bookId=${bookId}&chapterIndex=${pendingDeleteChapter.index}`, { method: 'DELETE' }); if (!response.ok) { @@ -261,7 +261,7 @@ export function AudiobookExportModal({ const targetBookId = bookId || documentId; if (!targetBookId) return; try { - const resp = await fetch(`/api/audio/convert/chapters?bookId=${targetBookId}`, { method: 'DELETE' }); + const resp = await fetch(`/api/audiobook?bookId=${targetBookId}`, { method: 'DELETE' }); if (!resp.ok) { throw new Error('Reset failed'); } @@ -281,7 +281,7 @@ export function AudiobookExportModal({ if (!chapter.bookId) return; try { - const response = await fetch(`/api/audio/convert/chapter?bookId=${chapter.bookId}&chapterIndex=${chapter.index}`); + const response = await fetch(`/api/audiobook/chapter?bookId=${chapter.bookId}&chapterIndex=${chapter.index}`); if (!response.ok) throw new Error('Download failed'); const blob = await response.blob(); @@ -306,7 +306,7 @@ export function AudiobookExportModal({ setIsCombining(true); try { - const response = await fetch(`/api/audio/convert?bookId=${bookId}&format=${format}`); + const response = await fetch(`/api/audiobook?bookId=${bookId}&format=${format}`); if (!response.ok) throw new Error('Download failed'); const reader = response.body?.getReader(); diff --git a/src/contexts/EPUBContext.tsx b/src/contexts/EPUBContext.tsx index 1922d2b..b555ff5 100644 --- a/src/contexts/EPUBContext.tsx +++ b/src/contexts/EPUBContext.tsx @@ -339,7 +339,7 @@ export function EPUBProvider({ children }: { children: ReactNode }) { const existingIndices = new Set(); if (bookId) { try { - const existingResponse = await fetch(`/api/audio/convert/chapters?bookId=${bookId}`); + const existingResponse = await fetch(`/api/audiobook/status?bookId=${bookId}`); if (existingResponse.ok) { const existingData = await existingResponse.json(); if (existingData.chapters && existingData.chapters.length > 0) { @@ -469,7 +469,7 @@ export function EPUBProvider({ children }: { children: ReactNode }) { } // Send to server for conversion and storage - const convertResponse = await fetch('/api/audio/convert', { + const convertResponse = await fetch(`/api/audiobook`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -645,7 +645,7 @@ export function EPUBProvider({ children }: { children: ReactNode }) { } // Send to server for conversion and storage - const convertResponse = await fetch('/api/audio/convert', { + const convertResponse = await fetch('/api/audiobook', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/src/contexts/PDFContext.tsx b/src/contexts/PDFContext.tsx index dfdb6b1..fb03eb8 100644 --- a/src/contexts/PDFContext.tsx +++ b/src/contexts/PDFContext.tsx @@ -299,7 +299,7 @@ export function PDFProvider({ children }: { children: ReactNode }) { const existingIndices = new Set(); if (bookId) { try { - const existingResponse = await fetch(`/api/audio/convert/chapters?bookId=${bookId}`); + const existingResponse = await fetch(`/api/audiobook/status?bookId=${bookId}`); if (existingResponse.ok) { const existingData = await existingResponse.json(); if (existingData.chapters && existingData.chapters.length > 0) { @@ -399,7 +399,7 @@ export function PDFProvider({ children }: { children: ReactNode }) { } // Send to server for conversion and storage - const convertResponse = await fetch('/api/audio/convert', { + const convertResponse = await fetch(`/api/audiobook`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -587,7 +587,7 @@ export function PDFProvider({ children }: { children: ReactNode }) { } // Send to server for conversion and storage - const convertResponse = await fetch('/api/audio/convert', { + const convertResponse = await fetch('/api/audiobook', { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/tests/api.spec.ts b/tests/api.spec.ts index ddfd974..dde7756 100644 --- a/tests/api.spec.ts +++ b/tests/api.spec.ts @@ -9,9 +9,9 @@ test.describe('API health checks', () => { expect(json.voices.length).toBeGreaterThan(0); }); - test('GET /api/audio/convert/chapters returns 200 with exists flag and chapters array', async ({ request }) => { + test('GET /api/audiobook/status returns 200 with exists flag and chapters array', async ({ request }) => { const bookId = `healthcheck-${Date.now()}`; - const res = await request.get(`/api/audio/convert/chapters?bookId=${bookId}`); + const res = await request.get(`/api/audiobook/status?bookId=${bookId}`); expect(res.ok()).toBeTruthy(); const json = await res.json(); expect(json).toHaveProperty('exists'); diff --git a/tests/export.spec.ts b/tests/export.spec.ts index a25bfe4..77324a7 100644 --- a/tests/export.spec.ts +++ b/tests/export.spec.ts @@ -67,7 +67,7 @@ async function getAudioDurationSeconds(filePath: string) { } async function expectChaptersBackendState(page: Page, bookId: string) { - const res = await page.request.get(`/api/audio/convert/chapters?bookId=${bookId}`); + const res = await page.request.get(`/api/audiobook/status?bookId=${bookId}`); expect(res.ok()).toBeTruthy(); const json = await res.json(); return json; @@ -271,7 +271,7 @@ test.describe('Audiobook export', () => { ).toBeVisible({ timeout: 60_000 }); // Backend should report no existing chapters for this bookId - const res = await page.request.get(`/api/audio/convert/chapters?bookId=${bookId}`); + const res = await page.request.get(`/api/audiobook/status?bookId=${bookId}`); expect(res.ok()).toBeTruthy(); const json = await res.json(); expect(json.exists).toBe(false);