/** * Database layer for Onebox using SQLite * Refactored into repository pattern */ import * as plugins from '../plugins.ts'; import type { IService, IRegistry, IRegistryToken, ISslCertificate, IMetric, ILogEntry, IUser, IPlatformService, IPlatformResource, TPlatformServiceType, IDomain, ICertificate, ICertRequirement, } from '../types.ts'; import type { TBindValue } from './types.ts'; import { logger } from '../logging.ts'; import { getErrorMessage } from '../utils/error.ts'; // Import repositories import { ServiceRepository, RegistryRepository, CertificateRepository, AuthRepository, MetricsRepository, PlatformRepository, } from './repositories/index.ts'; export class OneboxDatabase { private db: InstanceType | null = null; private dbPath: string; // Repositories private serviceRepo!: ServiceRepository; private registryRepo!: RegistryRepository; private certificateRepo!: CertificateRepository; private authRepo!: AuthRepository; private metricsRepo!: MetricsRepository; private platformRepo!: PlatformRepository; constructor(dbPath = './.nogit/onebox.db') { this.dbPath = dbPath; } /** * Initialize database connection and create tables */ async init(): Promise { try { // Ensure data directory exists const dbDir = plugins.path.dirname(this.dbPath); await Deno.mkdir(dbDir, { recursive: true }); // Open database this.db = new plugins.sqlite.DB(this.dbPath); logger.info(`Database initialized at ${this.dbPath}`); // Create tables await this.createTables(); // Run migrations if needed await this.runMigrations(); // Initialize repositories with bound query function const queryFn = this.query.bind(this); this.serviceRepo = new ServiceRepository(queryFn); this.registryRepo = new RegistryRepository(queryFn); this.certificateRepo = new CertificateRepository(queryFn); this.authRepo = new AuthRepository(queryFn); this.metricsRepo = new MetricsRepository(queryFn); this.platformRepo = new PlatformRepository(queryFn); } catch (error) { logger.error(`Failed to initialize database: ${getErrorMessage(error)}`); throw error; } } /** * Create all database tables */ private async createTables(): Promise { if (!this.db) throw new Error('Database not initialized'); // Services table this.query(` CREATE TABLE IF NOT EXISTS services ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, image TEXT NOT NULL, registry TEXT, env_vars TEXT NOT NULL, port INTEGER NOT NULL, domain TEXT, container_id TEXT, status TEXT NOT NULL DEFAULT 'stopped', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ) `); // Registries table this.query(` CREATE TABLE IF NOT EXISTS registries ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL UNIQUE, username TEXT NOT NULL, password_encrypted TEXT NOT NULL, created_at INTEGER NOT NULL ) `); // Nginx configs table this.query(` CREATE TABLE IF NOT EXISTS nginx_configs ( id INTEGER PRIMARY KEY AUTOINCREMENT, service_id INTEGER NOT NULL, domain TEXT NOT NULL, port INTEGER NOT NULL, ssl_enabled INTEGER NOT NULL DEFAULT 0, config_template TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE ) `); // SSL certificates table this.query(` CREATE TABLE IF NOT EXISTS ssl_certificates ( id INTEGER PRIMARY KEY AUTOINCREMENT, domain TEXT NOT NULL UNIQUE, cert_path TEXT NOT NULL, key_path TEXT NOT NULL, full_chain_path TEXT NOT NULL, expiry_date INTEGER NOT NULL, issuer TEXT NOT NULL, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ) `); // DNS records table this.query(` CREATE TABLE IF NOT EXISTS dns_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, domain TEXT NOT NULL UNIQUE, type TEXT NOT NULL, value TEXT NOT NULL, cloudflare_id TEXT, zone_id TEXT, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ) `); // Metrics table this.query(` CREATE TABLE IF NOT EXISTS metrics ( id INTEGER PRIMARY KEY AUTOINCREMENT, service_id INTEGER NOT NULL, timestamp INTEGER NOT NULL, cpu_percent REAL NOT NULL, memory_used INTEGER NOT NULL, memory_limit INTEGER NOT NULL, network_rx_bytes INTEGER NOT NULL, network_tx_bytes INTEGER NOT NULL, FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE ) `); // Create index for metrics queries this.query(` CREATE INDEX IF NOT EXISTS idx_metrics_service_timestamp ON metrics(service_id, timestamp DESC) `); // Logs table this.query(` CREATE TABLE IF NOT EXISTS logs ( id INTEGER PRIMARY KEY AUTOINCREMENT, service_id INTEGER NOT NULL, timestamp INTEGER NOT NULL, message TEXT NOT NULL, level TEXT NOT NULL, source TEXT NOT NULL, FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE ) `); // Create index for logs queries this.query(` CREATE INDEX IF NOT EXISTS idx_logs_service_timestamp ON logs(service_id, timestamp DESC) `); // Users table this.query(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user', created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ) `); // Settings table this.query(` CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at INTEGER NOT NULL ) `); // Version table for migrations this.query(` CREATE TABLE IF NOT EXISTS migrations ( version INTEGER PRIMARY KEY, applied_at INTEGER NOT NULL ) `); logger.debug('Database tables created successfully'); } /** * Run database migrations */ private async runMigrations(): Promise { if (!this.db) throw new Error('Database not initialized'); try { const currentVersion = this.getMigrationVersion(); logger.info(`Current database migration version: ${currentVersion}`); // Migration 1: Initial schema if (currentVersion === 0) { logger.info('Setting initial migration version to 1'); this.setMigrationVersion(1); } // Migration 2: Convert timestamp columns from INTEGER to REAL const updatedVersion = this.getMigrationVersion(); if (updatedVersion < 2) { logger.info('Running migration 2: Converting timestamps to REAL...'); // SSL certificates this.query(` CREATE TABLE ssl_certificates_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, domain TEXT NOT NULL UNIQUE, cert_path TEXT NOT NULL, key_path TEXT NOT NULL, full_chain_path TEXT NOT NULL, expiry_date REAL NOT NULL, issuer TEXT NOT NULL, created_at REAL NOT NULL, updated_at REAL NOT NULL ) `); this.query(`INSERT INTO ssl_certificates_new SELECT * FROM ssl_certificates`); this.query(`DROP TABLE ssl_certificates`); this.query(`ALTER TABLE ssl_certificates_new RENAME TO ssl_certificates`); // Services this.query(` CREATE TABLE services_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, image TEXT NOT NULL, registry TEXT, env_vars TEXT NOT NULL, port INTEGER NOT NULL, domain TEXT, container_id TEXT, status TEXT NOT NULL DEFAULT 'stopped', created_at REAL NOT NULL, updated_at REAL NOT NULL ) `); this.query(`INSERT INTO services_new SELECT * FROM services`); this.query(`DROP TABLE services`); this.query(`ALTER TABLE services_new RENAME TO services`); // Registries this.query(` CREATE TABLE registries_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL UNIQUE, username TEXT NOT NULL, password_encrypted TEXT NOT NULL, created_at REAL NOT NULL ) `); this.query(`INSERT INTO registries_new SELECT * FROM registries`); this.query(`DROP TABLE registries`); this.query(`ALTER TABLE registries_new RENAME TO registries`); // Nginx configs this.query(` CREATE TABLE nginx_configs_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, service_id INTEGER NOT NULL, domain TEXT NOT NULL, port INTEGER NOT NULL, ssl_enabled INTEGER NOT NULL DEFAULT 0, config_template TEXT NOT NULL, created_at REAL NOT NULL, updated_at REAL NOT NULL, FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE ) `); this.query(`INSERT INTO nginx_configs_new SELECT * FROM nginx_configs`); this.query(`DROP TABLE nginx_configs`); this.query(`ALTER TABLE nginx_configs_new RENAME TO nginx_configs`); // DNS records this.query(` CREATE TABLE dns_records_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, domain TEXT NOT NULL UNIQUE, type TEXT NOT NULL, value TEXT NOT NULL, cloudflare_id TEXT, zone_id TEXT, created_at REAL NOT NULL, updated_at REAL NOT NULL ) `); this.query(`INSERT INTO dns_records_new SELECT * FROM dns_records`); this.query(`DROP TABLE dns_records`); this.query(`ALTER TABLE dns_records_new RENAME TO dns_records`); // Metrics this.query(` CREATE TABLE metrics_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, service_id INTEGER NOT NULL, timestamp REAL NOT NULL, cpu_percent REAL NOT NULL, memory_used INTEGER NOT NULL, memory_limit INTEGER NOT NULL, network_rx_bytes INTEGER NOT NULL, network_tx_bytes INTEGER NOT NULL, FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE ) `); this.query(`INSERT INTO metrics_new SELECT * FROM metrics`); this.query(`DROP TABLE metrics`); this.query(`ALTER TABLE metrics_new RENAME TO metrics`); this.query(`CREATE INDEX IF NOT EXISTS idx_metrics_service_timestamp ON metrics(service_id, timestamp DESC)`); // Logs this.query(` CREATE TABLE logs_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, service_id INTEGER NOT NULL, timestamp REAL NOT NULL, message TEXT NOT NULL, level TEXT NOT NULL, source TEXT NOT NULL, FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE ) `); this.query(`INSERT INTO logs_new SELECT * FROM logs`); this.query(`DROP TABLE logs`); this.query(`ALTER TABLE logs_new RENAME TO logs`); this.query(`CREATE INDEX IF NOT EXISTS idx_logs_service_timestamp ON logs(service_id, timestamp DESC)`); // Users this.query(` CREATE TABLE users_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user', created_at REAL NOT NULL, updated_at REAL NOT NULL ) `); this.query(`INSERT INTO users_new SELECT * FROM users`); this.query(`DROP TABLE users`); this.query(`ALTER TABLE users_new RENAME TO users`); // Settings this.query(` CREATE TABLE settings_new ( key TEXT PRIMARY KEY, value TEXT NOT NULL, updated_at REAL NOT NULL ) `); this.query(`INSERT INTO settings_new SELECT * FROM settings`); this.query(`DROP TABLE settings`); this.query(`ALTER TABLE settings_new RENAME TO settings`); // Migrations table itself this.query(` CREATE TABLE migrations_new ( version INTEGER PRIMARY KEY, applied_at REAL NOT NULL ) `); this.query(`INSERT INTO migrations_new SELECT * FROM migrations`); this.query(`DROP TABLE migrations`); this.query(`ALTER TABLE migrations_new RENAME TO migrations`); this.setMigrationVersion(2); logger.success('Migration 2 completed: All timestamps converted to REAL'); } // Migration 3: Domain management tables const version3 = this.getMigrationVersion(); if (version3 < 3) { logger.info('Running migration 3: Creating domain management tables...'); this.query(` CREATE TABLE domains ( id INTEGER PRIMARY KEY AUTOINCREMENT, domain TEXT NOT NULL UNIQUE, dns_provider TEXT, cloudflare_zone_id TEXT, is_obsolete INTEGER NOT NULL DEFAULT 0, default_wildcard INTEGER NOT NULL DEFAULT 1, created_at REAL NOT NULL, updated_at REAL NOT NULL ) `); this.query(` CREATE TABLE certificates ( id INTEGER PRIMARY KEY AUTOINCREMENT, domain_id INTEGER NOT NULL, cert_domain TEXT NOT NULL, is_wildcard INTEGER NOT NULL DEFAULT 0, cert_path TEXT NOT NULL, key_path TEXT NOT NULL, full_chain_path TEXT NOT NULL, expiry_date REAL NOT NULL, issuer TEXT NOT NULL, is_valid INTEGER NOT NULL DEFAULT 1, created_at REAL NOT NULL, updated_at REAL NOT NULL, FOREIGN KEY (domain_id) REFERENCES domains(id) ON DELETE CASCADE ) `); this.query(` CREATE TABLE cert_requirements ( id INTEGER PRIMARY KEY AUTOINCREMENT, service_id INTEGER NOT NULL, domain_id INTEGER NOT NULL, subdomain TEXT NOT NULL, certificate_id INTEGER, status TEXT NOT NULL DEFAULT 'pending', created_at REAL NOT NULL, updated_at REAL NOT NULL, FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE, FOREIGN KEY (domain_id) REFERENCES domains(id) ON DELETE CASCADE, FOREIGN KEY (certificate_id) REFERENCES certificates(id) ON DELETE SET NULL ) `); interface OldSslCert { id?: number; domain?: string; cert_path?: string; key_path?: string; full_chain_path?: string; expiry_date?: number; issuer?: string; created_at?: number; updated_at?: number; [key: number]: unknown; } const existingCerts = this.query('SELECT * FROM ssl_certificates'); const now = Date.now(); const domainMap = new Map(); for (const cert of existingCerts) { const domain = String(cert.domain ?? (cert as Record)[1]); if (!domainMap.has(domain)) { this.query( 'INSERT INTO domains (domain, dns_provider, is_obsolete, default_wildcard, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)', [domain, null, 0, 1, now, now] ); const result = this.query<{ id?: number; [key: number]: unknown }>('SELECT last_insert_rowid() as id'); const domainId = result[0].id ?? (result[0] as Record)[0]; domainMap.set(domain, Number(domainId)); } } for (const cert of existingCerts) { const domain = String(cert.domain ?? (cert as Record)[1]); const domainId = domainMap.get(domain); this.query( `INSERT INTO certificates ( domain_id, cert_domain, is_wildcard, cert_path, key_path, full_chain_path, expiry_date, issuer, is_valid, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ domainId, domain, 0, String(cert.cert_path ?? (cert as Record)[2]), String(cert.key_path ?? (cert as Record)[3]), String(cert.full_chain_path ?? (cert as Record)[4]), Number(cert.expiry_date ?? (cert as Record)[5]), String(cert.issuer ?? (cert as Record)[6]), 1, Number(cert.created_at ?? (cert as Record)[7]), Number(cert.updated_at ?? (cert as Record)[8]) ] ); } this.query('DROP TABLE ssl_certificates'); this.query('CREATE INDEX IF NOT EXISTS idx_domains_cloudflare_zone ON domains(cloudflare_zone_id)'); this.query('CREATE INDEX IF NOT EXISTS idx_certificates_domain ON certificates(domain_id)'); this.query('CREATE INDEX IF NOT EXISTS idx_certificates_expiry ON certificates(expiry_date)'); this.query('CREATE INDEX IF NOT EXISTS idx_cert_requirements_service ON cert_requirements(service_id)'); this.query('CREATE INDEX IF NOT EXISTS idx_cert_requirements_domain ON cert_requirements(domain_id)'); this.setMigrationVersion(3); logger.success('Migration 3 completed: Domain management tables created'); } // Migration 4: Add Onebox Registry support columns const version4 = this.getMigrationVersion(); if (version4 < 4) { logger.info('Running migration 4: Adding Onebox Registry columns to services table...'); this.query(`ALTER TABLE services ADD COLUMN use_onebox_registry INTEGER DEFAULT 0`); this.query(`ALTER TABLE services ADD COLUMN registry_repository TEXT`); this.query(`ALTER TABLE services ADD COLUMN registry_token TEXT`); this.query(`ALTER TABLE services ADD COLUMN registry_image_tag TEXT DEFAULT 'latest'`); this.query(`ALTER TABLE services ADD COLUMN auto_update_on_push INTEGER DEFAULT 0`); this.query(`ALTER TABLE services ADD COLUMN image_digest TEXT`); this.setMigrationVersion(4); logger.success('Migration 4 completed: Onebox Registry columns added to services table'); } // Migration 5: Registry tokens table const version5 = this.getMigrationVersion(); if (version5 < 5) { logger.info('Running migration 5: Creating registry_tokens table...'); this.query(` CREATE TABLE registry_tokens ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, token_hash TEXT NOT NULL UNIQUE, token_type TEXT NOT NULL, scope TEXT NOT NULL, expires_at REAL, created_at REAL NOT NULL, last_used_at REAL, created_by TEXT NOT NULL ) `); this.query('CREATE INDEX IF NOT EXISTS idx_registry_tokens_type ON registry_tokens(token_type)'); this.query('CREATE INDEX IF NOT EXISTS idx_registry_tokens_hash ON registry_tokens(token_hash)'); this.setMigrationVersion(5); logger.success('Migration 5 completed: Registry tokens table created'); } // Migration 6: Drop registry_token column from services table const version6 = this.getMigrationVersion(); if (version6 < 6) { logger.info('Running migration 6: Dropping registry_token column from services table...'); this.query(` CREATE TABLE services_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, image TEXT NOT NULL, registry TEXT, env_vars TEXT, port INTEGER NOT NULL, domain TEXT, container_id TEXT, status TEXT NOT NULL, created_at REAL NOT NULL, updated_at REAL NOT NULL, use_onebox_registry INTEGER DEFAULT 0, registry_repository TEXT, registry_image_tag TEXT DEFAULT 'latest', auto_update_on_push INTEGER DEFAULT 0, image_digest TEXT ) `); this.query(` INSERT INTO services_new ( id, name, image, registry, env_vars, port, domain, container_id, status, created_at, updated_at, use_onebox_registry, registry_repository, registry_image_tag, auto_update_on_push, image_digest ) SELECT id, name, image, registry, env_vars, port, domain, container_id, status, created_at, updated_at, use_onebox_registry, registry_repository, registry_image_tag, auto_update_on_push, image_digest FROM services `); this.query('DROP TABLE services'); this.query('ALTER TABLE services_new RENAME TO services'); this.query('CREATE INDEX IF NOT EXISTS idx_services_name ON services(name)'); this.query('CREATE INDEX IF NOT EXISTS idx_services_status ON services(status)'); this.setMigrationVersion(6); logger.success('Migration 6 completed: registry_token column dropped from services table'); } // Migration 7: Platform services tables const version7 = this.getMigrationVersion(); if (version7 < 7) { logger.info('Running migration 7: Creating platform services tables...'); this.query(` CREATE TABLE platform_services ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, type TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'stopped', container_id TEXT, config TEXT NOT NULL DEFAULT '{}', admin_credentials_encrypted TEXT, created_at REAL NOT NULL, updated_at REAL NOT NULL ) `); this.query(` CREATE TABLE platform_resources ( id INTEGER PRIMARY KEY AUTOINCREMENT, platform_service_id INTEGER NOT NULL, service_id INTEGER NOT NULL, resource_type TEXT NOT NULL, resource_name TEXT NOT NULL, credentials_encrypted TEXT NOT NULL, created_at REAL NOT NULL, FOREIGN KEY (platform_service_id) REFERENCES platform_services(id) ON DELETE CASCADE, FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE ) `); this.query(`ALTER TABLE services ADD COLUMN platform_requirements TEXT DEFAULT '{}'`); this.query('CREATE INDEX IF NOT EXISTS idx_platform_services_type ON platform_services(type)'); this.query('CREATE INDEX IF NOT EXISTS idx_platform_resources_service ON platform_resources(service_id)'); this.query('CREATE INDEX IF NOT EXISTS idx_platform_resources_platform ON platform_resources(platform_service_id)'); this.setMigrationVersion(7); logger.success('Migration 7 completed: Platform services tables created'); } // Migration 8: Convert certificates table to store PEM content const version8 = this.getMigrationVersion(); if (version8 < 8) { logger.info('Running migration 8: Converting certificates table to store PEM content...'); this.query(` CREATE TABLE certificates_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, domain_id INTEGER NOT NULL, cert_domain TEXT NOT NULL, is_wildcard INTEGER NOT NULL DEFAULT 0, cert_pem TEXT NOT NULL DEFAULT '', key_pem TEXT NOT NULL DEFAULT '', fullchain_pem TEXT NOT NULL DEFAULT '', expiry_date REAL NOT NULL, issuer TEXT NOT NULL, is_valid INTEGER NOT NULL DEFAULT 1, created_at REAL NOT NULL, updated_at REAL NOT NULL, FOREIGN KEY (domain_id) REFERENCES domains(id) ON DELETE CASCADE ) `); this.query(` INSERT INTO certificates_new (id, domain_id, cert_domain, is_wildcard, cert_pem, key_pem, fullchain_pem, expiry_date, issuer, is_valid, created_at, updated_at) SELECT id, domain_id, cert_domain, is_wildcard, '', '', '', expiry_date, issuer, 0, created_at, updated_at FROM certificates `); this.query('DROP TABLE certificates'); this.query('ALTER TABLE certificates_new RENAME TO certificates'); this.query('CREATE INDEX IF NOT EXISTS idx_certificates_domain ON certificates(domain_id)'); this.query('CREATE INDEX IF NOT EXISTS idx_certificates_expiry ON certificates(expiry_date)'); this.setMigrationVersion(8); logger.success('Migration 8 completed: Certificates table now stores PEM content'); } } catch (error) { logger.error(`Migration failed: ${getErrorMessage(error)}`); if (error instanceof Error && error.stack) { logger.error(`Stack: ${error.stack}`); } throw error; } } /** * Get current migration version */ private getMigrationVersion(): number { if (!this.db) throw new Error('Database not initialized'); try { const result = this.query<{ version?: number | null; [key: number]: unknown }>('SELECT MAX(version) as version FROM migrations'); if (result.length === 0) return 0; const versionValue = result[0].version ?? (result[0] as Record)[0]; return versionValue !== null && versionValue !== undefined ? Number(versionValue) : 0; } catch (error) { logger.warn(`Error getting migration version: ${getErrorMessage(error)}, defaulting to 0`); return 0; } } /** * Set migration version */ private setMigrationVersion(version: number): void { if (!this.db) throw new Error('Database not initialized'); this.query('INSERT INTO migrations (version, applied_at) VALUES (?, ?)', [ version, Date.now(), ]); logger.debug(`Migration version set to ${version}`); } /** * Close database connection */ close(): void { if (this.db) { this.db.close(); this.db = null; logger.debug('Database connection closed'); } } /** * Execute a raw query */ query>(sql: string, params: TBindValue[] = []): T[] { if (!this.db) { const error = new Error('Database not initialized'); console.error('Database access before initialization!'); console.error('Stack trace:', error.stack); throw error; } if (params.length === 0 && !sql.trim().toUpperCase().startsWith('SELECT')) { this.db.exec(sql); return [] as T[]; } const stmt = this.db.prepare(sql); try { const result = stmt.all(...params); return result as T[]; } finally { stmt.finalize(); } } // ============ Services CRUD (delegated to repository) ============ async createService(service: Omit): Promise { return this.serviceRepo.create(service); } getServiceByName(name: string): IService | null { return this.serviceRepo.getByName(name); } getServiceByID(id: number): IService | null { return this.serviceRepo.getById(id); } getAllServices(): IService[] { return this.serviceRepo.getAll(); } updateService(id: number, updates: Partial): void { this.serviceRepo.update(id, updates); } deleteService(id: number): void { this.serviceRepo.delete(id); } // ============ Registries CRUD (delegated to repository) ============ async createRegistry(registry: Omit): Promise { return this.registryRepo.createRegistry(registry); } getRegistryByURL(url: string): IRegistry | null { return this.registryRepo.getRegistryByURL(url); } getAllRegistries(): IRegistry[] { return this.registryRepo.getAllRegistries(); } deleteRegistry(url: string): void { this.registryRepo.deleteRegistry(url); } // ============ Settings CRUD (delegated to repository) ============ getSetting(key: string): string | null { return this.authRepo.getSetting(key); } setSetting(key: string, value: string): void { this.authRepo.setSetting(key, value); } getAllSettings(): Record { return this.authRepo.getAllSettings(); } // ============ Users CRUD (delegated to repository) ============ async createUser(user: Omit): Promise { return this.authRepo.createUser(user); } getUserByUsername(username: string): IUser | null { return this.authRepo.getUserByUsername(username); } getAllUsers(): IUser[] { return this.authRepo.getAllUsers(); } updateUserPassword(username: string, passwordHash: string): void { this.authRepo.updateUserPassword(username, passwordHash); } deleteUser(username: string): void { this.authRepo.deleteUser(username); } // ============ Metrics (delegated to repository) ============ addMetric(metric: Omit): void { this.metricsRepo.addMetric(metric); } getMetrics(serviceId: number, limit = 100): IMetric[] { return this.metricsRepo.getMetrics(serviceId, limit); } // ============ Logs (delegated to repository) ============ addLog(log: Omit): void { this.metricsRepo.addLog(log); } getLogs(serviceId: number, limit = 1000): ILogEntry[] { return this.metricsRepo.getLogs(serviceId, limit); } // ============ SSL Certificates Legacy API (delegated to repository) ============ async createSSLCertificate(cert: Omit): Promise { return this.certificateRepo.createSSLCertificate(cert); } getSSLCertificate(domain: string): ISslCertificate | null { return this.certificateRepo.getSSLCertificate(domain); } getAllSSLCertificates(): ISslCertificate[] { return this.certificateRepo.getAllSSLCertificates(); } updateSSLCertificate(domain: string, updates: Partial): void { this.certificateRepo.updateSSLCertificate(domain, updates); } deleteSSLCertificate(domain: string): void { this.certificateRepo.deleteSSLCertificate(domain); } // ============ Domains (delegated to repository) ============ createDomain(domain: Omit): IDomain { return this.certificateRepo.createDomain(domain); } getDomainByName(domain: string): IDomain | null { return this.certificateRepo.getDomainByName(domain); } getDomainById(id: number): IDomain | null { return this.certificateRepo.getDomainById(id); } getAllDomains(): IDomain[] { return this.certificateRepo.getAllDomains(); } getDomainsByProvider(provider: 'cloudflare' | 'manual'): IDomain[] { return this.certificateRepo.getDomainsByProvider(provider); } updateDomain(id: number, updates: Partial): void { this.certificateRepo.updateDomain(id, updates); } deleteDomain(id: number): void { this.certificateRepo.deleteDomain(id); } // ============ Certificates (delegated to repository) ============ createCertificate(cert: Omit): ICertificate { return this.certificateRepo.createCertificate(cert); } getCertificateById(id: number): ICertificate | null { return this.certificateRepo.getCertificateById(id); } getCertificatesByDomain(domainId: number): ICertificate[] { return this.certificateRepo.getCertificatesByDomain(domainId); } getAllCertificates(): ICertificate[] { return this.certificateRepo.getAllCertificates(); } updateCertificate(id: number, updates: Partial): void { this.certificateRepo.updateCertificate(id, updates); } deleteCertificate(id: number): void { this.certificateRepo.deleteCertificate(id); } // ============ Certificate Requirements (delegated to repository) ============ createCertRequirement(req: Omit): ICertRequirement { return this.certificateRepo.createCertRequirement(req); } getCertRequirementById(id: number): ICertRequirement | null { return this.certificateRepo.getCertRequirementById(id); } getCertRequirementsByService(serviceId: number): ICertRequirement[] { return this.certificateRepo.getCertRequirementsByService(serviceId); } getCertRequirementsByDomain(domainId: number): ICertRequirement[] { return this.certificateRepo.getCertRequirementsByDomain(domainId); } getAllCertRequirements(): ICertRequirement[] { return this.certificateRepo.getAllCertRequirements(); } updateCertRequirement(id: number, updates: Partial): void { this.certificateRepo.updateCertRequirement(id, updates); } deleteCertRequirement(id: number): void { this.certificateRepo.deleteCertRequirement(id); } // ============ Registry Tokens (delegated to repository) ============ createRegistryToken(token: Omit): IRegistryToken { return this.registryRepo.createToken(token); } getRegistryTokenById(id: number): IRegistryToken | null { return this.registryRepo.getTokenById(id); } getRegistryTokenByHash(tokenHash: string): IRegistryToken | null { return this.registryRepo.getTokenByHash(tokenHash); } getAllRegistryTokens(): IRegistryToken[] { return this.registryRepo.getAllTokens(); } getRegistryTokensByType(type: 'global' | 'ci'): IRegistryToken[] { return this.registryRepo.getTokensByType(type); } updateRegistryTokenLastUsed(id: number): void { this.registryRepo.updateTokenLastUsed(id); } deleteRegistryToken(id: number): void { this.registryRepo.deleteToken(id); } // ============ Platform Services (delegated to repository) ============ createPlatformService(service: Omit): IPlatformService { return this.platformRepo.createPlatformService(service); } getPlatformServiceByName(name: string): IPlatformService | null { return this.platformRepo.getPlatformServiceByName(name); } getPlatformServiceById(id: number): IPlatformService | null { return this.platformRepo.getPlatformServiceById(id); } getPlatformServiceByType(type: TPlatformServiceType): IPlatformService | null { return this.platformRepo.getPlatformServiceByType(type); } getAllPlatformServices(): IPlatformService[] { return this.platformRepo.getAllPlatformServices(); } updatePlatformService(id: number, updates: Partial): void { this.platformRepo.updatePlatformService(id, updates); } deletePlatformService(id: number): void { this.platformRepo.deletePlatformService(id); } // ============ Platform Resources (delegated to repository) ============ createPlatformResource(resource: Omit): IPlatformResource { return this.platformRepo.createPlatformResource(resource); } getPlatformResourceById(id: number): IPlatformResource | null { return this.platformRepo.getPlatformResourceById(id); } getPlatformResourcesByService(serviceId: number): IPlatformResource[] { return this.platformRepo.getPlatformResourcesByService(serviceId); } getPlatformResourcesByPlatformService(platformServiceId: number): IPlatformResource[] { return this.platformRepo.getPlatformResourcesByPlatformService(platformServiceId); } getAllPlatformResources(): IPlatformResource[] { return this.platformRepo.getAllPlatformResources(); } deletePlatformResource(id: number): void { this.platformRepo.deletePlatformResource(id); } deletePlatformResourcesByService(serviceId: number): void { this.platformRepo.deletePlatformResourcesByService(serviceId); } }