diff --git a/src/app/api/user/shelves/sync/route.ts b/src/app/api/user/shelves/sync/route.ts index 0ae9459..49a8f1d 100644 --- a/src/app/api/user/shelves/sync/route.ts +++ b/src/app/api/user/shelves/sync/route.ts @@ -5,6 +5,7 @@ import { NextRequest, NextResponse } from 'next/server'; import { requireAuth, AuthenticatedRequest } from '@/lib/middleware/auth'; +import { prisma } from '@/lib/db'; import { getJobQueueService } from '@/lib/services/job-queue.service'; import { RMABLogger } from '@/lib/utils/logger'; import { z } from 'zod'; @@ -30,6 +31,21 @@ export async function POST(request: NextRequest) { const body = await request.json().catch(() => ({})); const { shelfId, shelfType } = SyncSchema.parse(body); + // Set lastSyncAt to null so the frontend SWR refresh catches the "Syncing..." state immediately + if (!shelfType || shelfType === 'goodreads') { + await prisma.goodreadsShelf.updateMany({ + where: { userId: req.user.id, ...(shelfId ? { id: shelfId } : {}) }, + data: { lastSyncAt: null }, + }); + } + + if (!shelfType || shelfType === 'hardcover') { + await prisma.hardcoverShelf.updateMany({ + where: { userId: req.user.id, ...(shelfId ? { id: shelfId } : {}) }, + data: { lastSyncAt: null }, + }); + } + const jobQueue = getJobQueueService(); // Trigger sync job with userId filter diff --git a/tests/api/shelves-sync.routes.test.ts b/tests/api/shelves-sync.routes.test.ts index 1a2ada7..6a82d9f 100644 --- a/tests/api/shelves-sync.routes.test.ts +++ b/tests/api/shelves-sync.routes.test.ts @@ -4,10 +4,12 @@ */ import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { createPrismaMock } from '../helpers/prisma'; let authRequest: any; const requireAuthMock = vi.hoisted(() => vi.fn()); +const prismaMock = createPrismaMock(); const jobQueueMock = vi.hoisted(() => ({ addSyncShelvesJob: vi.fn(() => Promise.resolve()), })); @@ -16,6 +18,10 @@ vi.mock('@/lib/middleware/auth', () => ({ requireAuth: requireAuthMock, })); +vi.mock('@/lib/db', () => ({ + prisma: prismaMock, +})); + vi.mock('@/lib/services/job-queue.service', () => ({ getJobQueueService: () => jobQueueMock, })); @@ -36,6 +42,17 @@ describe('POST /api/user/shelves/sync', () => { expect(response.status).toBe(200); expect(payload.success).toBe(true); + + // Both tables should have updateMany called to clear lastSyncAt + expect(prismaMock.goodreadsShelf.updateMany).toHaveBeenCalledWith({ + where: { userId: 'user-1' }, + data: { lastSyncAt: null }, + }); + expect(prismaMock.hardcoverShelf.updateMany).toHaveBeenCalledWith({ + where: { userId: 'user-1' }, + data: { lastSyncAt: null }, + }); + expect(jobQueueMock.addSyncShelvesJob).toHaveBeenCalledWith( undefined, // scheduledJobId undefined, // shelfId @@ -54,6 +71,14 @@ describe('POST /api/user/shelves/sync', () => { expect(response.status).toBe(200); expect(payload.success).toBe(true); + + // Only goodreads should be updated since shelfType is specified + expect(prismaMock.goodreadsShelf.updateMany).toHaveBeenCalledWith({ + where: { userId: 'user-1', id: 'shelf-123' }, + data: { lastSyncAt: null }, + }); + expect(prismaMock.hardcoverShelf.updateMany).not.toHaveBeenCalled(); + expect(jobQueueMock.addSyncShelvesJob).toHaveBeenCalledWith( undefined, // scheduledJobId 'shelf-123', // shelfId