import * as plugins from '../../plugins.ts'; import type { OpsServer } from '../classes.opsserver.ts'; import * as interfaces from '../../../ts_interfaces/index.ts'; import { requireValidIdentity } from '../helpers/guards.ts'; export class SecretsHandler { public typedrouter = new plugins.typedrequest.TypedRouter(); constructor(private opsServerRef: OpsServer) { this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter); this.registerHandlers(); } private registerHandlers(): void { // Get all secrets (cache-first, falls back to live fetch) this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getAllSecrets', async (dataArg) => { await requireValidIdentity(this.opsServerRef.adminHandler, dataArg); const scanService = this.opsServerRef.gitopsAppRef.secretsScanService; // Try cache first const hasCached = await scanService.hasCachedData(dataArg.connectionId, dataArg.scope); if (hasCached) { const secrets = await scanService.getCachedSecrets({ connectionId: dataArg.connectionId, scope: dataArg.scope, }); return { secrets }; } // Cache miss: live fetch and save to cache const provider = this.opsServerRef.gitopsAppRef.connectionManager.getProvider( dataArg.connectionId, ); const allSecrets: interfaces.data.ISecret[] = []; if (dataArg.scope === 'project') { const projects = await provider.getProjects(); for (let i = 0; i < projects.length; i += 5) { const batch = projects.slice(i, i + 5); const results = await Promise.allSettled( batch.map(async (p) => { const secrets = await provider.getProjectSecrets(p.id); return secrets.map((s) => ({ ...s, scopeName: p.fullPath || p.name, scope: 'project' as const, scopeId: p.id, connectionId: dataArg.connectionId, })); }), ); for (const result of results) { if (result.status === 'fulfilled') { allSecrets.push(...result.value); } } } } else { const groups = await provider.getGroups(); for (let i = 0; i < groups.length; i += 5) { const batch = groups.slice(i, i + 5); const results = await Promise.allSettled( batch.map(async (g) => { const secrets = await provider.getGroupSecrets(g.id); return secrets.map((s) => ({ ...s, scopeName: g.fullPath || g.name, scope: 'group' as const, scopeId: g.id, connectionId: dataArg.connectionId, })); }), ); for (const result of results) { if (result.status === 'fulfilled') { allSecrets.push(...result.value); } } } } // Save fetched secrets to cache (fire-and-forget) scanService.saveSecrets(allSecrets).catch(() => {}); return { secrets: allSecrets }; }, ), ); // Get secrets (cache-first for single entity) this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'getSecrets', async (dataArg) => { await requireValidIdentity(this.opsServerRef.adminHandler, dataArg); const scanService = this.opsServerRef.gitopsAppRef.secretsScanService; // Try cache first const cached = await scanService.getCachedSecrets({ connectionId: dataArg.connectionId, scope: dataArg.scope, scopeId: dataArg.scopeId, }); if (cached.length > 0) { return { secrets: cached }; } // Cache miss: live fetch const provider = this.opsServerRef.gitopsAppRef.connectionManager.getProvider( dataArg.connectionId, ); const secrets = dataArg.scope === 'project' ? await provider.getProjectSecrets(dataArg.scopeId) : await provider.getGroupSecrets(dataArg.scopeId); // Save to cache (fire-and-forget) const fullSecrets = secrets.map((s) => ({ ...s, scope: dataArg.scope, scopeId: dataArg.scopeId, connectionId: dataArg.connectionId, })); scanService.saveSecrets(fullSecrets).catch(() => {}); return { secrets }; }, ), ); // Create secret this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'createSecret', async (dataArg) => { await requireValidIdentity(this.opsServerRef.adminHandler, dataArg); const provider = this.opsServerRef.gitopsAppRef.connectionManager.getProvider( dataArg.connectionId, ); const secret = dataArg.scope === 'project' ? await provider.createProjectSecret(dataArg.scopeId, dataArg.key, dataArg.value) : await provider.createGroupSecret(dataArg.scopeId, dataArg.key, dataArg.value); // Refresh cache for this entity const scanService = this.opsServerRef.gitopsAppRef.secretsScanService; scanService.scanEntity(dataArg.connectionId, dataArg.scope, dataArg.scopeId).catch(() => {}); return { secret }; }, ), ); // Update secret this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'updateSecret', async (dataArg) => { await requireValidIdentity(this.opsServerRef.adminHandler, dataArg); const provider = this.opsServerRef.gitopsAppRef.connectionManager.getProvider( dataArg.connectionId, ); const secret = dataArg.scope === 'project' ? await provider.updateProjectSecret(dataArg.scopeId, dataArg.key, dataArg.value) : await provider.updateGroupSecret(dataArg.scopeId, dataArg.key, dataArg.value); // Refresh cache for this entity const scanService = this.opsServerRef.gitopsAppRef.secretsScanService; scanService.scanEntity(dataArg.connectionId, dataArg.scope, dataArg.scopeId).catch(() => {}); return { secret }; }, ), ); // Delete secret this.typedrouter.addTypedHandler( new plugins.typedrequest.TypedHandler( 'deleteSecret', async (dataArg) => { await requireValidIdentity(this.opsServerRef.adminHandler, dataArg); const provider = this.opsServerRef.gitopsAppRef.connectionManager.getProvider( dataArg.connectionId, ); if (dataArg.scope === 'project') { await provider.deleteProjectSecret(dataArg.scopeId, dataArg.key); } else { await provider.deleteGroupSecret(dataArg.scopeId, dataArg.key); } // Refresh cache for this entity const scanService = this.opsServerRef.gitopsAppRef.secretsScanService; scanService.scanEntity(dataArg.connectionId, dataArg.scope, dataArg.scopeId).catch(() => {}); return { ok: true }; }, ), ); } }