Add refresh shelf capability

This commit is contained in:
Rob Walsh
2026-03-05 22:24:42 -07:00
parent 01b59fae9d
commit a564fefd7c
11 changed files with 206 additions and 29 deletions
+1 -1
View File
@@ -41,7 +41,7 @@ export function useUpdateHardcoverShelf() {
const updateShelf = async (
shelfId: string,
updates: { listId?: string; apiToken?: string },
updates: { listId?: string; apiToken?: string; forceSync?: boolean },
) => {
return updateGeneric(shelfId, updates);
};
+51 -4
View File
@@ -2,10 +2,8 @@
* Component: Shelves Hook
* Documentation: documentation/frontend/components.md
*/
'use client';
import useSWR from 'swr';
import { useState } from 'react';
import useSWR, { mutate } from 'swr';
import { useAuth } from '@/contexts/AuthContext';
import { fetchWithAuth } from '@/lib/utils/api';
import { ShelfBook } from './useGoodreadsShelves';
@@ -38,3 +36,52 @@ export function useShelves() {
error,
};
}
export function useSyncShelves() {
const { accessToken } = useAuth();
const [isSyncing, setIsSyncing] = useState(false);
const [error, setError] = useState<string | null>(null);
const syncShelves = async (
shelfId?: string,
shelfType?: 'goodreads' | 'hardcover',
) => {
if (!accessToken) throw new Error('Not authenticated');
setIsSyncing(true);
setError(null);
try {
const response = await fetchWithAuth('/api/user/shelves/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ shelfId, shelfType }),
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.message || data.error || 'Failed to trigger sync');
}
// Invalidate both the provider-specific endpoints and the combined endpoint
mutate(
(key) =>
typeof key === 'string' &&
(key.includes('/api/user/shelves') ||
key.includes('/api/user/goodreads-shelves') ||
key.includes('/api/user/hardcover-shelves')),
);
return true;
} catch (err) {
const message = err instanceof Error ? err.message : 'Unknown error';
setError(message);
throw err;
} finally {
setIsSyncing(false);
}
};
return { syncShelves, isSyncing, error };
}
+5 -1
View File
@@ -15,6 +15,8 @@ export interface SyncShelvesPayload {
shelfId?: string;
/** The type of shelf, if shelfId is specified */
shelfType?: 'goodreads' | 'hardcover';
/** If set, only process shelves for this user */
userId?: string;
/** Max Audible lookups per shelf. 0 = unlimited. */
maxLookupsPerShelf?: number;
}
@@ -22,7 +24,7 @@ export interface SyncShelvesPayload {
export async function processSyncShelves(
payload: SyncShelvesPayload,
): Promise<any> {
const { jobId, shelfId, shelfType, maxLookupsPerShelf } = payload;
const { jobId, shelfId, shelfType, userId, maxLookupsPerShelf } = payload;
const logger = RMABLogger.forJob(jobId, 'SyncShelves');
const stats = {
@@ -48,6 +50,7 @@ export async function processSyncShelves(
await import('../services/goodreads-sync.service');
const grStats = await processGoodreadsShelves(logger, {
shelfId: shelfType === 'goodreads' ? shelfId : undefined,
userId,
maxLookupsPerShelf: maxLookupsPerShelf ?? (shelfId ? 0 : undefined),
});
@@ -70,6 +73,7 @@ export async function processSyncShelves(
await import('../services/hardcover-sync.service');
const hcStats = await processHardcoverShelves(logger, {
shelfId: shelfType === 'hardcover' ? shelfId : undefined,
userId,
maxLookupsPerShelf: maxLookupsPerShelf ?? (shelfId ? 0 : undefined),
});
+4 -1
View File
@@ -118,7 +118,10 @@ export async function processGoodreadsShelves(
const stats = createEmptyStats();
const maxLookups = resolveMaxLookups(options);
const whereClause = options.shelfId ? { id: options.shelfId } : {};
const whereClause: any = {};
if (options.shelfId) whereClause.id = options.shelfId;
if (options.userId) whereClause.userId = options.userId;
const shelves = await prisma.goodreadsShelf.findMany({
where: whereClause,
include: { user: { select: { id: true, plexUsername: true } } },
+4 -1
View File
@@ -39,7 +39,10 @@ export async function processHardcoverShelves(
const stats = createEmptyStats();
const maxLookups = resolveMaxLookups(options);
const whereClause = options.shelfId ? { id: options.shelfId } : {};
const whereClause: any = {};
if (options.shelfId) whereClause.id = options.shelfId;
if (options.userId) whereClause.userId = options.userId;
const shelves = await prisma.hardcoverShelf.findMany({
where: whereClause,
include: { user: { select: { id: true, plexUsername: true } } },
+1
View File
@@ -112,6 +112,7 @@ export interface SyncShelvesPayload extends JobPayload {
scheduledJobId?: string;
shelfId?: string;
shelfType?: 'goodreads' | 'hardcover';
userId?: string;
maxLookupsPerShelf?: number;
}
@@ -39,6 +39,7 @@ export interface ShelfSyncStats {
/** Common sync options */
export interface ShelfSyncOptions {
shelfId?: string;
userId?: string;
maxLookupsPerShelf?: number;
}