ccp: surface slug-collision as 409, not raw Prisma error
agents.routes.ts approve handler: wrap prisma.instance.create in try/catch. When a PrismaClientKnownRequestError P2002 with target including 'slug' is thrown, convert to a 409 AppError with a clear message directing the admin at DELETE /api/instances/:id or the new scripts/ccp-deregister.sh on the target host. Before this, re-registering a previously-registered host (after the operator tore down the underlying stack without cleaning CCP's DB row) returned 500 with a raw Prisma error string — the operator had to read a stack trace to understand the cause. Now the error is self-describing and points at the fix. Matches the pattern other CCP error paths use. Bunker Admin
This commit is contained in:
parent
ce8c5aaf1f
commit
6504598752
@ -150,23 +150,41 @@ router.post('/registrations/:id/approve', authenticate, requireRole('SUPER_ADMIN
|
|||||||
throw new AppError(400, `Registration is ${registration.status}, not PENDING`);
|
throw new AppError(400, `Registration is ${registration.status}, not PENDING`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the Instance record
|
// Create the Instance record.
|
||||||
const instance = await prisma.instance.create({
|
// The slug is unique. If a stale Instance with the same slug exists (e.g. the
|
||||||
data: {
|
// operator tore down the underlying stack without running ccp-deregister.sh)
|
||||||
slug: registration.slug,
|
// surface a clean 409 instead of leaking the raw Prisma error.
|
||||||
name: registration.name,
|
let instance;
|
||||||
domain: registration.domain,
|
try {
|
||||||
status: InstanceStatus.STOPPED,
|
instance = await prisma.instance.create({
|
||||||
statusMessage: 'Remote instance registered — agent connecting',
|
data: {
|
||||||
basePath: registration.basePath,
|
slug: registration.slug,
|
||||||
composeProject: registration.composeProject,
|
name: registration.name,
|
||||||
portConfig: (registration.metadata as Record<string, unknown>)?.portConfig || { api: 4000, admin: 3000, postgres: 5432, nginx: 80 },
|
domain: registration.domain,
|
||||||
isRegistered: true,
|
status: InstanceStatus.STOPPED,
|
||||||
isRemote: true,
|
statusMessage: 'Remote instance registered — agent connecting',
|
||||||
agentUrl: registration.agentUrl,
|
basePath: registration.basePath,
|
||||||
adminEmail: (registration.metadata as Record<string, unknown>)?.adminEmail as string || 'admin@example.com',
|
composeProject: registration.composeProject,
|
||||||
},
|
portConfig: (registration.metadata as Record<string, unknown>)?.portConfig || { api: 4000, admin: 3000, postgres: 5432, nginx: 80 },
|
||||||
});
|
isRegistered: true,
|
||||||
|
isRemote: true,
|
||||||
|
agentUrl: registration.agentUrl,
|
||||||
|
adminEmail: (registration.metadata as Record<string, unknown>)?.adminEmail as string || 'admin@example.com',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof Prisma.PrismaClientKnownRequestError && err.code === 'P2002') {
|
||||||
|
const target = (err.meta?.target as string[] | undefined) ?? [];
|
||||||
|
if (target.includes('slug')) {
|
||||||
|
throw new AppError(
|
||||||
|
409,
|
||||||
|
`Slug '${registration.slug}' is already in use by another Instance. Delete the stale instance first (DELETE /api/instances/:id) or run scripts/ccp-deregister.sh from the target host.`,
|
||||||
|
'SLUG_CONFLICT'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
// Issue mTLS certificates
|
// Issue mTLS certificates
|
||||||
const certMaterials = await issueAgentCert(instance.id, registration.slug, registration.agentUrl);
|
const certMaterials = await issueAgentCert(instance.id, registration.slug, registration.agentUrl);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user