Initial push

This commit is contained in:
2026-03-08 15:49:34 +01:00
parent 8da12bb7d1
commit 47127f276d
101 changed files with 13844 additions and 8 deletions

102
lib/db/index.ts Normal file
View File

@@ -0,0 +1,102 @@
import { Database } from "bun:sqlite";
import { drizzle } from "drizzle-orm/bun-sqlite";
import { mkdirSync } from "node:fs";
import { dirname } from "node:path";
import * as schema from "./schema";
const DB_PATH = "./data/cubeadmin.db";
// Ensure the data directory exists before opening the database
mkdirSync(dirname(DB_PATH), { recursive: true });
const sqlite = new Database(DB_PATH, { create: true });
// Enable WAL mode for better concurrent read performance
sqlite.exec("PRAGMA journal_mode = WAL;");
sqlite.exec("PRAGMA foreign_keys = ON;");
export const db = drizzle(sqlite, { schema });
export type DB = typeof db;
// Re-export all schema tables for convenient imports from a single location
export {
users,
sessions,
accounts,
verifications,
invitations,
mcPlayers,
playerBans,
playerChatHistory,
playerSpawnPoints,
plugins,
backups,
scheduledTasks,
auditLogs,
serverSettings,
} from "./schema";
// Re-export Zod schemas
export {
insertUserSchema,
selectUserSchema,
insertSessionSchema,
selectSessionSchema,
insertAccountSchema,
selectAccountSchema,
insertVerificationSchema,
selectVerificationSchema,
insertInvitationSchema,
selectInvitationSchema,
insertMcPlayerSchema,
selectMcPlayerSchema,
insertPlayerBanSchema,
selectPlayerBanSchema,
insertPlayerChatHistorySchema,
selectPlayerChatHistorySchema,
insertPlayerSpawnPointSchema,
selectPlayerSpawnPointSchema,
insertPluginSchema,
selectPluginSchema,
insertBackupSchema,
selectBackupSchema,
insertScheduledTaskSchema,
selectScheduledTaskSchema,
insertAuditLogSchema,
selectAuditLogSchema,
insertServerSettingsSchema,
selectServerSettingsSchema,
} from "./schema";
// Re-export inferred types
export type {
User,
NewUser,
Session,
NewSession,
Account,
NewAccount,
Verification,
NewVerification,
Invitation,
NewInvitation,
McPlayer,
NewMcPlayer,
PlayerBan,
NewPlayerBan,
PlayerChatHistory,
NewPlayerChatHistory,
PlayerSpawnPoint,
NewPlayerSpawnPoint,
Plugin,
NewPlugin,
Backup,
NewBackup,
ScheduledTask,
NewScheduledTask,
AuditLog,
NewAuditLog,
ServerSettings,
NewServerSettings,
} from "./schema";

31
lib/db/migrate.ts Normal file
View File

@@ -0,0 +1,31 @@
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
import { existsSync } from "node:fs";
import { resolve } from "node:path";
import { db } from "./index";
const MIGRATIONS_FOLDER = resolve(process.cwd(), "drizzle");
/**
* Run all pending Drizzle migrations at startup.
*
* If the migrations folder does not exist yet (e.g. fresh clone before the
* first `bun run db:generate` has been executed) the function exits silently
* so that the application can still start in development without crashing.
*/
export function runMigrations(): void {
if (!existsSync(MIGRATIONS_FOLDER)) {
console.warn(
`[migrate] Migrations folder not found at "${MIGRATIONS_FOLDER}". ` +
"Skipping migrations — run `bun run db:generate` to create migration files.",
);
return;
}
try {
migrate(db, { migrationsFolder: MIGRATIONS_FOLDER });
console.log("[migrate] Database migrations applied successfully.");
} catch (err) {
console.error("[migrate] Failed to apply database migrations:", err);
throw err;
}
}

359
lib/db/schema.ts Normal file
View File

