mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-07-14 08:41:11 +00:00
Advance existing requests from Interactive Search
Pass requestId and requestedByUserId through request list and card components and into AudiobookDetailsModal. Add ADVANCEABLE_FROM_INTERACTIVE_SEARCH constant and logic so the Interactive Search modal routes to /api/requests/[id]/select-torrent for own/admin advanceable requests instead of creating a new request. Fix download-token usage to use the in-flight request id, extend Audiobook type with requestedByUserId, and add tests covering advance-vs-create routing and select-torrent behavior.
This commit is contained in:
@@ -273,6 +273,8 @@ export function AudiobookCard({
|
||||
requestStatus={displayAudiobook.requestStatus}
|
||||
isAvailable={audiobook.isAvailable}
|
||||
requestedByUsername={audiobook.requestedByUsername}
|
||||
requestId={audiobook.requestId}
|
||||
requestedByUserId={audiobook.requestedByUserId}
|
||||
hasReportedIssue={audiobook.hasReportedIssue}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { FolderArrowDownIcon, EyeSlashIcon } from '@heroicons/react/24/outline';
|
||||
import { EyeSlashIcon as EyeSlashSolidIcon } from '@heroicons/react/24/solid';
|
||||
import { fetchWithAuth } from '@/lib/utils/api';
|
||||
import { useIsIgnored, useToggleIgnore } from '@/lib/hooks/useIgnoredAudiobooks';
|
||||
import { ADVANCEABLE_FROM_INTERACTIVE_SEARCH } from '@/lib/constants/request-statuses';
|
||||
|
||||
interface AudiobookDetailsModalProps {
|
||||
asin: string;
|
||||
@@ -35,6 +36,10 @@ interface AudiobookDetailsModalProps {
|
||||
requestStatus?: string | null;
|
||||
isAvailable?: boolean;
|
||||
requestedByUsername?: string | null;
|
||||
/** In-flight request id for this audiobook (own or other user's). When set together with an advanceable status and own/admin viewer, Interactive Search → Download routes through select-torrent instead of creating a new request. */
|
||||
requestId?: string | null;
|
||||
/** Owner of the in-flight request (matcher-populated). Used together with the viewer's id/role to gate advance-vs-create routing. */
|
||||
requestedByUserId?: string | null;
|
||||
hideRequestActions?: boolean;
|
||||
hasReportedIssue?: boolean;
|
||||
aiReason?: string | null;
|
||||
@@ -79,6 +84,8 @@ export function AudiobookDetailsModal({
|
||||
requestStatus = null,
|
||||
isAvailable = false,
|
||||
requestedByUsername = null,
|
||||
requestId = null,
|
||||
requestedByUserId = null,
|
||||
hideRequestActions = false,
|
||||
hasReportedIssue = false,
|
||||
aiReason = null,
|
||||
@@ -89,7 +96,7 @@ export function AudiobookDetailsModal({
|
||||
const { audiobook, audibleBaseUrl, isLoading, error } = useAudiobookDetails(isOpen ? asin : null);
|
||||
const { createRequest, isLoading: isRequesting } = useCreateRequest();
|
||||
const { ebookStatus, revalidate: revalidateEbookStatus } = useEbookStatus(isOpen && isAvailable ? asin : null);
|
||||
const { downloadAvailable, requestId } = useDownloadStatus(isOpen ? asin : null);
|
||||
const { downloadAvailable, requestId: downloadRequestId } = useDownloadStatus(isOpen ? asin : null);
|
||||
const { fetchEbook, isLoading: isFetchingEbook } = useFetchEbookByAsin();
|
||||
|
||||
const { isIgnored, ignoredId, isLoading: isLoadingIgnore } = useIsIgnored(isOpen ? asin : null);
|
||||
@@ -118,6 +125,15 @@ export function AudiobookDetailsModal({
|
||||
const status = getStatusInfo(isAvailable, effectiveStatus, requestedByUsername);
|
||||
const canShowEbookButtons = isAvailable && ebookStatus?.ebookSourcesEnabled && !ebookStatus?.hasActiveEbookRequest;
|
||||
|
||||
// Advance the existing request via select-torrent (instead of creating a new one)
|
||||
// only when the viewer owns the request, or is admin, AND the status is one we route on.
|
||||
// Outside this predicate the search modal falls back to today's "create new request" path.
|
||||
const shouldAdvance = !!requestId
|
||||
&& !!user
|
||||
&& (requestedByUserId === user.id || user.role === 'admin')
|
||||
&& (ADVANCEABLE_FROM_INTERACTIVE_SEARCH as readonly string[]).includes(effectiveStatus ?? '');
|
||||
const advanceRequestId = shouldAdvance ? requestId ?? undefined : undefined;
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
@@ -192,10 +208,10 @@ export function AudiobookDetailsModal({
|
||||
};
|
||||
|
||||
const handleDownload = async () => {
|
||||
if (!requestId) return;
|
||||
if (!downloadRequestId) return;
|
||||
setIsDownloading(true);
|
||||
try {
|
||||
const res = await fetchWithAuth(`/api/requests/${requestId}/download-token`, { method: 'POST' });
|
||||
const res = await fetchWithAuth(`/api/requests/${downloadRequestId}/download-token`, { method: 'POST' });
|
||||
if (!res.ok) throw new Error('Failed to get download link');
|
||||
const { downloadUrl } = await res.json();
|
||||
window.location.href = downloadUrl;
|
||||
@@ -576,7 +592,7 @@ export function AudiobookDetailsModal({
|
||||
)}
|
||||
|
||||
{/* Download Link - subtle utility, visible from any context */}
|
||||
{isAvailable && downloadAvailable && requestId && user?.permissions?.download !== false && (
|
||||
{isAvailable && downloadAvailable && downloadRequestId && user?.permissions?.download !== false && (
|
||||
<div>
|
||||
<p className="text-gray-500 dark:text-gray-400">Download</p>
|
||||
<button
|
||||
@@ -807,6 +823,7 @@ export function AudiobookDetailsModal({
|
||||
onSuccess={() => {
|
||||
onRequestSuccess?.();
|
||||
}}
|
||||
requestId={advanceRequestId}
|
||||
audiobook={{
|
||||
title: audiobook.title,
|
||||
author: audiobook.author,
|
||||
|
||||
Reference in New Issue
Block a user