feat(ssl): Add domain & certificate management, Cloudflare sync, SQLite cert manager, WebSocket realtime updates, and HTTP API SSL endpoints
This commit is contained in:
@@ -204,15 +204,313 @@ export class OneboxDatabase {
|
||||
private async runMigrations(): Promise<void> {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const currentVersion = this.getMigrationVersion();
|
||||
logger.debug(`Current database version: ${currentVersion}`);
|
||||
try {
|
||||
const currentVersion = this.getMigrationVersion();
|
||||
logger.info(`Current database migration version: ${currentVersion}`);
|
||||
|
||||
// Add migration logic here as needed
|
||||
// For now, just set version to 1
|
||||
if (currentVersion === 0) {
|
||||
this.setMigrationVersion(1);
|
||||
// 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...');
|
||||
|
||||
// For each table, we need to:
|
||||
// 1. Create new table with REAL timestamps
|
||||
// 2. Copy data
|
||||
// 3. Drop old table
|
||||
// 4. Rename new table
|
||||
|
||||
// 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...');
|
||||
|
||||
// 1. Create domains table
|
||||
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
|
||||
)
|
||||
`);
|
||||
|
||||
// 2. Create certificates table (renamed from ssl_certificates)
|
||||
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
|
||||
)
|
||||
`);
|
||||
|
||||
// 3. Create cert_requirements table
|
||||
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
|
||||
)
|
||||
`);
|
||||
|
||||
// 4. Migrate existing ssl_certificates data
|
||||
// Extract unique base domains from existing certificates
|
||||
const existingCerts = this.query('SELECT * FROM ssl_certificates');
|
||||
|
||||
const now = Date.now();
|
||||
const domainMap = new Map<string, number>();
|
||||
|
||||
// Create domain entries for each unique base domain
|
||||
for (const cert of existingCerts) {
|
||||
const domain = String(cert.domain ?? cert[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('SELECT last_insert_rowid() as id');
|
||||
const domainId = result[0].id ?? result[0][0];
|
||||
domainMap.set(domain, Number(domainId));
|
||||
}
|
||||
}
|
||||
|
||||
// Migrate certificates to new table
|
||||
for (const cert of existingCerts) {
|
||||
const domain = String(cert.domain ?? cert[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, // We don't know if it's wildcard, default to false
|
||||
String(cert.cert_path ?? cert[2]),
|
||||
String(cert.key_path ?? cert[3]),
|
||||
String(cert.full_chain_path ?? cert[4]),
|
||||
Number(cert.expiry_date ?? cert[5]),
|
||||
String(cert.issuer ?? cert[6]),
|
||||
1, // Assume valid
|
||||
Number(cert.created_at ?? cert[7]),
|
||||
Number(cert.updated_at ?? cert[8])
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Drop old ssl_certificates table
|
||||
this.query('DROP TABLE ssl_certificates');
|
||||
|
||||
// 6. Create indices for performance
|
||||
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');
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`Migration failed: ${error.message}`);
|
||||
logger.error(`Stack: ${error.stack}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current migration version
|
||||
@@ -222,8 +520,13 @@ export class OneboxDatabase {
|
||||
|
||||
try {
|
||||
const result = this.query('SELECT MAX(version) as version FROM migrations');
|
||||
return result.length > 0 && result[0][0] !== null ? Number(result[0][0]) : 0;
|
||||
} catch {
|
||||
if (result.length === 0) return 0;
|
||||
|
||||
// Handle both array and object access patterns
|
||||
const versionValue = result[0].version ?? result[0][0];
|
||||
return versionValue !== null && versionValue !== undefined ? Number(versionValue) : 0;
|
||||
} catch (error) {
|
||||
logger.warn(`Error getting migration version: ${error.message}, defaulting to 0`);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -695,4 +998,321 @@ export class OneboxDatabase {
|
||||
updatedAt: Number(row.updated_at || row[8]),
|
||||
};
|
||||
}
|
||||
|
||||
// ============ Domains ============
|
||||
|
||||
createDomain(domain: Omit<IDomain, 'id'>): IDomain {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
this.query(
|
||||
`INSERT INTO domains (domain, dns_provider, cloudflare_zone_id, is_obsolete, default_wildcard, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
domain.domain,
|
||||
domain.dnsProvider,
|
||||
domain.cloudflareZoneId,
|
||||
domain.isObsolete ? 1 : 0,
|
||||
domain.defaultWildcard ? 1 : 0,
|
||||
domain.createdAt,
|
||||
domain.updatedAt,
|
||||
]
|
||||
);
|
||||
|
||||
return this.getDomainByName(domain.domain)!;
|
||||
}
|
||||
|
||||
getDomainByName(domain: string): IDomain | null {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.query('SELECT * FROM domains WHERE domain = ?', [domain]);
|
||||
return rows.length > 0 ? this.rowToDomain(rows[0]) : null;
|
||||
}
|
||||
|
||||
getDomainById(id: number): IDomain | null {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.query('SELECT * FROM domains WHERE id = ?', [id]);
|
||||
return rows.length > 0 ? this.rowToDomain(rows[0]) : null;
|
||||
}
|
||||
|
||||
getAllDomains(): IDomain[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.query('SELECT * FROM domains ORDER BY domain ASC');
|
||||
return rows.map((row) => this.rowToDomain(row));
|
||||
}
|
||||
|
||||
getDomainsByProvider(provider: 'cloudflare' | 'manual'): IDomain[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.query('SELECT * FROM domains WHERE dns_provider = ? ORDER BY domain ASC', [
|
||||
provider,
|
||||
]);
|
||||
return rows.map((row) => this.rowToDomain(row));
|
||||
}
|
||||
|
||||
updateDomain(id: number, updates: Partial<IDomain>): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const fields: string[] = [];
|
||||
const values: unknown[] = [];
|
||||
|
||||
if (updates.domain !== undefined) {
|
||||
fields.push('domain = ?');
|
||||
values.push(updates.domain);
|
||||
}
|
||||
if (updates.dnsProvider !== undefined) {
|
||||
fields.push('dns_provider = ?');
|
||||
values.push(updates.dnsProvider);
|
||||
}
|
||||
if (updates.cloudflareZoneId !== undefined) {
|
||||
fields.push('cloudflare_zone_id = ?');
|
||||
values.push(updates.cloudflareZoneId);
|
||||
}
|
||||
if (updates.isObsolete !== undefined) {
|
||||
fields.push('is_obsolete = ?');
|
||||
values.push(updates.isObsolete ? 1 : 0);
|
||||
}
|
||||
if (updates.defaultWildcard !== undefined) {
|
||||
fields.push('default_wildcard = ?');
|
||||
values.push(updates.defaultWildcard ? 1 : 0);
|
||||
}
|
||||
|
||||
fields.push('updated_at = ?');
|
||||
values.push(Date.now());
|
||||
values.push(id);
|
||||
|
||||
this.query(`UPDATE domains SET ${fields.join(', ')} WHERE id = ?`, values);
|
||||
}
|
||||
|
||||
deleteDomain(id: number): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
this.query('DELETE FROM domains WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
private rowToDomain(row: any): IDomain {
|
||||
return {
|
||||
id: Number(row.id || row[0]),
|
||||
domain: String(row.domain || row[1]),
|
||||
dnsProvider: (row.dns_provider || row[2]) as IDomain['dnsProvider'],
|
||||
cloudflareZoneId: row.cloudflare_zone_id || row[3] || undefined,
|
||||
isObsolete: Boolean(row.is_obsolete || row[4]),
|
||||
defaultWildcard: Boolean(row.default_wildcard || row[5]),
|
||||
createdAt: Number(row.created_at || row[6]),
|
||||
updatedAt: Number(row.updated_at || row[7]),
|
||||
};
|
||||
}
|
||||
|
||||
// ============ Certificates ============
|
||||
|
||||
createCertificate(cert: Omit<ICertificate, 'id'>): ICertificate {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
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 (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
cert.domainId,
|
||||
cert.certDomain,
|
||||
cert.isWildcard ? 1 : 0,
|
||||
cert.certPath,
|
||||
cert.keyPath,
|
||||
cert.fullChainPath,
|
||||
cert.expiryDate,
|
||||
cert.issuer,
|
||||
cert.isValid ? 1 : 0,
|
||||
cert.createdAt,
|
||||
cert.updatedAt,
|
||||
]
|
||||
);
|
||||
|
||||
const rows = this.query('SELECT * FROM certificates WHERE id = last_insert_rowid()');
|
||||
return this.rowToCertificate(rows[0]);
|
||||
}
|
||||
|
||||
getCertificateById(id: number): ICertificate | null {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.query('SELECT * FROM certificates WHERE id = ?', [id]);
|
||||
return rows.length > 0 ? this.rowToCertificate(rows[0]) : null;
|
||||
}
|
||||
|
||||
getCertificatesByDomain(domainId: number): ICertificate[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.query('SELECT * FROM certificates WHERE domain_id = ? ORDER BY expiry_date DESC', [
|
||||
domainId,
|
||||
]);
|
||||
return rows.map((row) => this.rowToCertificate(row));
|
||||
}
|
||||
|
||||
getAllCertificates(): ICertificate[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.query('SELECT * FROM certificates ORDER BY expiry_date DESC');
|
||||
return rows.map((row) => this.rowToCertificate(row));
|
||||
}
|
||||
|
||||
updateCertificate(id: number, updates: Partial<ICertificate>): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const fields: string[] = [];
|
||||
const values: unknown[] = [];
|
||||
|
||||
if (updates.certDomain !== undefined) {
|
||||
fields.push('cert_domain = ?');
|
||||
values.push(updates.certDomain);
|
||||
}
|
||||
if (updates.isWildcard !== undefined) {
|
||||
fields.push('is_wildcard = ?');
|
||||
values.push(updates.isWildcard ? 1 : 0);
|
||||
}
|
||||
if (updates.certPath !== undefined) {
|
||||
fields.push('cert_path = ?');
|
||||
values.push(updates.certPath);
|
||||
}
|
||||
if (updates.keyPath !== undefined) {
|
||||
fields.push('key_path = ?');
|
||||
values.push(updates.keyPath);
|
||||
}
|
||||
if (updates.fullChainPath !== undefined) {
|
||||
fields.push('full_chain_path = ?');
|
||||
values.push(updates.fullChainPath);
|
||||
}
|
||||
if (updates.expiryDate !== undefined) {
|
||||
fields.push('expiry_date = ?');
|
||||
values.push(updates.expiryDate);
|
||||
}
|
||||
if (updates.issuer !== undefined) {
|
||||
fields.push('issuer = ?');
|
||||
values.push(updates.issuer);
|
||||
}
|
||||
if (updates.isValid !== undefined) {
|
||||
fields.push('is_valid = ?');
|
||||
values.push(updates.isValid ? 1 : 0);
|
||||
}
|
||||
|
||||
fields.push('updated_at = ?');
|
||||
values.push(Date.now());
|
||||
values.push(id);
|
||||
|
||||
this.query(`UPDATE certificates SET ${fields.join(', ')} WHERE id = ?`, values);
|
||||
}
|
||||
|
||||
deleteCertificate(id: number): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
this.query('DELETE FROM certificates WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
private rowToCertificate(row: any): ICertificate {
|
||||
return {
|
||||
id: Number(row.id || row[0]),
|
||||
domainId: Number(row.domain_id || row[1]),
|
||||
certDomain: String(row.cert_domain || row[2]),
|
||||
isWildcard: Boolean(row.is_wildcard || row[3]),
|
||||
certPath: String(row.cert_path || row[4]),
|
||||
keyPath: String(row.key_path || row[5]),
|
||||
fullChainPath: String(row.full_chain_path || row[6]),
|
||||
expiryDate: Number(row.expiry_date || row[7]),
|
||||
issuer: String(row.issuer || row[8]),
|
||||
isValid: Boolean(row.is_valid || row[9]),
|
||||
createdAt: Number(row.created_at || row[10]),
|
||||
updatedAt: Number(row.updated_at || row[11]),
|
||||
};
|
||||
}
|
||||
|
||||
// ============ Certificate Requirements ============
|
||||
|
||||
createCertRequirement(req: Omit<ICertRequirement, 'id'>): ICertRequirement {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
this.query(
|
||||
`INSERT INTO cert_requirements (service_id, domain_id, subdomain, certificate_id, status, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
req.serviceId,
|
||||
req.domainId,
|
||||
req.subdomain,
|
||||
req.certificateId,
|
||||
req.status,
|
||||
req.createdAt,
|
||||
req.updatedAt,
|
||||
]
|
||||
);
|
||||
|
||||
const rows = this.query('SELECT * FROM cert_requirements WHERE id = last_insert_rowid()');
|
||||
return this.rowToCertRequirement(rows[0]);
|
||||
}
|
||||
|
||||
getCertRequirementById(id: number): ICertRequirement | null {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.query('SELECT * FROM cert_requirements WHERE id = ?', [id]);
|
||||
return rows.length > 0 ? this.rowToCertRequirement(rows[0]) : null;
|
||||
}
|
||||
|
||||
getCertRequirementsByService(serviceId: number): ICertRequirement[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.query('SELECT * FROM cert_requirements WHERE service_id = ?', [serviceId]);
|
||||
return rows.map((row) => this.rowToCertRequirement(row));
|
||||
}
|
||||
|
||||
getCertRequirementsByDomain(domainId: number): ICertRequirement[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.query('SELECT * FROM cert_requirements WHERE domain_id = ?', [domainId]);
|
||||
return rows.map((row) => this.rowToCertRequirement(row));
|
||||
}
|
||||
|
||||
getAllCertRequirements(): ICertRequirement[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.query('SELECT * FROM cert_requirements ORDER BY created_at DESC');
|
||||
return rows.map((row) => this.rowToCertRequirement(row));
|
||||
}
|
||||
|
||||
updateCertRequirement(id: number, updates: Partial<ICertRequirement>): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const fields: string[] = [];
|
||||
const values: unknown[] = [];
|
||||
|
||||
if (updates.subdomain !== undefined) {
|
||||
fields.push('subdomain = ?');
|
||||
values.push(updates.subdomain);
|
||||
}
|
||||
if (updates.certificateId !== undefined) {
|
||||
fields.push('certificate_id = ?');
|
||||
values.push(updates.certificateId);
|
||||
}
|
||||
if (updates.status !== undefined) {
|
||||
fields.push('status = ?');
|
||||
values.push(updates.status);
|
||||
}
|
||||
|
||||
fields.push('updated_at = ?');
|
||||
values.push(Date.now());
|
||||
values.push(id);
|
||||
|
||||
this.query(`UPDATE cert_requirements SET ${fields.join(', ')} WHERE id = ?`, values);
|
||||
}
|
||||
|
||||
deleteCertRequirement(id: number): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
this.query('DELETE FROM cert_requirements WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
private rowToCertRequirement(row: any): ICertRequirement {
|
||||
return {
|
||||
id: Number(row.id || row[0]),
|
||||
serviceId: Number(row.service_id || row[1]),
|
||||
domainId: Number(row.domain_id || row[2]),
|
||||
subdomain: String(row.subdomain || row[3]),
|
||||
certificateId: row.certificate_id || row[4] || undefined,
|
||||
status: String(row.status || row[5]) as ICertRequirement['status'],
|
||||
createdAt: Number(row.created_at || row[6]),
|
||||
updatedAt: Number(row.updated_at || row[7]),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user