/** * GET /api/workspaces/[slug]/databases — list databases in this workspace * POST /api/workspaces/[slug]/databases — provision a new database * * Supported `type` values (all that Coolify v4 can deploy): * postgresql | mysql | mariadb | mongodb | redis | keydb | dragonfly | clickhouse * * POST body: * { * type: "postgresql", * name?: "my-db", * isPublic?: true, // expose a host port for remote clients * publicPort?: 5433, * image?: "postgres:16", * credentials?: { ... } // type-specific (e.g. postgres_user) * limits?: { memory?: "1G", cpus?: "1" }, * } * * Tenancy: every returned record is filtered to the workspace's own * Coolify project UUID. */ import { NextResponse } from 'next/server'; import { requireWorkspacePrincipal } from '@/lib/auth/workspace-auth'; import { listDatabasesInProject, createDatabase, getDatabase, projectUuidOf, type CoolifyDatabaseType, } from '@/lib/coolify'; import { slugify } from '@/lib/naming'; const SUPPORTED_TYPES: readonly CoolifyDatabaseType[] = [ 'postgresql', 'mysql', 'mariadb', 'mongodb', 'redis', 'keydb', 'dragonfly', 'clickhouse', ]; export async function GET( request: Request, { params }: { params: Promise<{ slug: string }> } ) { const { slug } = await params; const principal = await requireWorkspacePrincipal(request, { targetSlug: slug }); if (principal instanceof NextResponse) return principal; const ws = principal.workspace; if (!ws.coolify_project_uuid) { return NextResponse.json( { error: 'Workspace has no Coolify project yet', databases: [] }, { status: 503 } ); } try { const dbs = await listDatabasesInProject(ws.coolify_project_uuid); return NextResponse.json({ workspace: { slug: ws.slug, coolifyProjectUuid: ws.coolify_project_uuid }, databases: dbs.map(d => ({ uuid: d.uuid, name: d.name, type: d.type ?? null, status: d.status, isPublic: d.is_public ?? false, publicPort: d.public_port ?? null, projectUuid: projectUuidOf(d), })), }); } catch (err) { return NextResponse.json( { error: 'Coolify request failed', details: err instanceof Error ? err.message : String(err) }, { status: 502 } ); } } export async function POST( request: Request, { params }: { params: Promise<{ slug: string }> } ) { const { slug } = await params; const principal = await requireWorkspacePrincipal(request, { targetSlug: slug }); if (principal instanceof NextResponse) return principal; const ws = principal.workspace; if (!ws.coolify_project_uuid) { return NextResponse.json({ error: 'Workspace has no Coolify project yet' }, { status: 503 }); } type Body = { type?: string; name?: string; description?: string; isPublic?: boolean; publicPort?: number; image?: string; credentials?: Record; limits?: { memory?: string; cpus?: string }; instantDeploy?: boolean; }; let body: Body = {}; try { body = (await request.json()) as Body; } catch { return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 }); } const type = body.type as CoolifyDatabaseType | undefined; if (!type || !SUPPORTED_TYPES.includes(type)) { return NextResponse.json( { error: `\`type\` must be one of: ${SUPPORTED_TYPES.join(', ')}` }, { status: 400 } ); } const name = slugify(body.name ?? `${type}-${Date.now().toString(36)}`); try { const created = await createDatabase({ type, name, description: body.description, projectUuid: ws.coolify_project_uuid, serverUuid: ws.coolify_server_uuid ?? undefined, environmentName: ws.coolify_environment_name, destinationUuid: ws.coolify_destination_uuid ?? undefined, isPublic: body.isPublic, publicPort: body.publicPort, image: body.image, credentials: body.credentials, limits: body.limits, instantDeploy: body.instantDeploy ?? true, }); const db = await getDatabase(created.uuid); return NextResponse.json( { uuid: db.uuid, name: db.name, type: db.type ?? type, status: db.status, isPublic: db.is_public ?? false, publicPort: db.public_port ?? null, internalUrl: db.internal_db_url ?? null, externalUrl: db.external_db_url ?? null, }, { status: 201 } ); } catch (err) { return NextResponse.json( { error: 'Coolify create failed', details: err instanceof Error ? err.message : String(err) }, { status: 502 } ); } }