Initial commit: Onebox v1.0.0
- Complete Deno-based architecture following nupst/spark patterns - SQLite database with full schema - Docker container management - Service orchestration (Docker + Nginx + DNS + SSL) - Registry authentication - Nginx reverse proxy configuration - Cloudflare DNS integration - Let's Encrypt SSL automation - Background daemon with metrics collection - HTTP API server - Comprehensive CLI - Cross-platform compilation setup - NPM distribution wrapper - Shell installer script Core features: - Deploy containers with single command - Automatic domain configuration - Automatic SSL certificates - Multi-registry support - Metrics and logging - Systemd integration Ready for Angular UI implementation and testing.
This commit is contained in:
659
ts/onebox.classes.database.ts
Normal file
659
ts/onebox.classes.database.ts
Normal file
@@ -0,0 +1,659 @@
|
||||
/**
|
||||
* Database layer for Onebox using SQLite
|
||||
*/
|
||||
|
||||
import * as plugins from './onebox.plugins.ts';
|
||||
import type {
|
||||
IService,
|
||||
IRegistry,
|
||||
INginxConfig,
|
||||
ISslCertificate,
|
||||
IDnsRecord,
|
||||
IMetric,
|
||||
ILogEntry,
|
||||
IUser,
|
||||
ISetting,
|
||||
} from './onebox.types.ts';
|
||||
import { logger } from './onebox.logging.ts';
|
||||
|
||||
export class OneboxDatabase {
|
||||
private db: plugins.sqlite.DB | null = null;
|
||||
private dbPath: string;
|
||||
|
||||
constructor(dbPath = '/var/lib/onebox/onebox.db') {
|
||||
this.dbPath = dbPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize database connection and create tables
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
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();
|
||||
} catch (error) {
|
||||
logger.error(`Failed to initialize database: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all database tables
|
||||
*/
|
||||
private async createTables(): Promise<void> {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
// Services table
|
||||
this.db.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.db.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.db.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.db.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.db.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,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
// Metrics table
|
||||
this.db.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.db.query(`
|
||||
CREATE INDEX IF NOT EXISTS idx_metrics_service_timestamp
|
||||
ON metrics(service_id, timestamp DESC)
|
||||
`);
|
||||
|
||||
// Logs table
|
||||
this.db.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.db.query(`
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_service_timestamp
|
||||
ON logs(service_id, timestamp DESC)
|
||||
`);
|
||||
|
||||
// Users table
|
||||
this.db.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.db.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.db.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<void> {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const currentVersion = this.getMigrationVersion();
|
||||
logger.debug(`Current database version: ${currentVersion}`);
|
||||
|
||||
// Add migration logic here as needed
|
||||
// For now, just set version to 1
|
||||
if (currentVersion === 0) {
|
||||
this.setMigrationVersion(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current migration version
|
||||
*/
|
||||
private getMigrationVersion(): number {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
try {
|
||||
const result = this.db.query('SELECT MAX(version) as version FROM migrations');
|
||||
return result.length > 0 && result[0][0] !== null ? Number(result[0][0]) : 0;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set migration version
|
||||
*/
|
||||
private setMigrationVersion(version: number): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
this.db.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<T = unknown[]>(sql: string, params: unknown[] = []): T[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
return this.db.query(sql, params) as T[];
|
||||
}
|
||||
|
||||
// ============ Services CRUD ============
|
||||
|
||||
async createService(service: Omit<IService, 'id'>): Promise<IService> {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const now = Date.now();
|
||||
this.db.query(
|
||||
`INSERT INTO services (name, image, registry, env_vars, port, domain, container_id, status, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
service.name,
|
||||
service.image,
|
||||
service.registry || null,
|
||||
JSON.stringify(service.envVars),
|
||||
service.port,
|
||||
service.domain || null,
|
||||
service.containerID || null,
|
||||
service.status,
|
||||
now,
|
||||
now,
|
||||
]
|
||||
);
|
||||
|
||||
return this.getServiceByName(service.name)!;
|
||||
}
|
||||
|
||||
getServiceByName(name: string): IService | null {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query('SELECT * FROM services WHERE name = ?', [name]);
|
||||
return rows.length > 0 ? this.rowToService(rows[0]) : null;
|
||||
}
|
||||
|
||||
getServiceByID(id: number): IService | null {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query('SELECT * FROM services WHERE id = ?', [id]);
|
||||
return rows.length > 0 ? this.rowToService(rows[0]) : null;
|
||||
}
|
||||
|
||||
getAllServices(): IService[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query('SELECT * FROM services ORDER BY created_at DESC');
|
||||
return rows.map((row) => this.rowToService(row));
|
||||
}
|
||||
|
||||
updateService(id: number, updates: Partial<IService>): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const fields: string[] = [];
|
||||
const values: unknown[] = [];
|
||||
|
||||
if (updates.image !== undefined) {
|
||||
fields.push('image = ?');
|
||||
values.push(updates.image);
|
||||
}
|
||||
if (updates.registry !== undefined) {
|
||||
fields.push('registry = ?');
|
||||
values.push(updates.registry);
|
||||
}
|
||||
if (updates.envVars !== undefined) {
|
||||
fields.push('env_vars = ?');
|
||||
values.push(JSON.stringify(updates.envVars));
|
||||
}
|
||||
if (updates.port !== undefined) {
|
||||
fields.push('port = ?');
|
||||
values.push(updates.port);
|
||||
}
|
||||
if (updates.domain !== undefined) {
|
||||
fields.push('domain = ?');
|
||||
values.push(updates.domain);
|
||||
}
|
||||
if (updates.containerID !== undefined) {
|
||||
fields.push('container_id = ?');
|
||||
values.push(updates.containerID);
|
||||
}
|
||||
if (updates.status !== undefined) {
|
||||
fields.push('status = ?');
|
||||
values.push(updates.status);
|
||||
}
|
||||
|
||||
fields.push('updated_at = ?');
|
||||
values.push(Date.now());
|
||||
values.push(id);
|
||||
|
||||
this.db.query(`UPDATE services SET ${fields.join(', ')} WHERE id = ?`, values);
|
||||
}
|
||||
|
||||
deleteService(id: number): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
this.db.query('DELETE FROM services WHERE id = ?', [id]);
|
||||
}
|
||||
|
||||
private rowToService(row: unknown[]): IService {
|
||||
return {
|
||||
id: Number(row[0]),
|
||||
name: String(row[1]),
|
||||
image: String(row[2]),
|
||||
registry: row[3] ? String(row[3]) : undefined,
|
||||
envVars: JSON.parse(String(row[4])),
|
||||
port: Number(row[5]),
|
||||
domain: row[6] ? String(row[6]) : undefined,
|
||||
containerID: row[7] ? String(row[7]) : undefined,
|
||||
status: String(row[8]) as IService['status'],
|
||||
createdAt: Number(row[9]),
|
||||
updatedAt: Number(row[10]),
|
||||
};
|
||||
}
|
||||
|
||||
// ============ Registries CRUD ============
|
||||
|
||||
async createRegistry(registry: Omit<IRegistry, 'id'>): Promise<IRegistry> {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const now = Date.now();
|
||||
this.db.query(
|
||||
'INSERT INTO registries (url, username, password_encrypted, created_at) VALUES (?, ?, ?, ?)',
|
||||
[registry.url, registry.username, registry.passwordEncrypted, now]
|
||||
);
|
||||
|
||||
return this.getRegistryByURL(registry.url)!;
|
||||
}
|
||||
|
||||
getRegistryByURL(url: string): IRegistry | null {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query('SELECT * FROM registries WHERE url = ?', [url]);
|
||||
return rows.length > 0 ? this.rowToRegistry(rows[0]) : null;
|
||||
}
|
||||
|
||||
getAllRegistries(): IRegistry[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query('SELECT * FROM registries ORDER BY created_at DESC');
|
||||
return rows.map((row) => this.rowToRegistry(row));
|
||||
}
|
||||
|
||||
deleteRegistry(url: string): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
this.db.query('DELETE FROM registries WHERE url = ?', [url]);
|
||||
}
|
||||
|
||||
private rowToRegistry(row: unknown[]): IRegistry {
|
||||
return {
|
||||
id: Number(row[0]),
|
||||
url: String(row[1]),
|
||||
username: String(row[2]),
|
||||
passwordEncrypted: String(row[3]),
|
||||
createdAt: Number(row[4]),
|
||||
};
|
||||
}
|
||||
|
||||
// ============ Settings CRUD ============
|
||||
|
||||
getSetting(key: string): string | null {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query('SELECT value FROM settings WHERE key = ?', [key]);
|
||||
return rows.length > 0 ? String(rows[0][0]) : null;
|
||||
}
|
||||
|
||||
setSetting(key: string, value: string): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const now = Date.now();
|
||||
this.db.query(
|
||||
'INSERT OR REPLACE INTO settings (key, value, updated_at) VALUES (?, ?, ?)',
|
||||
[key, value, now]
|
||||
);
|
||||
}
|
||||
|
||||
getAllSettings(): Record<string, string> {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query('SELECT key, value FROM settings');
|
||||
const settings: Record<string, string> = {};
|
||||
for (const row of rows) {
|
||||
settings[String(row[0])] = String(row[1]);
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
// ============ Users CRUD ============
|
||||
|
||||
async createUser(user: Omit<IUser, 'id'>): Promise<IUser> {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const now = Date.now();
|
||||
this.db.query(
|
||||
'INSERT INTO users (username, password_hash, role, created_at, updated_at) VALUES (?, ?, ?, ?, ?)',
|
||||
[user.username, user.passwordHash, user.role, now, now]
|
||||
);
|
||||
|
||||
return this.getUserByUsername(user.username)!;
|
||||
}
|
||||
|
||||
getUserByUsername(username: string): IUser | null {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query('SELECT * FROM users WHERE username = ?', [username]);
|
||||
return rows.length > 0 ? this.rowToUser(rows[0]) : null;
|
||||
}
|
||||
|
||||
getAllUsers(): IUser[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query('SELECT * FROM users ORDER BY created_at DESC');
|
||||
return rows.map((row) => this.rowToUser(row));
|
||||
}
|
||||
|
||||
updateUserPassword(username: string, passwordHash: string): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
this.db.query('UPDATE users SET password_hash = ?, updated_at = ? WHERE username = ?', [
|
||||
passwordHash,
|
||||
Date.now(),
|
||||
username,
|
||||
]);
|
||||
}
|
||||
|
||||
deleteUser(username: string): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
this.db.query('DELETE FROM users WHERE username = ?', [username]);
|
||||
}
|
||||
|
||||
private rowToUser(row: unknown[]): IUser {
|
||||
return {
|
||||
id: Number(row[0]),
|
||||
username: String(row[1]),
|
||||
passwordHash: String(row[2]),
|
||||
role: String(row[3]) as IUser['role'],
|
||||
createdAt: Number(row[4]),
|
||||
updatedAt: Number(row[5]),
|
||||
};
|
||||
}
|
||||
|
||||
// ============ Metrics ============
|
||||
|
||||
addMetric(metric: Omit<IMetric, 'id'>): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
this.db.query(
|
||||
`INSERT INTO metrics (service_id, timestamp, cpu_percent, memory_used, memory_limit, network_rx_bytes, network_tx_bytes)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
metric.serviceId,
|
||||
metric.timestamp,
|
||||
metric.cpuPercent,
|
||||
metric.memoryUsed,
|
||||
metric.memoryLimit,
|
||||
metric.networkRxBytes,
|
||||
metric.networkTxBytes,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
getMetrics(serviceId: number, limit = 100): IMetric[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query(
|
||||
'SELECT * FROM metrics WHERE service_id = ? ORDER BY timestamp DESC LIMIT ?',
|
||||
[serviceId, limit]
|
||||
);
|
||||
return rows.map((row) => this.rowToMetric(row));
|
||||
}
|
||||
|
||||
private rowToMetric(row: unknown[]): IMetric {
|
||||
return {
|
||||
id: Number(row[0]),
|
||||
serviceId: Number(row[1]),
|
||||
timestamp: Number(row[2]),
|
||||
cpuPercent: Number(row[3]),
|
||||
memoryUsed: Number(row[4]),
|
||||
memoryLimit: Number(row[5]),
|
||||
networkRxBytes: Number(row[6]),
|
||||
networkTxBytes: Number(row[7]),
|
||||
};
|
||||
}
|
||||
|
||||
// ============ Logs ============
|
||||
|
||||
addLog(log: Omit<ILogEntry, 'id'>): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
this.db.query(
|
||||
'INSERT INTO logs (service_id, timestamp, message, level, source) VALUES (?, ?, ?, ?, ?)',
|
||||
[log.serviceId, log.timestamp, log.message, log.level, log.source]
|
||||
);
|
||||
}
|
||||
|
||||
getLogs(serviceId: number, limit = 1000): ILogEntry[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query(
|
||||
'SELECT * FROM logs WHERE service_id = ? ORDER BY timestamp DESC LIMIT ?',
|
||||
[serviceId, limit]
|
||||
);
|
||||
return rows.map((row) => this.rowToLog(row));
|
||||
}
|
||||
|
||||
private rowToLog(row: unknown[]): ILogEntry {
|
||||
return {
|
||||
id: Number(row[0]),
|
||||
serviceId: Number(row[1]),
|
||||
timestamp: Number(row[2]),
|
||||
message: String(row[3]),
|
||||
level: String(row[4]) as ILogEntry['level'],
|
||||
source: String(row[5]) as ILogEntry['source'],
|
||||
};
|
||||
}
|
||||
|
||||
// ============ SSL Certificates ============
|
||||
|
||||
async createSSLCertificate(cert: Omit<ISslCertificate, 'id'>): Promise<ISslCertificate> {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const now = Date.now();
|
||||
this.db.query(
|
||||
`INSERT INTO ssl_certificates (domain, cert_path, key_path, full_chain_path, expiry_date, issuer, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
cert.domain,
|
||||
cert.certPath,
|
||||
cert.keyPath,
|
||||
cert.fullChainPath,
|
||||
cert.expiryDate,
|
||||
cert.issuer,
|
||||
now,
|
||||
now,
|
||||
]
|
||||
);
|
||||
|
||||
return this.getSSLCertificate(cert.domain)!;
|
||||
}
|
||||
|
||||
getSSLCertificate(domain: string): ISslCertificate | null {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query('SELECT * FROM ssl_certificates WHERE domain = ?', [domain]);
|
||||
return rows.length > 0 ? this.rowToSSLCert(rows[0]) : null;
|
||||
}
|
||||
|
||||
getAllSSLCertificates(): ISslCertificate[] {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const rows = this.db.query('SELECT * FROM ssl_certificates ORDER BY expiry_date ASC');
|
||||
return rows.map((row) => this.rowToSSLCert(row));
|
||||
}
|
||||
|
||||
updateSSLCertificate(domain: string, updates: Partial<ISslCertificate>): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
|
||||
const fields: string[] = [];
|
||||
const values: unknown[] = [];
|
||||
|
||||
if (updates.certPath) {
|
||||
fields.push('cert_path = ?');
|
||||
values.push(updates.certPath);
|
||||
}
|
||||
if (updates.keyPath) {
|
||||
fields.push('key_path = ?');
|
||||
values.push(updates.keyPath);
|
||||
}
|
||||
if (updates.fullChainPath) {
|
||||
fields.push('full_chain_path = ?');
|
||||
values.push(updates.fullChainPath);
|
||||
}
|
||||
if (updates.expiryDate) {
|
||||
fields.push('expiry_date = ?');
|
||||
values.push(updates.expiryDate);
|
||||
}
|
||||
|
||||
fields.push('updated_at = ?');
|
||||
values.push(Date.now());
|
||||
values.push(domain);
|
||||
|
||||
this.db.query(`UPDATE ssl_certificates SET ${fields.join(', ')} WHERE domain = ?`, values);
|
||||
}
|
||||
|
||||
deleteSSLCertificate(domain: string): void {
|
||||
if (!this.db) throw new Error('Database not initialized');
|
||||
this.db.query('DELETE FROM ssl_certificates WHERE domain = ?', [domain]);
|
||||
}
|
||||
|
||||
private rowToSSLCert(row: unknown[]): ISslCertificate {
|
||||
return {
|
||||
id: Number(row[0]),
|
||||
domain: String(row[1]),
|
||||
certPath: String(row[2]),
|
||||
keyPath: String(row[3]),
|
||||
fullChainPath: String(row[4]),
|
||||
expiryDate: Number(row[5]),
|
||||
issuer: String(row[6]),
|
||||
createdAt: Number(row[7]),
|
||||
updatedAt: Number(row[8]),
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user