Add manual-import and download-access features

Introduce manual import workflow and download permission support. Adds a Prisma migration and schema field (users.download_access) to track per-user download access, and updates admin UI to toggle global and per-user download access. Implements new APIs: filesystem browse, manual-import endpoint, download-access settings, audiobook download-status, and on-demand download-token generation. Adds frontend components for manual import and related tests, plus documentation for the manual-import feature and the documentation-agent prompt. Key files: prisma/migrations/20260212000000_add_download_access_permission/migration.sql, prisma/schema.prisma, src/app/api/admin/filesystem/browse/route.ts, src/app/api/admin/manual-import/route.ts, src/app/api/admin/settings/download-access/route.ts, src/app/api/requests/[id]/download-token/route.ts, src/app/api/audiobooks/[asin]/download-status/route.ts, and updated admin users pages/components and permissions util.
This commit is contained in:
kikootwo
2026-02-27 12:15:23 -05:00
parent 73c5fe14e7
commit edc56bc457
29 changed files with 2196 additions and 27 deletions
+10 -3
View File
@@ -56,8 +56,13 @@ export function AudiobookCard({
const [showToast, setShowToast] = useState(false);
const [error, setError] = useState<string | null>(null);
const [showModal, setShowModal] = useState(false);
const [localRequestStatus, setLocalRequestStatus] = useState<string | undefined>(undefined);
const status = getStatusConfig(audiobook);
// Build a display-only audiobook with the local status override
const displayAudiobook = localRequestStatus !== undefined
? { ...audiobook, requestStatus: localRequestStatus }
: audiobook;
const status = getStatusConfig(displayAudiobook);
const handleRequest = async (e: React.MouseEvent) => {
e.stopPropagation();
@@ -69,6 +74,7 @@ export function AudiobookCard({
try {
await createRequest(audiobook);
setLocalRequestStatus('pending');
setShowToast(true);
setTimeout(() => setShowToast(false), 2500);
onRequestSuccess?.();
@@ -240,8 +246,9 @@ export function AudiobookCard({
isOpen={showModal}
onClose={() => setShowModal(false)}
onRequestSuccess={onRequestSuccess}
isRequested={audiobook.isRequested}
requestStatus={audiobook.requestStatus}
onStatusChange={(newStatus) => setLocalRequestStatus(newStatus)}
isRequested={audiobook.isRequested || localRequestStatus !== undefined}
requestStatus={displayAudiobook.requestStatus}
isAvailable={audiobook.isAvailable}
requestedByUsername={audiobook.requestedByUsername}
hasReportedIssue={audiobook.hasReportedIssue}