@@ -0,0 +1,359 @@
import {
sqliteTable,
text,
integer,
real,
uniqueIndex,
} from "drizzle-orm/sqlite-core";
import { createInsertSchema, createSelectSchema } from "drizzle-zod";
// ---------------------------------------------------------------------------
// Better Auth core tables
// ---------------------------------------------------------------------------
export const users = sqliteTable("users", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: integer("email_verified", { mode: "boolean" })
.notNull()
.default(false),
image: text("image"),
role: text("role", {
enum: ["superadmin", "admin", "moderator"],
})
.notNull()
.default("moderator"),
createdAt: integer("created_at").notNull(),
updatedAt: integer("updated_at").notNull(),
});
export const sessions = sqliteTable("sessions", {
id: text("id").primaryKey(),
userId: text("user_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
token: text("token").notNull().unique(),
expiresAt: integer("expires_at").notNull(),
ipAddress: text("ip_address"),
userAgent: text("user_agent"),
createdAt: integer("created_at").notNull(),
updatedAt: integer("updated_at").notNull(),
});
export const accounts = sqliteTable("accounts", {
id: text("id").primaryKey(),
userId: text("user_id")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
accountId: text("account_id").notNull(),
providerId: text("provider_id").notNull(),
accessToken: text("access_token"),
refreshToken: text("refresh_token"),
expiresAt: integer("expires_at"),
createdAt: integer("created_at").notNull(),
updatedAt: integer("updated_at").notNull(),
});
export const verifications = sqliteTable("verifications", {
id: text("id").primaryKey(),
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: integer("expires_at").notNull(),
createdAt: integer("created_at").notNull(),
updatedAt: integer("updated_at").notNull(),
});
// ---------------------------------------------------------------------------
// Invitation system
// ---------------------------------------------------------------------------
export const invitations = sqliteTable("invitations", {
id: text("id").primaryKey(),
email: text("email").notNull(),
role: text("role", {
enum: ["superadmin", "admin", "moderator"],
})
.notNull()
.default("moderator"),
invitedBy: text("invited_by")
.notNull()
.references(() => users.id, { onDelete: "cascade" }),
token: text("token").notNull().unique(),
expiresAt: integer("expires_at").notNull(),
acceptedAt: integer("accepted_at"),
createdAt: integer("created_at").notNull(),
});
// ---------------------------------------------------------------------------
// Minecraft player management
// ---------------------------------------------------------------------------
export const mcPlayers = sqliteTable(
"mc_players",
{
id: text("id").primaryKey(), // uuid
uuid: text("uuid").notNull(), // Minecraft UUID
username: text("username").notNull(),
firstSeen: integer("first_seen"),
lastSeen: integer("last_seen"),
isOnline: integer("is_online", { mode: "boolean" }).notNull().default(false),
playTime: integer("play_time").notNull().default(0), // minutes
role: text("role"),
isBanned: integer("is_banned", { mode: "boolean" })
.notNull()
.default(false),
notes: text("notes"),
},
(table) => [uniqueIndex("mc_players_uuid_idx").on(table.uuid)],
);
export const playerBans = sqliteTable("player_bans", {
id: text("id").primaryKey(),
playerId: text("player_id")
.notNull()
.references(() => mcPlayers.id, { onDelete: "cascade" }),
reason: text("reason"),
bannedBy: text("banned_by").references(() => users.id, {
onDelete: "set null",
}),
bannedAt: integer("banned_at").notNull(),
expiresAt: integer("expires_at"),
isActive: integer("is_active", { mode: "boolean" }).notNull().default(true),
unbannedBy: text("unbanned_by").references(() => users.id, {
onDelete: "set null",
}),
unbannedAt: integer("unbanned_at"),
});
export const playerChatHistory = sqliteTable("player_chat_history", {
id: text("id").primaryKey(),
playerId: text("player_id")
.notNull()
.references(() => mcPlayers.id, { onDelete: "cascade" }),
message: text("message").notNull(),
channel: text("channel"),
timestamp: integer("timestamp").notNull(),
serverId: text("server_id"),
});
export const playerSpawnPoints = sqliteTable("player_spawn_points", {
id: text("id").primaryKey(),
playerId: text("player_id")
.notNull()
.references(() => mcPlayers.id, { onDelete: "cascade" }),
name: text("name").notNull(),
world: text("world").notNull(),
x: real("x").notNull(),
y: real("y").notNull(),
z: real("z").notNull(),
createdAt: integer("created_at").notNull(),
});
// ---------------------------------------------------------------------------
// Plugin management
// ---------------------------------------------------------------------------
export const plugins = sqliteTable("plugins", {
id: text("id").primaryKey(),
name: text("name").notNull(),
version: text("version"),
description: text("description"),
isEnabled: integer("is_enabled", { mode: "boolean" })
.notNull()
.default(true),
jarFile: text("jar_file"),
config: text("config"), // JSON blob
installedAt: integer("installed_at").notNull(),
updatedAt: integer("updated_at").notNull(),
});
// ---------------------------------------------------------------------------
// Backup management
// ---------------------------------------------------------------------------
export const backups = sqliteTable("backups", {
id: text("id").primaryKey(),
name: text("name").notNull(),
type: text("type", {
enum: ["worlds", "plugins", "config", "full"],
}).notNull(),
size: integer("size"), // bytes
path: text("path"),
createdAt: integer("created_at").notNull(),
status: text("status", {
enum: ["pending", "running", "completed", "failed"],
})
.notNull()
.default("pending"),
triggeredBy: text("triggered_by").references(() => users.id, {
onDelete: "set null",
}),
});
// ---------------------------------------------------------------------------
// Scheduled tasks
// ---------------------------------------------------------------------------
export const scheduledTasks = sqliteTable("scheduled_tasks", {
id: text("id").primaryKey(),
name: text("name").notNull(),
description: text("description"),
cronExpression: text("cron_expression").notNull(),
command: text("command").notNull(), // MC command to run
isEnabled: integer("is_enabled", { mode: "boolean" })
.notNull()
.default(true),
lastRun: integer("last_run"),
nextRun: integer("next_run"),
createdAt: integer("created_at").notNull(),
updatedAt: integer("updated_at").notNull(),
});
// ---------------------------------------------------------------------------
// Audit logs
// ---------------------------------------------------------------------------
export const auditLogs = sqliteTable("audit_logs", {
id: text("id").primaryKey(),
userId: text("user_id").references(() => users.id, { onDelete: "set null" }),
action: text("action").notNull(),
target: text("target"),
targetId: text("target_id"),
details: text("details"), // JSON blob
ipAddress: text("ip_address"),
createdAt: integer("created_at").notNull(),
});
// ---------------------------------------------------------------------------
// Server settings (singleton row, id = 1)
// ---------------------------------------------------------------------------
export const serverSettings = sqliteTable("server_settings", {
id: integer("id").primaryKey().default(1),
minecraftPath: text("minecraft_path"),
serverJar: text("server_jar"),
serverVersion: text("server_version"),
serverType: text("server_type", {
enum: ["vanilla", "paper", "spigot", "bukkit", "fabric", "forge", "bedrock"],
}),
maxRam: integer("max_ram").default(4096),
minRam: integer("min_ram").default(1024),
rconEnabled: integer("rcon_enabled", { mode: "boolean" })
.notNull()
.default(false),
rconPort: integer("rcon_port").default(25575),
rconPassword: text("rcon_password"), // stored encrypted
javaArgs: text("java_args"),
autoStart: integer("auto_start", { mode: "boolean" })
.notNull()
.default(false),
restartOnCrash: integer("restart_on_crash", { mode: "boolean" })
.notNull()
.default(false),
backupEnabled: integer("backup_enabled", { mode: "boolean" })
.notNull()
.default(false),
backupSchedule: text("backup_schedule"),
bluemapEnabled: integer("bluemap_enabled", { mode: "boolean" })
.notNull()
.default(false),
bluemapUrl: text("bluemap_url"),
updatedAt: integer("updated_at").notNull(),
});
// ---------------------------------------------------------------------------
// Zod schemas (insert + select) for each table
// ---------------------------------------------------------------------------
export const insertUserSchema = createInsertSchema(users);
export const selectUserSchema = createSelectSchema(users);
export const insertSessionSchema = createInsertSchema(sessions);
export const selectSessionSchema = createSelectSchema(sessions);
export const insertAccountSchema = createInsertSchema(accounts);
export const selectAccountSchema = createSelectSchema(accounts);
export const insertVerificationSchema = createInsertSchema(verifications);
export const selectVerificationSchema = createSelectSchema(verifications);
export const insertInvitationSchema = createInsertSchema(invitations);
export const selectInvitationSchema = createSelectSchema(invitations);
export const insertMcPlayerSchema = createInsertSchema(mcPlayers);
export const selectMcPlayerSchema = createSelectSchema(mcPlayers);
export const insertPlayerBanSchema = createInsertSchema(playerBans);
export const selectPlayerBanSchema = createSelectSchema(playerBans);
export const insertPlayerChatHistorySchema =
createInsertSchema(playerChatHistory);
export const selectPlayerChatHistorySchema =
createSelectSchema(playerChatHistory);
export const insertPlayerSpawnPointSchema =
createInsertSchema(playerSpawnPoints);
export const selectPlayerSpawnPointSchema =
createSelectSchema(playerSpawnPoints);
export const insertPluginSchema = createInsertSchema(plugins);
export const selectPluginSchema = createSelectSchema(plugins);
export const insertBackupSchema = createInsertSchema(backups);
export const selectBackupSchema = createSelectSchema(backups);
export const insertScheduledTaskSchema = createInsertSchema(scheduledTasks);
export const selectScheduledTaskSchema = createSelectSchema(scheduledTasks);
export const insertAuditLogSchema = createInsertSchema(auditLogs);
export const selectAuditLogSchema = createSelectSchema(auditLogs);
export const insertServerSettingsSchema = createInsertSchema(serverSettings);
export const selectServerSettingsSchema = createSelectSchema(serverSettings);
// ---------------------------------------------------------------------------
// Inferred TypeScript types
// ---------------------------------------------------------------------------
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
export type Session = typeof sessions.$inferSelect;
export type NewSession = typeof sessions.$inferInsert;
export type Account = typeof accounts.$inferSelect;
export type NewAccount = typeof accounts.$inferInsert;
export type Verification = typeof verifications.$inferSelect;
export type NewVerification = typeof verifications.$inferInsert;
export type Invitation = typeof invitations.$inferSelect;
export type NewInvitation = typeof invitations.$inferInsert;
export type McPlayer = typeof mcPlayers.$inferSelect;
export type NewMcPlayer = typeof mcPlayers.$inferInsert;
export type PlayerBan = typeof playerBans.$inferSelect;
export type NewPlayerBan = typeof playerBans.$inferInsert;
export type PlayerChatHistory = typeof playerChatHistory.$inferSelect;
export type NewPlayerChatHistory = typeof playerChatHistory.$inferInsert;
export type PlayerSpawnPoint = typeof playerSpawnPoints.$inferSelect;
export type NewPlayerSpawnPoint = typeof playerSpawnPoints.$inferInsert;
export type Plugin = typeof plugins.$inferSelect;
export type NewPlugin = typeof plugins.$inferInsert;
export type Backup = typeof backups.$inferSelect;
export type NewBackup = typeof backups.$inferInsert;
export type ScheduledTask = typeof scheduledTasks.$inferSelect;
export type NewScheduledTask = typeof scheduledTasks.$inferInsert;
export type AuditLog = typeof auditLogs.$inferSelect;
export type NewAuditLog = typeof auditLogs.$inferInsert;
export type ServerSettings = typeof serverSettings.$inferSelect;
export type NewServerSettings = typeof serverSettings.$inferInsert;