2024-05-12 06:00:04 +07:00
|
|
|
import db from "../db";
|
|
|
|
import { backupModel, serverModel } from "../db/models";
|
2024-05-11 02:09:18 +07:00
|
|
|
import type {
|
|
|
|
CreateBackupSchema,
|
|
|
|
GetAllBackupQuery,
|
|
|
|
RestoreBackupSchema,
|
2024-05-12 06:00:04 +07:00
|
|
|
} from "../schemas/backup.schema";
|
|
|
|
import { and, count, desc, eq, inArray } from "drizzle-orm";
|
2024-05-11 02:09:18 +07:00
|
|
|
import DatabaseService from "./database.service";
|
|
|
|
import { HTTPException } from "hono/http-exception";
|
|
|
|
|
|
|
|
export default class BackupService {
|
|
|
|
private databaseService = new DatabaseService();
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all backups
|
|
|
|
*/
|
|
|
|
async getAll(query: GetAllBackupQuery = {}) {
|
|
|
|
const { serverId, databaseId } = query;
|
|
|
|
const page = query.page || 1;
|
|
|
|
const limit = query.limit || 10;
|
|
|
|
|
2024-05-12 06:00:04 +07:00
|
|
|
const where = and(
|
|
|
|
serverId ? eq(backupModel.serverId, serverId) : undefined,
|
|
|
|
databaseId ? eq(backupModel.databaseId, databaseId) : undefined
|
|
|
|
);
|
|
|
|
|
|
|
|
const [totalRows] = await db
|
|
|
|
.select({ count: count() })
|
|
|
|
.from(backupModel)
|
|
|
|
.where(where)
|
|
|
|
.limit(1);
|
|
|
|
|
2024-05-11 02:09:18 +07:00
|
|
|
const backups = await db.query.backup.findMany({
|
2024-05-12 06:00:04 +07:00
|
|
|
where,
|
|
|
|
with: {
|
|
|
|
database: { columns: { name: true } },
|
|
|
|
},
|
2024-05-11 02:09:18 +07:00
|
|
|
orderBy: desc(serverModel.createdAt),
|
|
|
|
limit,
|
|
|
|
offset: (page - 1) * limit,
|
|
|
|
});
|
|
|
|
|
2024-05-12 06:00:04 +07:00
|
|
|
return { count: totalRows.count, rows: backups };
|
2024-05-11 02:09:18 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
async getOrFail(id: string) {
|
|
|
|
const backup = await db.query.backup.findFirst({
|
|
|
|
where: eq(backupModel.id, id),
|
|
|
|
});
|
|
|
|
if (!backup) {
|
|
|
|
throw new HTTPException(404, { message: "Backup not found." });
|
|
|
|
}
|
|
|
|
return backup;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Queue new backup
|
|
|
|
*/
|
|
|
|
async create(data: CreateBackupSchema) {
|
|
|
|
const database = await this.databaseService.getOrFail(data.databaseId);
|
|
|
|
await this.checkPendingBackup(database.id);
|
|
|
|
|
|
|
|
const [result] = await db
|
|
|
|
.insert(backupModel)
|
|
|
|
.values({
|
|
|
|
type: "backup",
|
|
|
|
serverId: database.serverId,
|
|
|
|
databaseId: database.id,
|
|
|
|
})
|
|
|
|
.returning();
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
async restore(data: RestoreBackupSchema) {
|
|
|
|
const backup = await this.getOrFail(data.backupId);
|
|
|
|
await this.checkPendingBackup(backup.databaseId);
|
|
|
|
|
|
|
|
if (!backup.key) {
|
|
|
|
throw new HTTPException(400, {
|
|
|
|
message: "Cannot restore backup without file key.",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const [result] = await db
|
|
|
|
.insert(backupModel)
|
|
|
|
.values({
|
|
|
|
type: "restore",
|
|
|
|
serverId: backup.serverId,
|
|
|
|
databaseId: backup.databaseId,
|
|
|
|
key: backup.key,
|
|
|
|
hash: backup.hash,
|
|
|
|
size: backup.size,
|
|
|
|
})
|
|
|
|
.returning();
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
async checkPendingBackup(databaseId: string) {
|
|
|
|
const hasOngoingBackup = await db.query.backup.findFirst({
|
|
|
|
where: and(
|
|
|
|
eq(backupModel.databaseId, databaseId),
|
|
|
|
inArray(backupModel.status, ["pending", "running"])
|
|
|
|
),
|
|
|
|
});
|
|
|
|
if (hasOngoingBackup) {
|
|
|
|
throw new HTTPException(400, {
|
|
|
|
message: "There is already an ongoing backup for this database",
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|