Add multi-download-client support and UI management

Implements support for configuring both qBittorrent and SABnzbd simultaneously, including migration from legacy config, protocol-aware routing, and protocol filtering. Adds new CRUD API routes for download clients, new UI management components, and updates setup and settings flows to use the new multi-client architecture. Updates documentation to describe the new structure and usage.
This commit is contained in:
kikootwo
2026-01-29 09:21:33 -05:00
parent 3290ebbc9d
commit 2cda6decbe
26 changed files with 3452 additions and 924 deletions
+64 -44
View File
@@ -80,10 +80,6 @@ export async function POST(request: NextRequest) {
!prowlarr?.indexers ||
!Array.isArray(prowlarr.indexers) ||
prowlarr.indexers.length === 0 ||
!downloadClient?.type ||
!downloadClient?.url ||
!downloadClient?.username ||
!downloadClient?.password ||
!paths?.download_dir ||
!paths?.media_dir
) {
@@ -93,6 +89,39 @@ export async function POST(request: NextRequest) {
);
}
// Validate download client(s)
if (!downloadClient) {
return NextResponse.json(
{ success: false, error: 'Download client configuration is required' },
{ status: 400 }
);
}
// Support both legacy single client and new multi-client array
const clients = Array.isArray(downloadClient) ? downloadClient : [downloadClient];
if (clients.length === 0) {
return NextResponse.json(
{ success: false, error: 'At least one download client must be configured' },
{ status: 400 }
);
}
// Validate each client has required fields
for (const client of clients) {
if (!client.url || !client.password) {
return NextResponse.json(
{ success: false, error: 'Download client URL and password/API key are required' },
{ status: 400 }
);
}
if (client.type === 'qbittorrent' && !client.username) {
return NextResponse.json(
{ success: false, error: 'qBittorrent username is required' },
{ status: 400 }
);
}
}
// Create admin user (for Plex mode or ABS + Manual auth)
let adminUser: any = null;
let accessToken: string | null = null;
@@ -356,50 +385,41 @@ export async function POST(request: NextRequest) {
create: { key: 'prowlarr_indexers', value: JSON.stringify(prowlarr.indexers) },
});
// Download client configuration
await prisma.configuration.upsert({
where: { key: 'download_client_type' },
update: { value: downloadClient.type },
create: { key: 'download_client_type', value: downloadClient.type },
});
// Download clients configuration (multi-client support)
// Accept either legacy single client or new clients array
let downloadClientsArray: any[];
if (Array.isArray(downloadClient)) {
// New format: array of clients
downloadClientsArray = downloadClient;
} else if (downloadClient && typeof downloadClient === 'object') {
// Legacy format: convert single client to array
downloadClientsArray = [{
id: `temp-${Date.now()}`,
type: downloadClient.type,
name: downloadClient.type === 'qbittorrent' ? 'qBittorrent' : 'SABnzbd',
enabled: true,
url: downloadClient.url,
username: downloadClient.username,
password: downloadClient.password,
disableSSLVerify: downloadClient.disableSSLVerify || false,
remotePathMappingEnabled: downloadClient.remotePathMappingEnabled || false,
remotePath: downloadClient.remotePath,
localPath: downloadClient.localPath,
category: 'readmeabook',
}];
} else {
throw new Error('Invalid download client configuration');
}
await prisma.configuration.upsert({
where: { key: 'download_client_url' },
update: { value: downloadClient.url },
create: { key: 'download_client_url', value: downloadClient.url },
});
await prisma.configuration.upsert({
where: { key: 'download_client_username' },
update: { value: downloadClient.username },
create: { key: 'download_client_username', value: downloadClient.username },
});
await prisma.configuration.upsert({
where: { key: 'download_client_password' },
update: { value: downloadClient.password },
create: { key: 'download_client_password', value: downloadClient.password },
});
await prisma.configuration.upsert({
where: { key: 'download_client_disable_ssl_verify' },
update: { value: downloadClient.disableSSLVerify ? 'true' : 'false' },
create: {
key: 'download_client_disable_ssl_verify',
value: downloadClient.disableSSLVerify ? 'true' : 'false',
},
});
// Remote path mapping configuration
await prisma.configuration.upsert({
where: { key: 'download_client_remote_path_mapping_enabled' },
update: { value: downloadClient.remotePathMappingEnabled ? 'true' : 'false' },
create: {
key: 'download_client_remote_path_mapping_enabled',
value: downloadClient.remotePathMappingEnabled ? 'true' : 'false',
},
where: { key: 'download_clients' },
update: { value: JSON.stringify(downloadClientsArray) },
create: { key: 'download_clients', value: JSON.stringify(downloadClientsArray) },
});
// Legacy: Keep old keys for backward compatibility with migration
// (Will be cleaned up by migration on first access)
await prisma.configuration.upsert({
where: { key: 'download_client_remote_path' },
update: { value: downloadClient.remotePath || '' },