mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 04:40:09 +00:00
863f8466ea
Allow qBittorrent to be configured without credentials (supports IP whitelist) and require an API key for SABnzbd. Skip connection testing when disabling a client. Updates include: validation changes in admin and setup API routes, test-download-client flows, DownloadClientModal UI validation and save/test logic, and DownloadClientManager to pass empty strings for optional credentials. Tests updated to reflect SABnzbd API key requirement.
180 lines
6.2 KiB
TypeScript
180 lines
6.2 KiB
TypeScript
/**
|
|
* Component: Admin Download Client by ID API
|
|
* Documentation: documentation/phase3/download-clients.md
|
|
*/
|
|
|
|
import { NextRequest, NextResponse } from 'next/server';
|
|
import { requireAuth, requireAdmin, AuthenticatedRequest } from '@/lib/middleware/auth';
|
|
import { getConfigService } from '@/lib/services/config.service';
|
|
import { getDownloadClientManager, invalidateDownloadClientManager } from '@/lib/services/download-client-manager.service';
|
|
import { DownloadClientConfig } from '@/lib/services/download-client-manager.service';
|
|
import { RMABLogger } from '@/lib/utils/logger';
|
|
|
|
const logger = RMABLogger.create('API.Admin.Settings.DownloadClients.ID');
|
|
|
|
/**
|
|
* PUT - Update download client by ID
|
|
*/
|
|
export async function PUT(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
|
return requireAdmin(req, async () => {
|
|
try {
|
|
// Await params in Next.js 15+
|
|
const { id } = await params;
|
|
const body = await request.json();
|
|
const {
|
|
name,
|
|
enabled,
|
|
url,
|
|
username,
|
|
password,
|
|
disableSSLVerify,
|
|
remotePathMappingEnabled,
|
|
remotePath,
|
|
localPath,
|
|
category,
|
|
} = body;
|
|
|
|
const config = await getConfigService();
|
|
const manager = getDownloadClientManager(config);
|
|
const clients = await manager.getAllClients();
|
|
|
|
const clientIndex = clients.findIndex(c => c.id === id);
|
|
if (clientIndex === -1) {
|
|
return NextResponse.json(
|
|
{ error: 'Download client not found' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
const existingClient = clients[clientIndex];
|
|
|
|
// Build updated client (preserve fields not in request)
|
|
const updatedClient: DownloadClientConfig = {
|
|
...existingClient,
|
|
name: name !== undefined ? name : existingClient.name,
|
|
enabled: enabled !== undefined ? enabled : existingClient.enabled,
|
|
url: url !== undefined ? url : existingClient.url,
|
|
username: username !== undefined ? username : existingClient.username,
|
|
password: password === '********' ? existingClient.password : (password || existingClient.password),
|
|
disableSSLVerify: disableSSLVerify !== undefined ? disableSSLVerify : existingClient.disableSSLVerify,
|
|
remotePathMappingEnabled: remotePathMappingEnabled !== undefined ? remotePathMappingEnabled : existingClient.remotePathMappingEnabled,
|
|
remotePath: remotePath !== undefined ? remotePath : existingClient.remotePath,
|
|
localPath: localPath !== undefined ? localPath : existingClient.localPath,
|
|
category: category !== undefined ? category : existingClient.category,
|
|
};
|
|
|
|
// Validate path mapping if enabled
|
|
if (updatedClient.remotePathMappingEnabled) {
|
|
if (!updatedClient.remotePath || !updatedClient.localPath) {
|
|
return NextResponse.json(
|
|
{ error: 'Remote path and local path are required when path mapping is enabled' },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// Test connection if credentials/URL changed (skip if disabling client)
|
|
const isDisabling = enabled === false;
|
|
if (
|
|
!isDisabling &&
|
|
(
|
|
url !== undefined ||
|
|
username !== undefined ||
|
|
(password && password !== '********') ||
|
|
disableSSLVerify !== undefined
|
|
)
|
|
) {
|
|
const testResult = await manager.testConnection(updatedClient);
|
|
if (!testResult.success) {
|
|
return NextResponse.json(
|
|
{ error: `Connection test failed: ${testResult.message}` },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
}
|
|
|
|
// Update clients array
|
|
clients[clientIndex] = updatedClient;
|
|
await config.setMany([
|
|
{ key: 'download_clients', value: JSON.stringify(clients) },
|
|
]);
|
|
|
|
// Invalidate cache
|
|
invalidateDownloadClientManager();
|
|
|
|
logger.info('Download client updated', { id, type: updatedClient.type, name: updatedClient.name });
|
|
|
|
return NextResponse.json({
|
|
message: 'Download client updated successfully',
|
|
client: {
|
|
...updatedClient,
|
|
password: '********',
|
|
},
|
|
});
|
|
} catch (error) {
|
|
logger.error('Failed to update download client', { error: error instanceof Error ? error.message : String(error) });
|
|
return NextResponse.json(
|
|
{ error: 'Failed to update download client' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* DELETE - Remove download client by ID
|
|
*/
|
|
export async function DELETE(
|
|
request: NextRequest,
|
|
{ params }: { params: Promise<{ id: string }> }
|
|
) {
|
|
return requireAuth(request, async (req: AuthenticatedRequest) => {
|
|
return requireAdmin(req, async () => {
|
|
try {
|
|
// Await params in Next.js 15+
|
|
const { id } = await params;
|
|
|
|
const config = await getConfigService();
|
|
const manager = getDownloadClientManager(config);
|
|
const clients = await manager.getAllClients();
|
|
|
|
const clientIndex = clients.findIndex(c => c.id === id);
|
|
if (clientIndex === -1) {
|
|
return NextResponse.json(
|
|
{ error: 'Download client not found' },
|
|
{ status: 404 }
|
|
);
|
|
}
|
|
|
|
const deletedClient = clients[clientIndex];
|
|
|
|
// Remove client from array
|
|
const updatedClients = clients.filter(c => c.id !== id);
|
|
await config.setMany([
|
|
{ key: 'download_clients', value: JSON.stringify(updatedClients) },
|
|
]);
|
|
|
|
// Invalidate cache
|
|
invalidateDownloadClientManager();
|
|
|
|
logger.info('Download client deleted', { id, type: deletedClient.type, name: deletedClient.name });
|
|
|
|
return NextResponse.json({
|
|
message: 'Download client deleted successfully',
|
|
});
|
|
} catch (error) {
|
|
logger.error('Failed to delete download client', { error: error instanceof Error ? error.message : String(error) });
|
|
return NextResponse.json(
|
|
{ error: 'Failed to delete download client' },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
});
|
|
});
|
|
}
|