Add e-book fetch API and UI integration for requests

Introduces an API endpoint to trigger e-book downloads for completed requests, with admin UI integration in RecentRequestsTable and RequestActionsDropdown. Updates the admin dashboard to detect e-book sidecar feature availability from settings. Enhances torrent search result handling with info URLs, improves ranking algorithm normalization, and refines interactive search to show all results without threshold filtering. Also allows nullable ratings in request schemas.
This commit is contained in:
kikootwo
2026-01-08 02:09:57 -05:00
parent 95c25ff73a
commit 722a78ac33
10 changed files with 342 additions and 57 deletions
@@ -21,6 +21,8 @@ export interface RequestActionsDropdownProps {
onDelete: (requestId: string, title: string) => void;
onManualSearch: (requestId: string) => Promise<void>;
onCancel: (requestId: string) => Promise<void>;
onFetchEbook?: (requestId: string) => Promise<void>;
ebookSidecarEnabled?: boolean;
isLoading?: boolean;
}
@@ -29,6 +31,8 @@ export function RequestActionsDropdown({
onDelete,
onManualSearch,
onCancel,
onFetchEbook,
ebookSidecarEnabled = false,
isLoading = false,
}: RequestActionsDropdownProps) {
const [isOpen, setIsOpen] = useState(false);
@@ -40,6 +44,7 @@ export function RequestActionsDropdown({
const canCancel = ['pending', 'searching', 'downloading'].includes(request.status);
const canDelete = true; // Admins can always delete
const canViewSource = !!request.torrentUrl && ['downloading', 'processing', 'downloaded', 'available'].includes(request.status);
const canFetchEbook = ebookSidecarEnabled && ['downloaded', 'available'].includes(request.status);
// Close dropdown when clicking outside
useEffect(() => {
@@ -88,6 +93,17 @@ export function RequestActionsDropdown({
onDelete(request.requestId, request.title);
};
const handleFetchEbook = async () => {
setIsOpen(false);
if (onFetchEbook) {
try {
await onFetchEbook(request.requestId);
} catch (error) {
console.error('Failed to fetch e-book:', error);
}
}
};
return (
<div className="relative" ref={dropdownRef}>
{/* Three-dot menu button */}
@@ -185,8 +201,32 @@ export function RequestActionsDropdown({
</a>
)}
{/* Fetch E-book */}
{canFetchEbook && (
<button
onClick={handleFetchEbook}
className="w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-700 flex items-center gap-2 transition-colors"
role="menuitem"
>
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"
/>
</svg>
Try to fetch Ebook
</button>
)}
{/* Divider if we have search/view actions and other actions */}
{(canSearch || canViewSource) && (canCancel || canDelete) && (
{(canSearch || canViewSource || canFetchEbook) && (canCancel || canDelete) && (
<div className="border-t border-gray-200 dark:border-gray-700 my-1" />
)}