import * as plugins from '../plugins.ts'; import { logger } from '../logging.ts'; import type * as interfaces from '../../ts_interfaces/index.ts'; import { BaseProvider, GiteaProvider, GitLabProvider } from '../providers/index.ts'; import type { StorageManager } from '../storage/index.ts'; const LEGACY_CONNECTIONS_FILE = './.nogit/connections.json'; const CONNECTIONS_PREFIX = '/connections/'; /** * Manages provider connections — persists each connection as an * individual JSON file via StorageManager. */ export class ConnectionManager { private connections: interfaces.data.IProviderConnection[] = []; private storageManager: StorageManager; constructor(storageManager: StorageManager) { this.storageManager = storageManager; } async init(): Promise { await this.migrateLegacyFile(); await this.loadConnections(); } /** * One-time migration from the legacy .nogit/connections.json file. */ private async migrateLegacyFile(): Promise { try { const text = await Deno.readTextFile(LEGACY_CONNECTIONS_FILE); const legacy: interfaces.data.IProviderConnection[] = JSON.parse(text); if (legacy.length > 0) { logger.info(`Migrating ${legacy.length} connection(s) from legacy file...`); for (const conn of legacy) { await this.storageManager.setJSON(`${CONNECTIONS_PREFIX}${conn.id}.json`, conn); } // Rename legacy file so migration doesn't repeat await Deno.rename(LEGACY_CONNECTIONS_FILE, LEGACY_CONNECTIONS_FILE + '.migrated'); logger.success('Legacy connections migrated successfully'); } } catch { // No legacy file or already migrated — nothing to do } } private async loadConnections(): Promise { const keys = await this.storageManager.list(CONNECTIONS_PREFIX); this.connections = []; for (const key of keys) { const conn = await this.storageManager.getJSON(key); if (conn) { this.connections.push(conn); } } if (this.connections.length > 0) { logger.info(`Loaded ${this.connections.length} connection(s)`); } else { logger.debug('No existing connections found, starting fresh'); } } private async persistConnection(conn: interfaces.data.IProviderConnection): Promise { await this.storageManager.setJSON(`${CONNECTIONS_PREFIX}${conn.id}.json`, conn); } private async removeConnection(id: string): Promise { await this.storageManager.delete(`${CONNECTIONS_PREFIX}${id}.json`); } getConnections(): interfaces.data.IProviderConnection[] { return this.connections.map((c) => ({ ...c, token: '***' })); } getConnection(id: string): interfaces.data.IProviderConnection | undefined { return this.connections.find((c) => c.id === id); } async createConnection( name: string, providerType: interfaces.data.TProviderType, baseUrl: string, token: string, ): Promise { const connection: interfaces.data.IProviderConnection = { id: crypto.randomUUID(), name, providerType, baseUrl: baseUrl.replace(/\/+$/, ''), token, createdAt: Date.now(), status: 'disconnected', }; this.connections.push(connection); await this.persistConnection(connection); logger.success(`Connection created: ${name} (${providerType})`); return { ...connection, token: '***' }; } async updateConnection( id: string, updates: { name?: string; baseUrl?: string; token?: string }, ): Promise { const conn = this.connections.find((c) => c.id === id); if (!conn) throw new Error(`Connection not found: ${id}`); if (updates.name) conn.name = updates.name; if (updates.baseUrl) conn.baseUrl = updates.baseUrl.replace(/\/+$/, ''); if (updates.token) conn.token = updates.token; await this.persistConnection(conn); return { ...conn, token: '***' }; } async deleteConnection(id: string): Promise { const idx = this.connections.findIndex((c) => c.id === id); if (idx === -1) throw new Error(`Connection not found: ${id}`); this.connections.splice(idx, 1); await this.removeConnection(id); logger.info(`Connection deleted: ${id}`); } async testConnection(id: string): Promise<{ ok: boolean; error?: string }> { const provider = this.getProvider(id); const result = await provider.testConnection(); const conn = this.connections.find((c) => c.id === id)!; conn.status = result.ok ? 'connected' : 'error'; await this.persistConnection(conn); return result; } /** * Factory: returns the correct provider instance for a connection ID */ getProvider(connectionId: string): BaseProvider { const conn = this.connections.find((c) => c.id === connectionId); if (!conn) throw new Error(`Connection not found: ${connectionId}`); switch (conn.providerType) { case 'gitea': return new GiteaProvider(conn.id, conn.baseUrl, conn.token); case 'gitlab': return new GitLabProvider(conn.id, conn.baseUrl, conn.token); default: throw new Error(`Unknown provider type: ${conn.providerType}`); } } }