This commit is contained in:
hibna
2026-02-21 13:02:41 +03:00
commit 2215003a4d
59 changed files with 6100 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/schema/index.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
+25
View File
@@ -0,0 +1,25 @@
{
"name": "@source/database",
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"build": "tsc",
"lint": "eslint src/",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:seed": "tsx src/seed.ts",
"db:studio": "drizzle-kit studio"
},
"dependencies": {
"drizzle-orm": "^0.38.0",
"postgres": "^3.4.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"drizzle-kit": "^0.30.0",
"tsx": "^4.19.0"
}
}
+10
View File
@@ -0,0 +1,10 @@
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
import * as schema from './schema';
export function createDb(connectionString: string) {
const client = postgres(connectionString);
return drizzle(client, { schema });
}
export type Database = ReturnType<typeof createDb>;
+2
View File
@@ -0,0 +1,2 @@
export * from './schema';
export * from './client';
@@ -0,0 +1,19 @@
import { pgTable, uuid, varchar, jsonb, timestamp } from 'drizzle-orm/pg-core';
import { organizations } from './organizations';
import { users } from './users';
import { servers } from './servers';
export const auditLogs = pgTable('audit_logs', {
id: uuid('id').defaultRandom().primaryKey(),
organizationId: uuid('organization_id')
.notNull()
.references(() => organizations.id, { onDelete: 'cascade' }),
userId: uuid('user_id')
.notNull()
.references(() => users.id),
serverId: uuid('server_id').references(() => servers.id, { onDelete: 'set null' }),
action: varchar('action', { length: 255 }).notNull(),
metadata: jsonb('metadata').default({}).notNull(),
ipAddress: varchar('ip_address', { length: 45 }),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
});
+16
View File
@@ -0,0 +1,16 @@
import { pgTable, uuid, varchar, bigint, text, boolean, timestamp } from 'drizzle-orm/pg-core';
import { servers } from './servers';
export const backups = pgTable('backups', {
id: uuid('id').defaultRandom().primaryKey(),
serverId: uuid('server_id')
.notNull()
.references(() => servers.id, { onDelete: 'cascade' }),
name: varchar('name', { length: 255 }).notNull(),
sizeBytes: bigint('size_bytes', { mode: 'number' }),
cdnPath: text('cdn_path'),
checksum: text('checksum'),
isLocked: boolean('is_locked').default(false).notNull(),
completedAt: timestamp('completed_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
});
+15
View File
@@ -0,0 +1,15 @@
import { pgTable, uuid, varchar, integer, text, jsonb, timestamp } from 'drizzle-orm/pg-core';
export const games = pgTable('games', {
id: uuid('id').defaultRandom().primaryKey(),
slug: varchar('slug', { length: 100 }).notNull().unique(),
name: varchar('name', { length: 255 }).notNull(),
dockerImage: text('docker_image').notNull(),
defaultPort: integer('default_port').notNull(),
configFiles: jsonb('config_files').default([]).notNull(),
startupCommand: text('startup_command').notNull(),
stopCommand: text('stop_command'),
environmentVars: jsonb('environment_vars').default([]).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
});
+9
View File
@@ -0,0 +1,9 @@
export * from './users';
export * from './organizations';
export * from './nodes';
export * from './games';
export * from './servers';
export * from './backups';
export * from './plugins';
export * from './schedules';
export * from './audit-logs';
+45
View File
@@ -0,0 +1,45 @@
import {
pgTable,
uuid,
varchar,
integer,
bigint,
text,
boolean,
timestamp,
} from 'drizzle-orm/pg-core';
import { organizations } from './organizations';
import { servers } from './servers';
export const nodes = pgTable('nodes', {
id: uuid('id').defaultRandom().primaryKey(),
organizationId: uuid('organization_id')
.notNull()
.references(() => organizations.id, { onDelete: 'cascade' }),
name: varchar('name', { length: 255 }).notNull(),
fqdn: varchar('fqdn', { length: 255 }).notNull(),
daemonPort: integer('daemon_port').default(8443).notNull(),
grpcPort: integer('grpc_port').default(50051).notNull(),
location: varchar('location', { length: 255 }),
memoryTotal: bigint('memory_total', { mode: 'number' }).notNull(),
diskTotal: bigint('disk_total', { mode: 'number' }).notNull(),
memoryOveralloc: integer('memory_overalloc').default(0).notNull(),
diskOveralloc: integer('disk_overalloc').default(0).notNull(),
daemonToken: text('daemon_token').notNull(),
tlsEnabled: boolean('tls_enabled').default(true).notNull(),
isOnline: boolean('is_online').default(false).notNull(),
lastHeartbeat: timestamp('last_heartbeat', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
});
export const allocations = pgTable('allocations', {
id: uuid('id').defaultRandom().primaryKey(),
nodeId: uuid('node_id')
.notNull()
.references(() => nodes.id, { onDelete: 'cascade' }),
serverId: uuid('server_id').references(() => servers.id, { onDelete: 'set null' }),
ip: varchar('ip', { length: 45 }).notNull(),
port: integer('port').notNull(),
isDefault: boolean('is_default').default(false).notNull(),
});
@@ -0,0 +1,30 @@
import { pgTable, uuid, varchar, integer, timestamp, jsonb, pgEnum } from 'drizzle-orm/pg-core';
import { users } from './users';
export const orgRoleEnum = pgEnum('org_role', ['admin', 'user']);
export const organizations = pgTable('organizations', {
id: uuid('id').defaultRandom().primaryKey(),
name: varchar('name', { length: 255 }).notNull(),
slug: varchar('slug', { length: 255 }).notNull().unique(),
ownerId: uuid('owner_id')
.notNull()
.references(() => users.id),
maxServers: integer('max_servers').default(0).notNull(),
maxNodes: integer('max_nodes').default(0).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
});
export const organizationMembers = pgTable('organization_members', {
id: uuid('id').defaultRandom().primaryKey(),
organizationId: uuid('organization_id')
.notNull()
.references(() => organizations.id, { onDelete: 'cascade' }),
userId: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
role: orgRoleEnum('role').default('user').notNull(),
customPermissions: jsonb('custom_permissions').default({}).notNull(),
joinedAt: timestamp('joined_at', { withTimezone: true }).defaultNow().notNull(),
});
+42
View File
@@ -0,0 +1,42 @@
import {
pgTable,
uuid,
varchar,
text,
boolean,
timestamp,
pgEnum,
} from 'drizzle-orm/pg-core';
import { games } from './games';
import { servers } from './servers';
export const pluginSourceEnum = pgEnum('plugin_source', ['spiget', 'manual']);
export const plugins = pgTable('plugins', {
id: uuid('id').defaultRandom().primaryKey(),
gameId: uuid('game_id')
.notNull()
.references(() => games.id, { onDelete: 'cascade' }),
name: varchar('name', { length: 255 }).notNull(),
slug: varchar('slug', { length: 255 }).notNull(),
description: text('description'),
source: pluginSourceEnum('source').notNull(),
externalId: varchar('external_id', { length: 255 }),
downloadUrl: text('download_url'),
version: varchar('version', { length: 100 }),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
});
export const serverPlugins = pgTable('server_plugins', {
id: uuid('id').defaultRandom().primaryKey(),
serverId: uuid('server_id')
.notNull()
.references(() => servers.id, { onDelete: 'cascade' }),
pluginId: uuid('plugin_id')
.notNull()
.references(() => plugins.id, { onDelete: 'cascade' }),
installedVersion: varchar('installed_version', { length: 100 }),
isActive: boolean('is_active').default(true).notNull(),
installedAt: timestamp('installed_at', { withTimezone: true }).defaultNow().notNull(),
});
+37
View File
@@ -0,0 +1,37 @@
import {
pgTable,
uuid,
varchar,
text,
boolean,
jsonb,
timestamp,
pgEnum,
} from 'drizzle-orm/pg-core';
import { servers } from './servers';
export const scheduleActionEnum = pgEnum('schedule_action', ['command', 'power', 'backup']);
export const scheduleTypeEnum = pgEnum('schedule_type', [
'interval',
'daily',
'weekly',
'cron',
]);
export const scheduledTasks = pgTable('scheduled_tasks', {
id: uuid('id').defaultRandom().primaryKey(),
serverId: uuid('server_id')
.notNull()
.references(() => servers.id, { onDelete: 'cascade' }),
name: varchar('name', { length: 255 }).notNull(),
action: scheduleActionEnum('action').notNull(),
payload: text('payload').notNull(),
scheduleType: scheduleTypeEnum('schedule_type').notNull(),
scheduleData: jsonb('schedule_data').notNull(),
isActive: boolean('is_active').default(true).notNull(),
lastRunAt: timestamp('last_run_at', { withTimezone: true }),
nextRunAt: timestamp('next_run_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
});
+63
View File
@@ -0,0 +1,63 @@
import {
pgTable,
uuid,
varchar,
integer,
bigint,
text,
jsonb,
timestamp,
pgEnum,
} from 'drizzle-orm/pg-core';
import { organizations } from './organizations';
import { nodes } from './nodes';
import { games } from './games';
import { users } from './users';
export const serverStatusEnum = pgEnum('server_status', [
'installing',
'running',
'stopped',
'suspended',
'error',
]);
export const servers = pgTable('servers', {
id: uuid('id').defaultRandom().primaryKey(),
uuid: varchar('uuid', { length: 36 }).notNull().unique(),
organizationId: uuid('organization_id')
.notNull()
.references(() => organizations.id, { onDelete: 'cascade' }),
nodeId: uuid('node_id')
.notNull()
.references(() => nodes.id),
gameId: uuid('game_id')
.notNull()
.references(() => games.id),
name: varchar('name', { length: 255 }).notNull(),
description: text('description'),
status: serverStatusEnum('status').default('installing').notNull(),
memoryLimit: bigint('memory_limit', { mode: 'number' }).notNull(),
diskLimit: bigint('disk_limit', { mode: 'number' }).notNull(),
cpuLimit: integer('cpu_limit').default(100).notNull(),
port: integer('port').notNull(),
additionalPorts: jsonb('additional_ports').default([]).notNull(),
environment: jsonb('environment').default({}).notNull(),
startupOverride: text('startup_override'),
installedAt: timestamp('installed_at', { withTimezone: true }),
suspendedAt: timestamp('suspended_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
});
export const serverSubusers = pgTable('server_subusers', {
id: uuid('id').defaultRandom().primaryKey(),
serverId: uuid('server_id')
.notNull()
.references(() => servers.id, { onDelete: 'cascade' }),
userId: uuid('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
permissions: jsonb('permissions').notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
});
+12
View File
@@ -0,0 +1,12 @@
import { pgTable, uuid, varchar, text, boolean, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: uuid('id').defaultRandom().primaryKey(),
email: varchar('email', { length: 255 }).notNull().unique(),
username: varchar('username', { length: 100 }).notNull().unique(),
passwordHash: text('password_hash').notNull(),
isSuperAdmin: boolean('is_super_admin').default(false).notNull(),
avatarUrl: text('avatar_url'),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
});
+115
View File
@@ -0,0 +1,115 @@
import { createDb } from './client';
import { games } from './schema/games';
async function seed() {
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
console.error('DATABASE_URL is required');
process.exit(1);
}
const db = createDb(databaseUrl);
console.log('Seeding games...');
await db
.insert(games)
.values([
{
slug: 'minecraft-java',
name: 'Minecraft: Java Edition',
dockerImage: 'itzg/minecraft-server:latest',
defaultPort: 25565,
startupCommand: '/start',
stopCommand: 'stop',
configFiles: JSON.stringify([
{
path: 'server.properties',
parser: 'properties',
editableKeys: [
'server-port',
'max-players',
'motd',
'difficulty',
'gamemode',
'level-seed',
'pvp',
'spawn-protection',
'view-distance',
'online-mode',
'white-list',
],
},
{ path: 'ops.json', parser: 'json' },
{ path: 'whitelist.json', parser: 'json' },
{ path: 'bukkit.yml', parser: 'yaml' },
{ path: 'spigot.yml', parser: 'yaml' },
]),
environmentVars: JSON.stringify([
{ key: 'EULA', default: 'TRUE', description: 'Accept Minecraft EULA', required: true },
{
key: 'TYPE',
default: 'PAPER',
description: 'Server type (VANILLA, PAPER, SPIGOT, FORGE, FABRIC)',
required: true,
},
{
key: 'VERSION',
default: 'LATEST',
description: 'Minecraft version',
required: true,
},
{ key: 'MEMORY', default: '1G', description: 'JVM memory allocation', required: false },
]),
},
{
slug: 'cs2',
name: 'Counter-Strike 2',
dockerImage: 'cm2network/csgo:latest',
defaultPort: 27015,
startupCommand:
'./srcds_run -game csgo -console -usercon +game_type 0 +game_mode 0 +mapgroup mg_active +map de_dust2',
stopCommand: 'quit',
configFiles: JSON.stringify([
{
path: 'csgo/cfg/server.cfg',
parser: 'keyvalue',
editableKeys: [
'hostname',
'sv_password',
'rcon_password',
'sv_cheats',
'mp_autoteambalance',
'mp_limitteams',
],
},
{ path: 'csgo/cfg/autoexec.cfg', parser: 'keyvalue' },
]),
environmentVars: JSON.stringify([
{
key: 'SRCDS_TOKEN',
default: '',
description: 'Steam Game Server Login Token',
required: true,
},
{ key: 'SRCDS_RCONPW', default: '', description: 'RCON password', required: false },
{ key: 'SRCDS_PW', default: '', description: 'Server password', required: false },
{
key: 'SRCDS_MAXPLAYERS',
default: '16',
description: 'Max players',
required: false,
},
]),
},
])
.onConflictDoNothing();
console.log('Seed completed successfully!');
process.exit(0);
}
seed().catch((err) => {
console.error('Seed failed:', err);
process.exit(1);
});
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src"]
}