import { json } from '@sveltejs/kit'; import type { RequestHandler } from './$types'; import { getGitStacks, createGitStack, getGitCredentials, getGitRepository, createGitRepository, upsertStackSource } from '$lib/server/db'; import { deployGitStack } from '$lib/server/git'; import { authorize } from '$lib/server/authorize'; import { registerSchedule } from '$lib/server/scheduler'; import crypto from 'node:crypto'; export const GET: RequestHandler = async ({ url, cookies }) => { const auth = await authorize(cookies); const envId = url.searchParams.get('env'); const envIdNum = envId ? parseInt(envId) : undefined; // Permission check with environment context if (auth.authEnabled && !await auth.can('stacks', 'view', envIdNum)) { return json({ error: 'Permission denied' }, { status: 403 }); } try { const stacks = await getGitStacks(envIdNum); return json(stacks); } catch (error) { console.error('Failed to get git stacks:', error); return json({ error: 'Failed to get git stacks' }, { status: 500 }); } }; export const POST: RequestHandler = async ({ request, cookies }) => { const auth = await authorize(cookies); try { const data = await request.json(); // Permission check with environment context if (auth.authEnabled && !await auth.can('stacks', 'create', data.environmentId || undefined)) { return json({ error: 'Permission denied' }, { status: 403 }); } if (!data.stackName || typeof data.stackName !== 'string') { return json({ error: 'Stack name is required' }, { status: 400 }); } // Either repositoryId or new repo details (url, branch) must be provided let repositoryId = data.repositoryId; if (!repositoryId) { // Create a new repository if URL is provided if (!data.url || typeof data.url !== 'string') { return json({ error: 'Repository URL or existing repository ID is required' }, { status: 400 }); } // Validate credential if provided if (data.credentialId) { const credentials = await getGitCredentials(); const credential = credentials.find(c => c.id === data.credentialId); if (!credential) { return json({ error: 'Invalid credential ID' }, { status: 400 }); } } // Create the repository first const repoName = data.repoName || data.stackName; try { const repo = await createGitRepository({ name: repoName, url: data.url, branch: data.branch || 'main', credentialId: data.credentialId || null }); repositoryId = repo.id; } catch (error: any) { if (error.message?.includes('UNIQUE constraint failed')) { return json({ error: 'A repository with this name already exists' }, { status: 400 }); } throw error; } } else { // Verify repository exists const repo = await getGitRepository(repositoryId); if (!repo) { return json({ error: 'Repository not found' }, { status: 400 }); } } // Generate webhook secret if webhook is enabled let webhookSecret = data.webhookSecret; if (data.webhookEnabled && !webhookSecret) { webhookSecret = crypto.randomBytes(32).toString('hex'); } const gitStack = await createGitStack({ stackName: data.stackName, environmentId: data.environmentId || null, repositoryId: repositoryId, composePath: data.composePath || 'docker-compose.yml', envFilePath: data.envFilePath || null, autoUpdate: data.autoUpdate || false, autoUpdateSchedule: data.autoUpdateSchedule || 'daily', autoUpdateCron: data.autoUpdateCron || '0 3 * * *', webhookEnabled: data.webhookEnabled || false, webhookSecret: webhookSecret }); // Create stack_sources entry so the stack appears in the list immediately await upsertStackSource({ stackName: data.stackName, environmentId: data.environmentId || null, sourceType: 'git', gitRepositoryId: repositoryId, gitStackId: gitStack.id }); // Register schedule with croner if auto-update is enabled if (gitStack.autoUpdate && gitStack.autoUpdateCron) { await registerSchedule(gitStack.id, 'git_stack_sync', gitStack.environmentId); } // If deployNow is set, deploy immediately if (data.deployNow) { const deployResult = await deployGitStack(gitStack.id); return json({ ...gitStack, deployResult: deployResult }); } return json(gitStack); } catch (error: any) { console.error('Failed to create git stack:', error); if (error.message?.includes('UNIQUE constraint failed')) { return json({ error: 'A git stack with this name already exists for this environment' }, { status: 400 }); } return json({ error: 'Failed to create git stack' }, { status: 500 }); } };