mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 04:40:09 +00:00
Merge branch 'main' of https://github.com/kikootwo/ReadMeABook
This commit is contained in:
@@ -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);
|
||||
};
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
* 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 +38,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 };
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import axios from 'axios';
|
||||
import { XMLParser } from 'fast-xml-parser';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { Prisma } from '@/generated/prisma/client';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
import {
|
||||
ShelfBook,
|
||||
@@ -118,7 +119,10 @@ export async function processGoodreadsShelves(
|
||||
const stats = createEmptyStats();
|
||||
const maxLookups = resolveMaxLookups(options);
|
||||
|
||||
const whereClause = options.shelfId ? { id: options.shelfId } : {};
|
||||
const whereClause: Prisma.GoodreadsShelfWhereInput = {};
|
||||
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 } } },
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import { prisma } from '@/lib/db';
|
||||
import { Prisma } from '@/generated/prisma/client';
|
||||
import { getEncryptionService } from '@/lib/services/encryption.service';
|
||||
import { RMABLogger } from '@/lib/utils/logger';
|
||||
import { fetchHardcoverList, HardcoverApiBook } from '@/lib/services/hardcover-api.service';
|
||||
@@ -38,8 +39,10 @@ export async function processHardcoverShelves(
|
||||
const log = jobLogger || logger;
|
||||
const stats = createEmptyStats();
|
||||
const maxLookups = resolveMaxLookups(options);
|
||||
const whereClause: Prisma.HardcoverShelfWhereInput = {};
|
||||
if (options.shelfId) whereClause.id = options.shelfId;
|
||||
if (options.userId) whereClause.userId = options.userId;
|
||||
|
||||
const whereClause = options.shelfId ? { id: options.shelfId } : {};
|
||||
const shelves = await prisma.hardcoverShelf.findMany({
|
||||
where: whereClause,
|
||||
include: { user: { select: { id: true, plexUsername: true } } },
|
||||
|
||||
@@ -112,6 +112,7 @@ export interface SyncShelvesPayload extends JobPayload {
|
||||
scheduledJobId?: string;
|
||||
shelfId?: string;
|
||||
shelfType?: 'goodreads' | 'hardcover';
|
||||
userId?: string;
|
||||
maxLookupsPerShelf?: number;
|
||||
}
|
||||
|
||||
@@ -770,7 +771,13 @@ export class JobQueueService {
|
||||
/**
|
||||
* Add sync reading shelves job
|
||||
*/
|
||||
async addSyncShelvesJob(scheduledJobId?: string, shelfId?: string, shelfType?: 'goodreads' | 'hardcover', maxLookupsPerShelf?: number): Promise<string> {
|
||||
async addSyncShelvesJob(
|
||||
scheduledJobId?: string,
|
||||
shelfId?: string,
|
||||
shelfType?: 'goodreads' | 'hardcover',
|
||||
maxLookupsPerShelf?: number,
|
||||
userId?: string
|
||||
): Promise<string> {
|
||||
return await this.addJob(
|
||||
'sync_reading_shelves',
|
||||
{
|
||||
@@ -778,6 +785,7 @@ export class JobQueueService {
|
||||
shelfId,
|
||||
shelfType,
|
||||
maxLookupsPerShelf,
|
||||
userId,
|
||||
} as SyncShelvesPayload,
|
||||
{
|
||||
priority: 7,
|
||||
|
||||
@@ -39,6 +39,7 @@ export interface ShelfSyncStats {
|
||||
/** Common sync options */
|
||||
export interface ShelfSyncOptions {
|
||||
shelfId?: string;
|
||||
userId?: string;
|
||||
maxLookupsPerShelf?: number;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user