export const runtime = "nodejs"; import { auth } from '@/auth'; import { NextResponse } from 'next/server'; import { createBackup, listBackups, restoreBackup } from '@/src/lib/config/backup-config'; import { backupConfig, exportConfig, importConfig, loadFullConfig, saveAppConfig, saveServices } from '@/src/lib/config'; import { appConfigSchema, servicesArraySchema } from '@/src/lib/config/schema'; import { CONFIG_FILE_PATH } from '@/src/lib/config/paths'; import { resolveProjectPath } from '@/src/lib/config/load-config'; import { promises as fs } from 'fs'; import path from 'path'; const CONFIG_PATH = CONFIG_FILE_PATH; function jsonError(message: string, status: number) { return NextResponse.json({ error: message }, { status }); } function normalizePath(pathname: string) { return pathname.replace(/\/+$/, ''); } async function requireAuth() { const session = await auth(); if (!session) { return null; } return session; } export async function GET(request: Request) { const pathname = new URL(request.url).pathname; const cleanPath = normalizePath(pathname); if (cleanPath === '/api/config') { const session = await requireAuth(); if (!session) { return jsonError('Unauthorized', 401); } try { const result = await loadFullConfig(); return NextResponse.json(result); } catch (error) { console.error('GET /api/config failed:', error); return jsonError('Failed to load configuration', 500); } } if (cleanPath === '/api/config/backups') { const session = await requireAuth(); if (!session) { return jsonError('Unauthorized', 401); } try { const backups = await listBackups(); return NextResponse.json({ backups }); } catch (error) { console.error('GET /api/config/backups failed:', error); return jsonError('Failed to list backups', 500); } } if (cleanPath === '/api/config/export') { const session = await requireAuth(); if (!session) { return jsonError('Unauthorized', 401); } try { const payload = await exportConfig(); const body = JSON.stringify(payload, null, 2); return new NextResponse(body, { status: 200, headers: { 'Content-Type': 'application/json', 'Content-Disposition': `attachment; filename="homelab-config-export-${new Date().toISOString().replace(/[:.]/g, '-')}.json"`, }, }); } catch (error) { console.error('GET /api/config/export failed:', error); return jsonError('Failed to export configuration', 500); } } return jsonError('Not found', 404); } export async function POST(request: Request) { const session = await requireAuth(); if (!session) { return jsonError('Unauthorized', 401); } const pathname = new URL(request.url).pathname; const cleanPath = normalizePath(pathname); if (cleanPath === '/api/config/backup') { try { const backupFile = await createBackup(); return NextResponse.json({ success: true, message: 'Backup created successfully', backupFilename: path.basename(backupFile), }); } catch (error) { console.error('POST /api/config/backup failed:', error); return jsonError( error instanceof Error ? error.message : 'Failed to create backup', 500 ); } } if (cleanPath === '/api/config/import') { let payload: unknown; try { const contentType = request.headers.get('content-type') || ''; if (contentType.includes('multipart/form-data')) { const formData = await request.formData(); const file = formData.get('file'); if (!(file instanceof File)) { return jsonError('No file uploaded', 400); } const fileText = await file.text(); payload = JSON.parse(fileText); } else { payload = await request.json(); } } catch (error) { return jsonError( 'Request body must be valid JSON or a valid JSON upload', 400 ); } try { await importConfig(payload); return NextResponse.json({ success: true, message: 'Configuration imported successfully', }); } catch (error) { console.error('POST /api/config/import failed:', error); return jsonError( error instanceof Error ? error.message : 'Failed to import configuration', 400 ); } } if (cleanPath === '/api/config/restore') { let payload: any; try { payload = await request.json(); } catch (error) { return jsonError('Request body must be valid JSON', 400); } const { backupFilename } = payload; if (!backupFilename || typeof backupFilename !== 'string') { return jsonError('backupFilename is required and must be a string', 400); } if ( !backupFilename.startsWith('config-backup-') || !backupFilename.endsWith('.json') || backupFilename.includes('/') || backupFilename.includes('\\') ) { return jsonError('Invalid backup filename format', 400); } try { await restoreBackup(backupFilename); return NextResponse.json({ success: true, message: 'Configuration restored successfully', }); } catch (error) { console.error('POST /api/config/restore failed:', error); return jsonError( error instanceof Error ? error.message : 'Failed to restore backup', 500 ); } } return jsonError('Not found', 404); } export async function PUT(request: Request) { const session = await requireAuth(); if (!session) { return jsonError('Unauthorized', 401); } const pathname = new URL(request.url).pathname; const cleanPath = normalizePath(pathname); if (cleanPath !== '/api/config') { return jsonError('Not found', 404); } let payload: any; try { payload = await request.json(); } catch (error) { return jsonError('Request body must be valid JSON', 400); } const appConfigResult = appConfigSchema.safeParse(payload.appConfig); if (!appConfigResult.success) { return jsonError( `App config validation failed: ${appConfigResult.error.message}`, 400 ); } const servicesResult = servicesArraySchema.safeParse(payload.services); if (!servicesResult.success) { return jsonError( `Services validation failed: ${servicesResult.error.message}`, 400 ); } try { await fs.access(CONFIG_PATH); await backupConfig(); } catch (error) { // ignore missing config file } try { const servicesPath = resolveProjectPath(appConfigResult.data.servicesFile); await saveAppConfig(appConfigResult.data, CONFIG_PATH); await saveServices(servicesResult.data, servicesPath); return NextResponse.json({ success: true }); } catch (error) { console.error('PUT /api/config failed:', error); return jsonError('Failed to save configuration', 500); } }