Files
multi-simadmin/apps/api/src/control-plane.ts
T

131 lines
5.5 KiB
TypeScript

import type Database from 'better-sqlite3';
import { EventJournal } from './application/events/event-journal.js';
import { registerOperationRoutes } from './interface/http/operation-routes.js';
import { operationCatalogRegistry } from './application/operations/operation-catalog-data.js';
import {
SecureOperationExecution,
secureOperationRegistry,
} from './application/operations/secure-operation-execution.js';
import type { FastifyInstance, FastifyRequest } from 'fastify';
import { buildApp, type BuildAppOptions } from './app.js';
import {
ConnectionProbe,
type ConnectionTransport,
} from './application/connections/connection-probe.js';
import { InstanceCredentialResolver } from './application/connections/instance-credential-resolver.js';
import { InstanceLoginService } from './application/connections/instance-login-service.js';
import {
InstanceSessionStore,
UpstreamSessionClient,
type UpstreamSessionClientOptions,
} from './application/connections/upstream-session-client.js';
import { InstanceService } from './application/instances/instance-service.js';
import { DeleteInstanceOperation } from './application/operations/delete-instance-operation.js';
import type { SecretStore } from './infrastructure/secrets/secret-store.js';
import { registerInstanceRoutes } from './interface/http/instance-routes.js';
import { registerEventRoutes } from './interface/http/event-routes.js';
import { JobQueryService } from './application/jobs/job-query-service.js';
import { registerJobRoutes } from './interface/http/job-routes.js';
import { AuditQueryService } from './application/audit/audit-query-service.js';
import { registerAuditRoutes } from './interface/http/audit-routes.js';
import { InstanceResourceService } from './application/resources/instance-resource-service.js';
import { ConsoleAuthService } from './application/auth/console-auth-service.js';
import { registerConsoleAuth } from './interface/http/console-auth-routes.js';
import { InstanceMessageService } from './application/messages/instance-message-service.js';
export interface SafeControlPlaneUpstream extends ConnectionTransport {
request: UpstreamSessionClientOptions['request'];
postNetworkRegisterAuto(origin: string): Promise<{ readonly status: number }>;
}
export interface ControlPlaneOptions {
readonly db: Database.Database;
readonly store: SecretStore;
readonly upstream: SafeControlPlaneUpstream;
readonly now?: () => Date;
readonly authenticateEventStream?: (request: FastifyRequest) => boolean;
readonly app?: Omit<BuildAppOptions, 'registerRoutes'>;
}
export interface ControlPlaneApp extends FastifyInstance {
retryPendingSecretCleanup(): Promise<unknown>;
}
export function buildControlPlaneApp(options: ControlPlaneOptions): ControlPlaneApp {
const eventJournal = new EventJournal(options.db);
const jobs = new JobQueryService(options.db);
const audit = new AuditQueryService(options.db);
const instances = options.now
? new InstanceService({ db: options.db, store: options.store, now: options.now })
: new InstanceService({ db: options.db, store: options.store });
const connections = options.now
? new ConnectionProbe({
db: options.db,
instances,
transport: options.upstream,
now: options.now,
})
: new ConnectionProbe({ db: options.db, instances, transport: options.upstream });
const sessions = new InstanceSessionStore();
const client = new UpstreamSessionClient({ sessions, request: options.upstream.request });
const resources = new InstanceResourceService({
instances,
sessions,
request: options.upstream.request,
});
const messages = new InstanceMessageService({
instances,
sessions,
request: options.upstream.request,
});
const resolver = new InstanceCredentialResolver({ db: options.db, store: options.store });
const login = options.now
? new InstanceLoginService({ db: options.db, client, resolver, now: options.now })
: new InstanceLoginService({ db: options.db, client, resolver });
const deletion = options.now
? new DeleteInstanceOperation({ db: options.db, instances, now: options.now })
: new DeleteInstanceOperation({ db: options.db, instances });
deletion.reconcileInterruptedJobs();
const secureExecution = new SecureOperationExecution({
db: options.db,
registry: secureOperationRegistry,
transport: {
request: async ({ origin }) => {
const response = await options.upstream.postNetworkRegisterAuto(origin);
return { status: response.status };
},
},
...(options.now ? { now: options.now } : {}),
});
secureExecution.reconcileInterruptedJobs();
const auth = new ConsoleAuthService({
db: options.db,
...(options.now ? { now: options.now } : {}),
});
const app = buildApp({
...options.app,
registerRoutes: (app) => {
registerConsoleAuth(app, auth);
registerInstanceRoutes(app, {
instances,
connections,
login,
deletion,
resources,
messages,
registerDeletionPreparationRoute: false,
});
registerOperationRoutes(app, operationCatalogRegistry, secureExecution, deletion);
registerJobRoutes(app, { jobs });
registerAuditRoutes(app, { audit });
registerEventRoutes(app, {
journal: eventJournal,
...(options.authenticateEventStream
? { authenticate: options.authenticateEventStream }
: {}),
});
},
});
Object.assign(app, {
retryPendingSecretCleanup: () => instances.retryPendingSecretCleanup(),
});
return app as ControlPlaneApp;
}