fix: align backend API routes with console frontend contract
Some checks failed
CI — P3 Alert / test (push) Successful in 34s
CI — P4 Portal / test (push) Successful in 37s
CI — P5 Cost / test (push) Successful in 35s
CI — P6 Run / saas (push) Successful in 33s
CI — P5 Cost / build-push (push) Failing after 5s
CI — P6 Run / build-push (push) Failing after 4s
CI — P2 Drift (Go + Node) / agent (push) Successful in 1m5s
CI — P2 Drift (Go + Node) / saas (push) Successful in 37s
CI — P2 Drift (Go + Node) / build-push (push) Failing after 16s
CI — P3 Alert / build-push (push) Failing after 14s
CI — P4 Portal / build-push (push) Failing after 27s
Some checks failed
CI — P3 Alert / test (push) Successful in 34s
CI — P4 Portal / test (push) Successful in 37s
CI — P5 Cost / test (push) Successful in 35s
CI — P6 Run / saas (push) Successful in 33s
CI — P5 Cost / build-push (push) Failing after 5s
CI — P6 Run / build-push (push) Failing after 4s
CI — P2 Drift (Go + Node) / agent (push) Successful in 1m5s
CI — P2 Drift (Go + Node) / saas (push) Successful in 37s
CI — P2 Drift (Go + Node) / build-push (push) Failing after 16s
CI — P3 Alert / build-push (push) Failing after 14s
CI — P4 Portal / build-push (push) Failing after 27s
This commit is contained in:
@@ -33,7 +33,80 @@ export function registerApprovalRoutes(app: FastifyInstance) {
|
||||
return { approvals: result.rows };
|
||||
});
|
||||
|
||||
// Approve or reject a step
|
||||
// Approve a step (console route: POST /api/v1/approvals/:id/approve)
|
||||
app.post('/api/v1/approvals/:id/approve', async (req, reply) => {
|
||||
const { id } = req.params as { id: string };
|
||||
const reason = ((req.body as any)?.reason as string) ?? undefined;
|
||||
const tenantId = (req as any).tenantId;
|
||||
const userId = (req as any).userId;
|
||||
|
||||
const result = await withTenant(tenantId, async (client) => {
|
||||
const entry = await client.query(
|
||||
`SELECT ae.id, ae.execution_id, ae.status, e.runbook_id
|
||||
FROM audit_entries ae JOIN executions e ON ae.execution_id = e.id
|
||||
WHERE ae.id = $1 AND ae.status = 'awaiting_approval'`,
|
||||
[id],
|
||||
);
|
||||
|
||||
if (!entry.rows[0]) return null;
|
||||
|
||||
await client.query(
|
||||
`UPDATE audit_entries SET status = 'approved', approved_by = $1, approval_method = 'api'
|
||||
WHERE id = $2`,
|
||||
[userId, id],
|
||||
);
|
||||
|
||||
return entry.rows[0];
|
||||
});
|
||||
|
||||
if (!result) return reply.status(404).send({ error: 'Pending approval not found' });
|
||||
|
||||
await bridge.sendApproval(tenantId, result.execution_id, id, 'approve');
|
||||
|
||||
logger.info({ stepId: id, decision: 'approve', userId }, 'Approval decision recorded');
|
||||
return { step_id: id, decision: 'approve', reason };
|
||||
});
|
||||
|
||||
// Reject a step (console route: POST /api/v1/approvals/:id/reject)
|
||||
app.post('/api/v1/approvals/:id/reject', async (req, reply) => {
|
||||
const { id } = req.params as { id: string };
|
||||
const reason = ((req.body as any)?.reason as string) ?? undefined;
|
||||
const tenantId = (req as any).tenantId;
|
||||
const userId = (req as any).userId;
|
||||
|
||||
const result = await withTenant(tenantId, async (client) => {
|
||||
const entry = await client.query(
|
||||
`SELECT ae.id, ae.execution_id, ae.status, e.runbook_id
|
||||
FROM audit_entries ae JOIN executions e ON ae.execution_id = e.id
|
||||
WHERE ae.id = $1 AND ae.status = 'awaiting_approval'`,
|
||||
[id],
|
||||
);
|
||||
|
||||
if (!entry.rows[0]) return null;
|
||||
|
||||
await client.query(
|
||||
`UPDATE audit_entries SET status = 'rejected', approved_by = $1, approval_method = 'api'
|
||||
WHERE id = $2`,
|
||||
[userId, id],
|
||||
);
|
||||
|
||||
await client.query(
|
||||
`UPDATE executions SET status = 'aborted', completed_at = now() WHERE id = $1`,
|
||||
[entry.rows[0].execution_id],
|
||||
);
|
||||
|
||||
return entry.rows[0];
|
||||
});
|
||||
|
||||
if (!result) return reply.status(404).send({ error: 'Pending approval not found' });
|
||||
|
||||
await bridge.sendApproval(tenantId, result.execution_id, id, 'reject');
|
||||
|
||||
logger.info({ stepId: id, decision: 'reject', userId }, 'Approval decision recorded');
|
||||
return { step_id: id, decision: 'reject', reason };
|
||||
});
|
||||
|
||||
// Approve or reject a step (legacy route)
|
||||
app.post('/api/v1/approvals/:stepId', async (req, reply) => {
|
||||
const { stepId } = req.params as { stepId: string };
|
||||
const body = approvalDecisionSchema.parse(req.body);
|
||||
@@ -41,7 +114,6 @@ export function registerApprovalRoutes(app: FastifyInstance) {
|
||||
const userId = (req as any).userId;
|
||||
|
||||
const result = await withTenant(tenantId, async (client) => {
|
||||
// Get the audit entry and its execution
|
||||
const entry = await client.query(
|
||||
`SELECT ae.id, ae.execution_id, ae.status, e.runbook_id
|
||||
FROM audit_entries ae JOIN executions e ON ae.execution_id = e.id
|
||||
@@ -59,7 +131,6 @@ export function registerApprovalRoutes(app: FastifyInstance) {
|
||||
[newStatus, userId, stepId],
|
||||
);
|
||||
|
||||
// If rejected, abort the execution
|
||||
if (body.decision === 'reject') {
|
||||
await client.query(
|
||||
`UPDATE executions SET status = 'aborted', completed_at = now() WHERE id = $1`,
|
||||
@@ -72,7 +143,6 @@ export function registerApprovalRoutes(app: FastifyInstance) {
|
||||
|
||||
if (!result) return reply.status(404).send({ error: 'Pending approval not found' });
|
||||
|
||||
// Notify agent via Redis pub/sub
|
||||
await bridge.sendApproval(tenantId, result.execution_id, stepId, body.decision);
|
||||
|
||||
logger.info({ stepId, decision: body.decision, userId }, 'Approval decision recorded');
|
||||
|
||||
Reference in New Issue
Block a user