mirror of
https://github.com/khairul169/db-backup-tool.git
synced 2026-09-16 17:33:26 +07:00
feat: init db, add rest api
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
import path from "path";
|
||||
|
||||
export const DOCKER_HOST = "host.docker.internal";
|
||||
export const STORAGE_DIR = path.resolve(__dirname, "../storage");
|
||||
export const BACKUP_DIR = STORAGE_DIR + "/backups";
|
||||
export const DATABASE_PATH = path.join(STORAGE_DIR, "database.db");
|
||||
@@ -0,0 +1,16 @@
|
||||
import path from "path";
|
||||
import { drizzle } from "drizzle-orm/bun-sqlite";
|
||||
import { Database } from "bun:sqlite";
|
||||
import { DATABASE_PATH } from "@/consts";
|
||||
import { mkdir } from "@/utility/utils";
|
||||
import schema from "./schema";
|
||||
|
||||
// Create database directory if not exists
|
||||
mkdir(path.dirname(DATABASE_PATH));
|
||||
|
||||
// Initialize database
|
||||
const sqlite = new Database(DATABASE_PATH);
|
||||
const db = drizzle(sqlite, { schema });
|
||||
|
||||
export { sqlite };
|
||||
export default db;
|
||||
@@ -0,0 +1,17 @@
|
||||
import fs from "fs";
|
||||
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
||||
import { DATABASE_PATH } from "@/consts";
|
||||
import db, { sqlite } from ".";
|
||||
import { seed } from "./seed";
|
||||
|
||||
const initializeData = fs.existsSync(DATABASE_PATH);
|
||||
|
||||
await migrate(db, {
|
||||
migrationsFolder: __dirname + "/migrations",
|
||||
});
|
||||
|
||||
if (initializeData) {
|
||||
await seed();
|
||||
}
|
||||
|
||||
await sqlite.close();
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { DatabaseConfig } from "@/types/database.types";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
export const userModel = sqliteTable("users", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
username: text("username").notNull().unique(),
|
||||
password: text("password").notNull(),
|
||||
isActive: integer("is_active", { mode: "boolean" }).notNull().default(true),
|
||||
createdAt: text("created_at")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
});
|
||||
|
||||
export const serverModel = sqliteTable("servers", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
name: text("name").notNull(),
|
||||
type: text("type", { enum: ["postgres"] }).notNull(),
|
||||
connection: text("connection"),
|
||||
ssh: text("ssh"),
|
||||
isActive: integer("is_active", { mode: "boolean" }).notNull().default(true),
|
||||
createdAt: text("created_at")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
});
|
||||
|
||||
export const databaseModel = sqliteTable("databases", {
|
||||
id: text("id")
|
||||
.primaryKey()
|
||||
.$defaultFn(() => nanoid()),
|
||||
serverId: text("server_id")
|
||||
.references(() => serverModel.id, {
|
||||
onUpdate: "cascade",
|
||||
onDelete: "cascade",
|
||||
})
|
||||
.notNull(),
|
||||
name: text("name").notNull(),
|
||||
isActive: integer("is_active", { mode: "boolean" }).notNull().default(true),
|
||||
lastBackupAt: text("last_backup_at"),
|
||||
createdAt: text("created_at")
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { databaseModel, serverModel, userModel } from "./models";
|
||||
|
||||
const schema = {
|
||||
users: userModel,
|
||||
servers: serverModel,
|
||||
database: databaseModel,
|
||||
};
|
||||
|
||||
export default schema;
|
||||
@@ -0,0 +1,12 @@
|
||||
import db from ".";
|
||||
import { userModel } from "./models";
|
||||
|
||||
export const seed = async () => {
|
||||
await db
|
||||
.insert(userModel)
|
||||
.values({
|
||||
username: "admin",
|
||||
password: await Bun.password.hash("admin", { algorithm: "bcrypt" }),
|
||||
})
|
||||
.execute();
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import BaseDbms from "../dbms/base";
|
||||
import PostgresDbms from "../dbms/postgres";
|
||||
import BaseDbms from "./dbms/base";
|
||||
import PostgresDbms from "./dbms/postgres";
|
||||
import type { DatabaseConfig, DatabaseListItem } from "../types/database.types";
|
||||
|
||||
class DatabaseUtil {
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { DatabaseListItem } from "../types/database.types";
|
||||
import type { DatabaseListItem } from "../../types/database.types";
|
||||
|
||||
class BaseDbms {
|
||||
async getDatabases(): Promise<DatabaseListItem[]> {
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { DatabaseListItem, PostgresConfig } from "../types/database.types";
|
||||
import { exec } from "../utility/process";
|
||||
import type {
|
||||
DatabaseListItem,
|
||||
PostgresConfig,
|
||||
} from "../../types/database.types";
|
||||
import { exec } from "../../utility/process";
|
||||
import BaseDbms from "./base";
|
||||
|
||||
class PostgresDbms extends BaseDbms {
|
||||
@@ -0,0 +1,5 @@
|
||||
import routers from "./routers";
|
||||
|
||||
console.log("Starting app..");
|
||||
|
||||
export default routers;
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Hono, type Context } from "hono";
|
||||
import server from "./server.router";
|
||||
|
||||
const handleError = (err: Error, c: Context) => {
|
||||
return c.json({
|
||||
success: false,
|
||||
error: err,
|
||||
message: err.message,
|
||||
});
|
||||
};
|
||||
|
||||
const routers = new Hono()
|
||||
.onError(handleError)
|
||||
.get("/health-check", (c) => c.text("OK"))
|
||||
.route("/servers", server);
|
||||
|
||||
export default routers;
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Hono } from "hono";
|
||||
import { zValidator } from "@hono/zod-validator";
|
||||
import { createServerSchema } from "@/schemas/server.schema";
|
||||
import db from "@/db";
|
||||
import { asc, eq } from "drizzle-orm";
|
||||
import { HTTPException } from "hono/http-exception";
|
||||
import { serverModel } from "@/db/models";
|
||||
|
||||
const router = new Hono()
|
||||
|
||||
.get("/", async (c) => {
|
||||
const servers = await db.query.servers.findMany({
|
||||
columns: { connection: false, ssh: false },
|
||||
orderBy: asc(serverModel.createdAt),
|
||||
});
|
||||
return c.json(servers);
|
||||
})
|
||||
|
||||
.post("/", zValidator("json", createServerSchema), async (c) => {
|
||||
const data = c.req.valid("json");
|
||||
const isExist = await db.query.servers.findFirst({
|
||||
where: eq(serverModel.name, data.name),
|
||||
});
|
||||
if (isExist) {
|
||||
throw new HTTPException(400, { message: "Server name already exists" });
|
||||
}
|
||||
|
||||
const dataValue = {
|
||||
...data,
|
||||
connection: data.connection ? JSON.stringify(data.connection) : null,
|
||||
ssh: data.ssh ? JSON.stringify(data.ssh) : null,
|
||||
};
|
||||
const [result] = await db.insert(serverModel).values(dataValue).returning();
|
||||
|
||||
return c.json(result);
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,36 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const serverTypeEnum = ["postgres"] as const;
|
||||
|
||||
export const serverSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
ssh: z
|
||||
.object({
|
||||
host: z.string(),
|
||||
port: z.number().optional(),
|
||||
user: z.string(),
|
||||
pass: z.string().optional(),
|
||||
privateKey: z.string().optional(),
|
||||
})
|
||||
.optional()
|
||||
.nullable(),
|
||||
isActive: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const postgresSchema = serverSchema.merge(
|
||||
z.object({
|
||||
type: z.literal("postgres"),
|
||||
connection: z.object({
|
||||
host: z.string(),
|
||||
port: z.number().optional(),
|
||||
user: z.string(),
|
||||
pass: z.string().optional(),
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
export const createServerSchema = z.discriminatedUnion("type", [
|
||||
postgresSchema,
|
||||
]);
|
||||
|
||||
export type CreateServerSchema = z.infer<typeof createServerSchema>;
|
||||
@@ -0,0 +1,35 @@
|
||||
import DatabaseUtil from "@/lib/database-util";
|
||||
import { DOCKER_HOST, BACKUP_DIR } from "@/consts";
|
||||
import { mkdir } from "@/utility/utils";
|
||||
import path from "path";
|
||||
|
||||
const main = async () => {
|
||||
try {
|
||||
const db = new DatabaseUtil({
|
||||
type: "postgres",
|
||||
host: DOCKER_HOST,
|
||||
user: "postgres",
|
||||
pass: "postgres",
|
||||
port: 5432,
|
||||
});
|
||||
|
||||
const databases = await db.getDatabases();
|
||||
console.log(databases);
|
||||
|
||||
const dbName = "test";
|
||||
|
||||
// Create backup
|
||||
const outDir = path.join(BACKUP_DIR, db.config.host, dbName);
|
||||
mkdir(outDir);
|
||||
const outFile = path.join(outDir, `/${Date.now()}.tar`);
|
||||
console.log(await db.dump(dbName, outFile));
|
||||
console.log(outFile);
|
||||
|
||||
// Restore backup
|
||||
console.log(await db.restore(outFile));
|
||||
} catch (err) {
|
||||
console.log((err as any).message);
|
||||
}
|
||||
};
|
||||
|
||||
main();
|
||||
@@ -1,4 +0,0 @@
|
||||
import path from "path";
|
||||
|
||||
export const DOCKER_HOST = "host.docker.internal";
|
||||
export const STORAGE_DIR = path.resolve(__dirname, "../../storage");
|
||||
Reference in New Issue
Block a user