From 68fbe42d6c6854c674210a4e9059f19bc36676b4 Mon Sep 17 00:00:00 2001 From: Khairul Hidayat Date: Fri, 10 May 2024 23:09:41 +0700 Subject: [PATCH] feat: init db, add rest api --- .DS_Store | Bin 0 -> 6148 bytes backend/.env.example | 0 backend/.gitignore | 1 + backend/Dockerfile.dev | 21 ++------ backend/Dockerfile.dev.bak | 28 ---------- backend/docker-compose.dev.yml | 2 + backend/drizzle.config.ts | 11 ++++ backend/package.json | 21 ++++++-- backend/src/consts.ts | 6 +++ backend/src/db/index.ts | 16 ++++++ backend/src/db/migrate.ts | 17 +++++++ backend/src/db/models.ts | 48 ++++++++++++++++++ backend/src/db/schema.ts | 9 ++++ backend/src/db/seed.ts | 12 +++++ .../src/lib/{database.ts => database-util.ts} | 4 +- backend/src/{ => lib}/dbms/base.ts | 2 +- backend/src/{ => lib}/dbms/postgres.ts | 7 ++- backend/src/main.ts | 5 ++ backend/src/routers/index.ts | 17 +++++++ backend/src/routers/server.router.ts | 38 ++++++++++++++ backend/src/schemas/server.schema.ts | 36 +++++++++++++ backend/{index.ts => src/test.ts} | 6 +-- backend/src/utility/consts.ts | 4 -- bun.lockb | Bin 0 -> 50671 bytes package.json | 4 ++ pnpm-workspace.yaml | 3 -- 26 files changed, 253 insertions(+), 65 deletions(-) create mode 100644 .DS_Store create mode 100644 backend/.env.example delete mode 100644 backend/Dockerfile.dev.bak create mode 100644 backend/drizzle.config.ts create mode 100644 backend/src/consts.ts create mode 100644 backend/src/db/index.ts create mode 100644 backend/src/db/migrate.ts create mode 100644 backend/src/db/models.ts create mode 100644 backend/src/db/schema.ts create mode 100644 backend/src/db/seed.ts rename backend/src/lib/{database.ts => database-util.ts} (89%) rename backend/src/{ => lib}/dbms/base.ts (80%) rename backend/src/{ => lib}/dbms/postgres.ts (90%) create mode 100644 backend/src/main.ts create mode 100644 backend/src/routers/index.ts create mode 100644 backend/src/routers/server.router.ts create mode 100644 backend/src/schemas/server.schema.ts rename backend/{index.ts => src/test.ts} (79%) delete mode 100644 backend/src/utility/consts.ts create mode 100755 bun.lockb delete mode 100644 pnpm-workspace.yaml diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..1495e773ad815c30058437203fe56af5aa7c4f45 GIT binary patch literal 6148 zcmeHK%Sr=55UkdKfn0L*IKSW@3?Y6&ejvt(2W168&wKK_@@c7l5DXhHf)}ZV?waZ8 znq}*-y$!%tANxCC31Cil#FvMu`MLYVt}0_hI`4SGfc@v@eRzGFR9{Xw_XayW;1Mt1 z`NMPGdYPuI6p#W^Knh3!Dey}Ly!X=Pi$p~!AO)nrNdf;pG`eFi924Wy!4M+=amI8Q z*D*^Fn`tS2f;0V!~< zz;$ky-v96EKg|E> /etc/nsswitch.conf && \ - apk del curl && \ - rm -rf /var/cache/apk/* glibc.apk glibc-bin.apk - -RUN unzip bun-linux-x64.zip && chmod +x ./bun-linux-x64/bun && mv ./bun-linux-x64/bun /usr/bin && rm -f bun-linux-x64.zip +COPY ["package.json", "bun.lockb", "./"] +RUN bun install # Add db clients -RUN apk --no-cache add postgresql16-client +RUN apk --no-cache --repository=http://dl-cdn.alpinelinux.org/alpine/edge/main add postgresql16-client ENTRYPOINT ["bun", "run", "dev"] diff --git a/backend/Dockerfile.dev.bak b/backend/Dockerfile.dev.bak deleted file mode 100644 index 67ad9ed..0000000 --- a/backend/Dockerfile.dev.bak +++ /dev/null @@ -1,28 +0,0 @@ -FROM alpine:3.19 -WORKDIR /app - -ENV GLIBC_VERSION 2.35-r1 - -RUN apk update && \ - apk add --no-cache --update unzip curl - # curl -Lo /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub && \ - # curl -Lo glibc.apk "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VERSION}/glibc-${GLIBC_VERSION}.apk" && \ - # curl -Lo glibc-bin.apk "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VERSION}/glibc-bin-${GLIBC_VERSION}.apk" && \ - # apk add --force-overwrite glibc-bin.apk glibc.apk && \ - # /usr/glibc-compat/sbin/ldconfig /lib /usr/glibc-compat/lib && \ - # echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf && \ - # apk del curl && \ - # rm -rf /var/cache/apk/* glibc.apk glibc-bin.apk - -ADD https://github.com/oven-sh/bun/releases/latest/download/bun-linux-x64.zip bun-linux-x64.zip -# RUN unzip bun-linux-x64.zip && chmod +x ./bun-linux-x64/bun && mv ./bun-linux-x64/bun /usr/local/bin && rm -rf bun-linux-x64.zip -RUN unzip bun-linux-x64.zip && ls bun-linux-x64 && ./bun-linux-x64/bun --version - -RUN chmod +x /usr/local/bin/bun -RUN /usr/local/bin/bun --version - -# CMD ["bun", "--version"] - -# RUN apk --no-cache add postgresql16-client - -# ENTRYPOINT ["bun", "run", "dev"] diff --git a/backend/docker-compose.dev.yml b/backend/docker-compose.dev.yml index 41efbfc..86e3ea1 100644 --- a/backend/docker-compose.dev.yml +++ b/backend/docker-compose.dev.yml @@ -10,3 +10,5 @@ services: - ./:/app:rw extra_hosts: - "host.docker.internal:host-gateway" + ports: + - "3000:3000" diff --git a/backend/drizzle.config.ts b/backend/drizzle.config.ts new file mode 100644 index 0000000..3ab544e --- /dev/null +++ b/backend/drizzle.config.ts @@ -0,0 +1,11 @@ +import { STORAGE_DIR } from "@/consts"; +import { defineConfig } from "drizzle-kit"; + +export default defineConfig({ + dialect: "sqlite", + dbCredentials: { + url: STORAGE_DIR + "/database.db", + }, + schema: "./src/db/models.ts", + out: "./src/db/migrations", +}); diff --git a/backend/package.json b/backend/package.json index ca2c39a..701a105 100644 --- a/backend/package.json +++ b/backend/package.json @@ -3,15 +3,26 @@ "module": "index.ts", "type": "module", "scripts": { - "dev": "bun --watch index.ts", - "dev:compose": "docker compose -f docker-compose.dev.yml up --build", + "dev": "bun --watch src/main.ts", + "dev:compose": "cp ../bun.lockb . && docker compose -f docker-compose.dev.yml up --build", "build": "bun build index.ts --outdir dist --target bun", - "start": "bun dist/index.js" + "start": "bun dist/main.js", + "generate": "drizzle-kit generate", + "migrate": "bun src/db/migrate.ts", + "reset": "rm -f storage/database.db && bun run migrate" }, "devDependencies": { - "@types/bun": "latest" + "@types/bun": "latest", + "drizzle-kit": "^0.21.0" }, "peerDependencies": { "typescript": "^5.0.0" + }, + "dependencies": { + "@hono/zod-validator": "^0.2.1", + "drizzle-orm": "^0.30.10", + "hono": "^4.3.4", + "nanoid": "^5.0.7", + "zod": "^3.23.8" } -} \ No newline at end of file +} diff --git a/backend/src/consts.ts b/backend/src/consts.ts new file mode 100644 index 0000000..e4b3952 --- /dev/null +++ b/backend/src/consts.ts @@ -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"); diff --git a/backend/src/db/index.ts b/backend/src/db/index.ts new file mode 100644 index 0000000..81568b1 --- /dev/null +++ b/backend/src/db/index.ts @@ -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; diff --git a/backend/src/db/migrate.ts b/backend/src/db/migrate.ts new file mode 100644 index 0000000..8a300b9 --- /dev/null +++ b/backend/src/db/migrate.ts @@ -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(); diff --git a/backend/src/db/models.ts b/backend/src/db/models.ts new file mode 100644 index 0000000..db246d7 --- /dev/null +++ b/backend/src/db/models.ts @@ -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`), +}); diff --git a/backend/src/db/schema.ts b/backend/src/db/schema.ts new file mode 100644 index 0000000..934c779 --- /dev/null +++ b/backend/src/db/schema.ts @@ -0,0 +1,9 @@ +import { databaseModel, serverModel, userModel } from "./models"; + +const schema = { + users: userModel, + servers: serverModel, + database: databaseModel, +}; + +export default schema; diff --git a/backend/src/db/seed.ts b/backend/src/db/seed.ts new file mode 100644 index 0000000..1090f34 --- /dev/null +++ b/backend/src/db/seed.ts @@ -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(); +}; diff --git a/backend/src/lib/database.ts b/backend/src/lib/database-util.ts similarity index 89% rename from backend/src/lib/database.ts rename to backend/src/lib/database-util.ts index b7d0f6f..82b721f 100644 --- a/backend/src/lib/database.ts +++ b/backend/src/lib/database-util.ts @@ -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 { diff --git a/backend/src/dbms/base.ts b/backend/src/lib/dbms/base.ts similarity index 80% rename from backend/src/dbms/base.ts rename to backend/src/lib/dbms/base.ts index 5a5a2b0..536786e 100644 --- a/backend/src/dbms/base.ts +++ b/backend/src/lib/dbms/base.ts @@ -1,4 +1,4 @@ -import type { DatabaseListItem } from "../types/database.types"; +import type { DatabaseListItem } from "../../types/database.types"; class BaseDbms { async getDatabases(): Promise { diff --git a/backend/src/dbms/postgres.ts b/backend/src/lib/dbms/postgres.ts similarity index 90% rename from backend/src/dbms/postgres.ts rename to backend/src/lib/dbms/postgres.ts index 899d8e0..d9d2f52 100644 --- a/backend/src/dbms/postgres.ts +++ b/backend/src/lib/dbms/postgres.ts @@ -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 { diff --git a/backend/src/main.ts b/backend/src/main.ts new file mode 100644 index 0000000..25c5d17 --- /dev/null +++ b/backend/src/main.ts @@ -0,0 +1,5 @@ +import routers from "./routers"; + +console.log("Starting app.."); + +export default routers; diff --git a/backend/src/routers/index.ts b/backend/src/routers/index.ts new file mode 100644 index 0000000..df5cecf --- /dev/null +++ b/backend/src/routers/index.ts @@ -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; diff --git a/backend/src/routers/server.router.ts b/backend/src/routers/server.router.ts new file mode 100644 index 0000000..e9de634 --- /dev/null +++ b/backend/src/routers/server.router.ts @@ -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; diff --git a/backend/src/schemas/server.schema.ts b/backend/src/schemas/server.schema.ts new file mode 100644 index 0000000..e79b077 --- /dev/null +++ b/backend/src/schemas/server.schema.ts @@ -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; diff --git a/backend/index.ts b/backend/src/test.ts similarity index 79% rename from backend/index.ts rename to backend/src/test.ts index 31841b5..2fff5dd 100644 --- a/backend/index.ts +++ b/backend/src/test.ts @@ -1,5 +1,5 @@ -import DatabaseUtil from "@/lib/database"; -import { DOCKER_HOST, STORAGE_DIR } from "@/utility/consts"; +import DatabaseUtil from "@/lib/database-util"; +import { DOCKER_HOST, BACKUP_DIR } from "@/consts"; import { mkdir } from "@/utility/utils"; import path from "path"; @@ -19,7 +19,7 @@ const main = async () => { const dbName = "test"; // Create backup - const outDir = path.join(STORAGE_DIR, db.config.host, dbName); + 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)); diff --git a/backend/src/utility/consts.ts b/backend/src/utility/consts.ts deleted file mode 100644 index 33d3a26..0000000 --- a/backend/src/utility/consts.ts +++ /dev/null @@ -1,4 +0,0 @@ -import path from "path"; - -export const DOCKER_HOST = "host.docker.internal"; -export const STORAGE_DIR = path.resolve(__dirname, "../../storage"); diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..270fe91465c44aeb16e056ad40ecc6ba7429cec1 GIT binary patch literal 50671 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