import * as plugins from '../../plugins.js'; import { DcRouterDb } from '../classes.dcrouter-db.js'; import type { IApiTokenPolicy, TGatewayClientType } from '../../../ts_interfaces/data/route-management.js'; export interface IGatewayClientPendingProvisioning { type: TGatewayClientType; name: string; description: string | null; hostnamePatterns: string[]; allowedRouteTargets: NonNullable; capabilities: NonNullable; enabled: boolean; policyGeneration: number; policyDigest: string; credentialSequence: number; stagedAt: number; stagedBy: string; } const getDb = () => DcRouterDb.getInstance().getDb(); @plugins.smartdata.Collection(() => getDb()) export class GatewayClientDoc extends plugins.smartdata.SmartDataDbDoc { @plugins.smartdata.unI() @plugins.smartdata.svDb() public id!: string; @plugins.smartdata.svDb() public type!: TGatewayClientType; @plugins.smartdata.svDb() public name: string = ''; @plugins.smartdata.svDb() public description?: string; @plugins.smartdata.svDb() public hostnamePatterns: string[] = []; @plugins.smartdata.svDb() public allowedRouteTargets: NonNullable = []; @plugins.smartdata.svDb() public capabilities: NonNullable = {}; @plugins.smartdata.svDb() public enabled: boolean = true; @plugins.smartdata.svDb() public policyGeneration: number = 1; @plugins.smartdata.svDb() public credentialSequence: number = 0; /** Exact provisioned credential sequence authorized for the active policy. */ @plugins.smartdata.svDb() public activeCredentialSequence: number = 0; /** Monotonic compare-and-set authority for every mutable client field. */ @plugins.smartdata.svDb() public mutationRevision: number = 0; @plugins.smartdata.svDb() public pendingProvisioning: IGatewayClientPendingProvisioning | null = null; @plugins.smartdata.svDb() public createdAt!: number; @plugins.smartdata.svDb() public updatedAt!: number; @plugins.smartdata.svDb() public createdBy!: string; constructor() { super(); } public static async findById(id: string): Promise { return await GatewayClientDoc.getInstance({ id }); } private static async getNativeCollection() { const smartdataCollection = (GatewayClientDoc as typeof GatewayClientDoc & { collection: plugins.smartdata.SmartdataCollection; }).collection; await smartdataCollection.init(); return smartdataCollection.mongoDbCollection; } private static buildRevisionSelector(idArg: string, revisionArg: number): Record { if (revisionArg === 0) { return { id: idArg, $or: [ { mutationRevision: 0 }, { mutationRevision: { $exists: false } }, ], }; } return { id: idArg, mutationRevision: revisionArg }; } public static async compareAndSet( idArg: string, expectedRevisionArg: number, setArg: Record, unsetArg: string[] = [], ): Promise { const collection = await GatewayClientDoc.getNativeCollection(); const update: Record = { $set: { ...setArg, mutationRevision: expectedRevisionArg + 1, _updatedAt: new Date().toISOString(), }, }; if (unsetArg.length > 0) { update.$unset = Object.fromEntries(unsetArg.map((fieldArg) => [fieldArg, ''])); } const result = await collection.updateOne( GatewayClientDoc.buildRevisionSelector(idArg, expectedRevisionArg), update, ); if (result.matchedCount !== 1) return null; return await GatewayClientDoc.findById(idArg); } public static async deleteIfRevision(idArg: string, expectedRevisionArg: number): Promise { const collection = await GatewayClientDoc.getNativeCollection(); const result = await collection.deleteOne( GatewayClientDoc.buildRevisionSelector(idArg, expectedRevisionArg), ); return result.deletedCount === 1; } public static async findAll(): Promise { return await GatewayClientDoc.getInstances({}); } }