feat(api): advance capability and secure operations slices
This commit is contained in:
@@ -1,7 +1,19 @@
|
||||
import type { OperationPageQuery } from '@multi-simadmin/contracts';
|
||||
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
||||
import type {
|
||||
ExecuteOperationRequest,
|
||||
OperationPageQuery,
|
||||
PrepareOperationRequest,
|
||||
} from '@multi-simadmin/contracts';
|
||||
import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
|
||||
import { OperationCatalogService } from '../../application/operations/operation-catalog-service.js';
|
||||
import type { OperationCatalogRegistry } from '../../application/operations/operation-catalog-service.js';
|
||||
import {
|
||||
DeleteInstanceOperation,
|
||||
DeleteInstanceOperationError,
|
||||
} from '../../application/operations/delete-instance-operation.js';
|
||||
import {
|
||||
SecureOperationExecution,
|
||||
SecureOperationExecutionError,
|
||||
} from '../../application/operations/secure-operation-execution.js';
|
||||
|
||||
const capabilities = new Set(['query', 'command', 'job']);
|
||||
const keys = new Set([
|
||||
@@ -89,6 +101,8 @@ function parseQuery(request: FastifyRequest): OperationPageQuery {
|
||||
export function registerOperationRoutes(
|
||||
app: FastifyInstance,
|
||||
registry: OperationCatalogRegistry,
|
||||
execution?: SecureOperationExecution,
|
||||
deletion?: DeleteInstanceOperation,
|
||||
): void {
|
||||
const service = new OperationCatalogService(registry);
|
||||
app.get('/api/v1/operations', async (request, reply) => {
|
||||
@@ -106,4 +120,67 @@ export function registerOperationRoutes(
|
||||
});
|
||||
}
|
||||
});
|
||||
if (!execution) return;
|
||||
const problem = (
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
error: SecureOperationExecutionError,
|
||||
) => {
|
||||
const status =
|
||||
error.code === 'NOT_FOUND' ? 404 : error.code === 'REVISION_CONFLICT' ? 409 : 400;
|
||||
const title = status === 404 ? 'Not Found' : status === 409 ? 'Conflict' : 'Bad Request';
|
||||
return reply.code(status).type('application/problem+json').send({
|
||||
type: 'about:blank',
|
||||
title,
|
||||
status,
|
||||
code: error.code,
|
||||
detail: error.message,
|
||||
requestId: request.id,
|
||||
});
|
||||
};
|
||||
app.post('/api/v1/operations/prepare', async (request, reply) => {
|
||||
try {
|
||||
const body = request.body as { operationId?: unknown };
|
||||
if (body && typeof body === 'object' && body.operationId === 'deleteInstance' && deletion)
|
||||
return reply.code(200).send(await deletion.prepare(request.body as never, request.id));
|
||||
return reply
|
||||
.code(200)
|
||||
.send(await execution.prepare(request.body as PrepareOperationRequest, request.id));
|
||||
} catch (error) {
|
||||
if (error instanceof DeleteInstanceOperationError) {
|
||||
const status =
|
||||
error.code === 'NOT_FOUND' ? 404 : error.code === 'REVISION_CONFLICT' ? 412 : 400;
|
||||
return reply
|
||||
.code(status)
|
||||
.type('application/problem+json')
|
||||
.send({
|
||||
type: 'about:blank',
|
||||
title:
|
||||
status === 404 ? 'Not Found' : status === 412 ? 'Precondition Failed' : 'Bad Request',
|
||||
status,
|
||||
code: error.code,
|
||||
detail: 'The requested operation could not be prepared.',
|
||||
requestId: request.id,
|
||||
});
|
||||
}
|
||||
if (error instanceof SecureOperationExecutionError) return problem(request, reply, error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
app.post('/api/v1/operations/execute', async (request, reply) => {
|
||||
try {
|
||||
return reply
|
||||
.code(202)
|
||||
.send(
|
||||
await execution.execute(
|
||||
request.body as ExecuteOperationRequest,
|
||||
'loopback-control-plane',
|
||||
request.id,
|
||||
),
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof SecureOperationExecutionError) return problem(request, reply, error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user