Admin requests: paginated API & UI overhaul

Add a paginated Admin Requests API and fully refactor the admin requests UI to support filtering, sorting, pagination, and URL state.

- New API: src/app/api/admin/requests/route.ts implements paginated, searchable, filterable, and sortable request listing with proper relation includes and pagination metadata.
- Frontend: RecentRequestsTable rewritten to fetch via SWR (authenticatedFetcher), read/write URL query params, debounce search, support status/user filters, sortable columns, page size selector, and full pagination UI; added loading/error states and toast feedback for actions.
- Admin page updated to use Suspense and the new RecentRequestsTable (component now fetches its own data).
- Settings: deprecated single download-client PUT route now maps updates into the new multi-client format (download_clients JSON), logs deprecation, and invalidates download client manager; settings GET now reads multi-client config for backward compatibility.
- Processors: monitor-download and retry-failed-imports updated to use the download-client-manager and new PathMappingConfig shape for path mapping logic.
- Minor API/schema updates: request-with-torrent schema extended (indexerId, infoUrl, protocol) and setup complete no longer writes legacy path keys.
- Tests updated to reflect API and processor changes.

This change centralizes request management on the server, modernizes the UI for large datasets, and migrates download client settings toward a multi-client configuration while keeping backward compatibility.
This commit is contained in:
kikootwo
2026-02-02 10:24:09 -05:00
parent 2cda6decbe
commit aefc9ef667
15 changed files with 1258 additions and 361 deletions
@@ -29,6 +29,11 @@ const pathMapperMock = vi.hoisted(() => ({
}));
const invalidateQbMock = vi.hoisted(() => vi.fn());
const invalidateSabMock = vi.hoisted(() => vi.fn());
const invalidateDownloadClientManagerMock = vi.hoisted(() => vi.fn());
const downloadClientManagerMock = vi.hoisted(() => ({
getAllClients: vi.fn(),
testConnection: vi.fn(),
}));
vi.mock('@/lib/db', () => ({
prisma: prismaMock,
@@ -67,12 +72,20 @@ vi.mock('@/lib/integrations/sabnzbd.service', () => ({
invalidateSABnzbdService: invalidateSabMock,
}));
vi.mock('@/lib/services/download-client-manager.service', () => ({
getDownloadClientManager: () => downloadClientManagerMock,
invalidateDownloadClientManager: invalidateDownloadClientManagerMock,
}));
describe('Admin settings core routes', () => {
beforeEach(() => {
vi.clearAllMocks();
authRequest = { user: { id: 'admin-1', role: 'admin' }, json: vi.fn() };
requireAuthMock.mockImplementation((_req: any, handler: any) => handler(authRequest));
requireAdminMock.mockImplementation((_req: any, handler: any) => handler());
// Reset download client manager mocks with default values
downloadClientManagerMock.getAllClients.mockResolvedValue([]);
downloadClientManagerMock.testConnection.mockResolvedValue({ success: true, message: 'Connected' });
});
it('returns settings payload', async () => {