feat: add SQLite database with Drizzle ORM schema and migrations

This commit is contained in:
Siddharth Kumar Sah
2026-03-22 02:51:57 +08:00
parent 1e7fb11da0
commit a24c6dd014
11 changed files with 1433 additions and 2 deletions
+20
View File
@@ -0,0 +1,20 @@
import Database from "better-sqlite3";
import { drizzle } from "drizzle-orm/better-sqlite3";
import { env } from "../config.js";
import * as schema from "./schema.js";
import { mkdirSync } from "node:fs";
import { dirname } from "node:path";
// Ensure data directory exists
mkdirSync(dirname(env.DB_PATH), { recursive: true });
const sqlite = new Database(env.DB_PATH);
// Critical SQLite pragmas for reliability
sqlite.pragma("journal_mode = WAL");
sqlite.pragma("busy_timeout = 5000");
sqlite.pragma("synchronous = NORMAL");
sqlite.pragma("foreign_keys = ON");
export const db = drizzle(sqlite, { schema });
export { schema };
+14
View File
@@ -0,0 +1,14 @@
import { migrate } from "drizzle-orm/better-sqlite3/migrator";
import { db } from "./index.js";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Resolve migrations folder relative to this file, not the working directory
const migrationsFolder = join(__dirname, "../../drizzle");
export function runMigrations() {
migrate(db, { migrationsFolder });
}
+46
View File
@@ -0,0 +1,46 @@
import { sqliteTable, text, integer, real } from "drizzle-orm/sqlite-core";
export const users = sqliteTable("users", {
id: text("id").primaryKey(),
username: text("username").notNull().unique(),
passwordHash: text("password_hash").notNull(),
role: text("role", { enum: ["admin", "user"] }).notNull().default("user"),
mustChangePassword: integer("must_change_password", { mode: "boolean" }).notNull().default(true),
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => new Date()),
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => new Date()),
});
export const sessions = sqliteTable("sessions", {
id: text("id").primaryKey(),
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
expiresAt: integer("expires_at", { mode: "timestamp" }).notNull(),
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => new Date()),
});
export const settings = sqliteTable("settings", {
key: text("key").primaryKey(),
value: text("value").notNull(),
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull().$defaultFn(() => new Date()),
});
export const jobs = sqliteTable("jobs", {
id: text("id").primaryKey(),
type: text("type").notNull(),
status: text("status", { enum: ["queued", "processing", "completed", "failed"] }).notNull().default("queued"),
progress: real("progress").notNull().default(0),
inputFiles: text("input_files").notNull(),
outputPath: text("output_path"),
settings: text("settings"),
error: text("error"),
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => new Date()),
completedAt: integer("completed_at", { mode: "timestamp" }),
});
export const apiKeys = sqliteTable("api_keys", {
id: text("id").primaryKey(),
userId: text("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
keyHash: text("key_hash").notNull(),
name: text("name").notNull().default("Default API Key"),
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => new Date()),
lastUsedAt: integer("last_used_at", { mode: "timestamp" }),
});
+5
View File
@@ -3,6 +3,11 @@ import cors from "@fastify/cors";
import rateLimit from "@fastify/rate-limit";
import { env } from "./config.js";
import { APP_VERSION } from "@stirling-image/shared";
import { runMigrations } from "./db/migrate.js";
// Run before anything else
runMigrations();
console.log("Database initialized");
const app = Fastify({
logger: true,