feat(api): complete phase 2.4 control plane

This commit is contained in:
chick
2026-07-17 00:37:11 +08:00
parent c2702b5fc6
commit 7b91dbbad1
40 changed files with 4526 additions and 74 deletions
@@ -0,0 +1,48 @@
import type Database from 'better-sqlite3';
import type { SessionMetadata } from '@multi-simadmin/contracts';
import { CredentialResolverError } from './instance-credential-resolver.js';
import { UpstreamSessionClient } from './upstream-session-client.js';
export interface CredentialResolver {
resolve(instanceId: string): Promise<string>;
}
export class InstanceLoginServiceError extends Error {
constructor(readonly code: 'NOT_FOUND' | 'CREDENTIAL_UNAVAILABLE') {
super(code);
}
}
export interface InstanceLoginServiceOptions {
readonly db: Database.Database;
readonly client: UpstreamSessionClient;
readonly resolver: CredentialResolver;
readonly now?: () => Date;
}
export class InstanceLoginService {
private readonly now: () => Date;
constructor(private readonly options: InstanceLoginServiceOptions) {
this.now = options.now ?? (() => new Date());
}
async login(instanceId: string, password?: string): Promise<SessionMetadata> {
const origin = this.origin(instanceId);
let resolved: string;
try {
resolved = password ?? (await this.options.resolver.resolve(instanceId));
} catch (error) {
if (error instanceof CredentialResolverError) throw new InstanceLoginServiceError(error.code);
throw error;
}
const result = await this.options.client.login(instanceId, origin, resolved);
return { instanceId, authenticated: result.authenticated, checkedAt: this.now().toISOString() };
}
async logout(instanceId: string): Promise<SessionMetadata> {
await this.options.client.logout(instanceId, this.origin(instanceId));
return { instanceId, authenticated: false, checkedAt: this.now().toISOString() };
}
private origin(instanceId: string): string {
const row = this.options.db
.prepare('SELECT base_url FROM instances WHERE id=?')
.get(instanceId) as { base_url: string } | undefined;
if (!row) throw new InstanceLoginServiceError('NOT_FOUND');
return row.base_url;
}
}