250 lines
8.2 KiB
TypeScript
250 lines
8.2 KiB
TypeScript
/**
|
|
* Backup Repository
|
|
* Handles CRUD operations for backups and backup_schedules tables
|
|
*/
|
|
|
|
import { BaseRepository } from '../base.repository.ts';
|
|
import type {
|
|
IBackup,
|
|
IBackupSchedule,
|
|
IBackupScheduleUpdate,
|
|
TPlatformServiceType,
|
|
TBackupScheduleScope,
|
|
IRetentionPolicy,
|
|
} from '../../types.ts';
|
|
|
|
export class BackupRepository extends BaseRepository {
|
|
// ============ Backup CRUD ============
|
|
|
|
create(backup: Omit<IBackup, 'id'>): IBackup {
|
|
this.query(
|
|
`INSERT INTO backups (
|
|
service_id, service_name, filename, size_bytes, created_at,
|
|
includes_image, platform_resources, checksum, schedule_id
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
backup.serviceId,
|
|
backup.serviceName,
|
|
backup.filename,
|
|
backup.sizeBytes,
|
|
backup.createdAt,
|
|
backup.includesImage ? 1 : 0,
|
|
JSON.stringify(backup.platformResources),
|
|
backup.checksum,
|
|
backup.scheduleId ?? null,
|
|
]
|
|
);
|
|
|
|
// Get the created backup by looking for the most recent one with matching filename
|
|
const rows = this.query(
|
|
'SELECT * FROM backups WHERE filename = ? ORDER BY id DESC LIMIT 1',
|
|
[backup.filename]
|
|
);
|
|
|
|
return this.rowToBackup(rows[0]);
|
|
}
|
|
|
|
getById(id: number): IBackup | null {
|
|
const rows = this.query('SELECT * FROM backups WHERE id = ?', [id]);
|
|
return rows.length > 0 ? this.rowToBackup(rows[0]) : null;
|
|
}
|
|
|
|
getByService(serviceId: number): IBackup[] {
|
|
const rows = this.query(
|
|
'SELECT * FROM backups WHERE service_id = ? ORDER BY created_at DESC',
|
|
[serviceId]
|
|
);
|
|
return rows.map((row) => this.rowToBackup(row));
|
|
}
|
|
|
|
getAll(): IBackup[] {
|
|
const rows = this.query('SELECT * FROM backups ORDER BY created_at DESC');
|
|
return rows.map((row) => this.rowToBackup(row));
|
|
}
|
|
|
|
delete(id: number): void {
|
|
this.query('DELETE FROM backups WHERE id = ?', [id]);
|
|
}
|
|
|
|
deleteByService(serviceId: number): void {
|
|
this.query('DELETE FROM backups WHERE service_id = ?', [serviceId]);
|
|
}
|
|
|
|
getBySchedule(scheduleId: number): IBackup[] {
|
|
const rows = this.query(
|
|
'SELECT * FROM backups WHERE schedule_id = ? ORDER BY created_at DESC',
|
|
[scheduleId]
|
|
);
|
|
return rows.map((row) => this.rowToBackup(row));
|
|
}
|
|
|
|
private rowToBackup(row: any): IBackup {
|
|
let platformResources: TPlatformServiceType[] = [];
|
|
const platformResourcesRaw = row.platform_resources;
|
|
if (platformResourcesRaw) {
|
|
try {
|
|
platformResources = JSON.parse(String(platformResourcesRaw));
|
|
} catch {
|
|
platformResources = [];
|
|
}
|
|
}
|
|
|
|
return {
|
|
id: Number(row.id),
|
|
serviceId: Number(row.service_id),
|
|
serviceName: String(row.service_name),
|
|
filename: String(row.filename),
|
|
sizeBytes: Number(row.size_bytes),
|
|
createdAt: Number(row.created_at),
|
|
includesImage: Boolean(row.includes_image),
|
|
platformResources,
|
|
checksum: String(row.checksum),
|
|
scheduleId: row.schedule_id ? Number(row.schedule_id) : undefined,
|
|
};
|
|
}
|
|
|
|
// ============ Backup Schedule CRUD ============
|
|
|
|
createSchedule(schedule: Omit<IBackupSchedule, 'id'>): IBackupSchedule {
|
|
const now = Date.now();
|
|
this.query(
|
|
`INSERT INTO backup_schedules (
|
|
scope_type, scope_pattern, service_id, service_name, cron_expression,
|
|
retention_hourly, retention_daily, retention_weekly, retention_monthly,
|
|
enabled, last_run_at, next_run_at, last_status, last_error, created_at, updated_at
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
[
|
|
schedule.scopeType,
|
|
schedule.scopePattern ?? null,
|
|
schedule.serviceId ?? null,
|
|
schedule.serviceName ?? null,
|
|
schedule.cronExpression,
|
|
schedule.retention.hourly,
|
|
schedule.retention.daily,
|
|
schedule.retention.weekly,
|
|
schedule.retention.monthly,
|
|
schedule.enabled ? 1 : 0,
|
|
schedule.lastRunAt,
|
|
schedule.nextRunAt,
|
|
schedule.lastStatus,
|
|
schedule.lastError,
|
|
now,
|
|
now,
|
|
]
|
|
);
|
|
|
|
// Get the created schedule by looking for the most recent one with matching scope
|
|
const rows = this.query(
|
|
'SELECT * FROM backup_schedules WHERE scope_type = ? AND cron_expression = ? ORDER BY id DESC LIMIT 1',
|
|
[schedule.scopeType, schedule.cronExpression]
|
|
);
|
|
|
|
return this.rowToSchedule(rows[0]);
|
|
}
|
|
|
|
getScheduleById(id: number): IBackupSchedule | null {
|
|
const rows = this.query('SELECT * FROM backup_schedules WHERE id = ?', [id]);
|
|
return rows.length > 0 ? this.rowToSchedule(rows[0]) : null;
|
|
}
|
|
|
|
getSchedulesByService(serviceId: number): IBackupSchedule[] {
|
|
const rows = this.query(
|
|
'SELECT * FROM backup_schedules WHERE service_id = ? ORDER BY created_at DESC',
|
|
[serviceId]
|
|
);
|
|
return rows.map((row) => this.rowToSchedule(row));
|
|
}
|
|
|
|
getEnabledSchedules(): IBackupSchedule[] {
|
|
const rows = this.query(
|
|
'SELECT * FROM backup_schedules WHERE enabled = 1 ORDER BY next_run_at ASC'
|
|
);
|
|
return rows.map((row) => this.rowToSchedule(row));
|
|
}
|
|
|
|
getAllSchedules(): IBackupSchedule[] {
|
|
const rows = this.query('SELECT * FROM backup_schedules ORDER BY created_at DESC');
|
|
return rows.map((row) => this.rowToSchedule(row));
|
|
}
|
|
|
|
updateSchedule(id: number, updates: IBackupScheduleUpdate & { lastRunAt?: number; nextRunAt?: number; lastStatus?: 'success' | 'failed' | null; lastError?: string | null }): void {
|
|
const setClauses: string[] = [];
|
|
const params: (string | number | null)[] = [];
|
|
|
|
if (updates.cronExpression !== undefined) {
|
|
setClauses.push('cron_expression = ?');
|
|
params.push(updates.cronExpression);
|
|
}
|
|
if (updates.retention !== undefined) {
|
|
setClauses.push('retention_hourly = ?');
|
|
params.push(updates.retention.hourly);
|
|
setClauses.push('retention_daily = ?');
|
|
params.push(updates.retention.daily);
|
|
setClauses.push('retention_weekly = ?');
|
|
params.push(updates.retention.weekly);
|
|
setClauses.push('retention_monthly = ?');
|
|
params.push(updates.retention.monthly);
|
|
}
|
|
if (updates.enabled !== undefined) {
|
|
setClauses.push('enabled = ?');
|
|
params.push(updates.enabled ? 1 : 0);
|
|
}
|
|
if (updates.lastRunAt !== undefined) {
|
|
setClauses.push('last_run_at = ?');
|
|
params.push(updates.lastRunAt);
|
|
}
|
|
if (updates.nextRunAt !== undefined) {
|
|
setClauses.push('next_run_at = ?');
|
|
params.push(updates.nextRunAt);
|
|
}
|
|
if (updates.lastStatus !== undefined) {
|
|
setClauses.push('last_status = ?');
|
|
params.push(updates.lastStatus);
|
|
}
|
|
if (updates.lastError !== undefined) {
|
|
setClauses.push('last_error = ?');
|
|
params.push(updates.lastError);
|
|
}
|
|
|
|
if (setClauses.length === 0) return;
|
|
|
|
setClauses.push('updated_at = ?');
|
|
params.push(Date.now());
|
|
params.push(id);
|
|
|
|
this.query(`UPDATE backup_schedules SET ${setClauses.join(', ')} WHERE id = ?`, params);
|
|
}
|
|
|
|
deleteSchedule(id: number): void {
|
|
this.query('DELETE FROM backup_schedules WHERE id = ?', [id]);
|
|
}
|
|
|
|
deleteSchedulesByService(serviceId: number): void {
|
|
this.query('DELETE FROM backup_schedules WHERE service_id = ?', [serviceId]);
|
|
}
|
|
|
|
private rowToSchedule(row: any): IBackupSchedule {
|
|
return {
|
|
id: Number(row.id),
|
|
scopeType: (String(row.scope_type) || 'service') as TBackupScheduleScope,
|
|
scopePattern: row.scope_pattern ? String(row.scope_pattern) : undefined,
|
|
serviceId: row.service_id ? Number(row.service_id) : undefined,
|
|
serviceName: row.service_name ? String(row.service_name) : undefined,
|
|
cronExpression: String(row.cron_expression),
|
|
retention: {
|
|
hourly: Number(row.retention_hourly ?? 0),
|
|
daily: Number(row.retention_daily ?? 7),
|
|
weekly: Number(row.retention_weekly ?? 4),
|
|
monthly: Number(row.retention_monthly ?? 12),
|
|
} as IRetentionPolicy,
|
|
enabled: Boolean(row.enabled),
|
|
lastRunAt: row.last_run_at ? Number(row.last_run_at) : null,
|
|
nextRunAt: row.next_run_at ? Number(row.next_run_at) : null,
|
|
lastStatus: row.last_status ? (String(row.last_status) as 'success' | 'failed') : null,
|
|
lastError: row.last_error ? String(row.last_error) : null,
|
|
createdAt: Number(row.created_at),
|
|
updatedAt: Number(row.updated_at),
|
|
};
|
|
}
|
|
}
|