omegga 1.9.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@
4
4
 
5
5
  - Future features go here
6
6
 
7
+ ## 1.9.1 - 2026-07-07
8
+
9
+ Ban-history crash fix, web-ui ban/kick modal polish, and a brs-js bump.
10
+
11
+ - Database: Fix server crash when syncing bans
12
+ - Update brs-js to 3.2.4
13
+
7
14
  ## 1.9.0 - 2026-07-05
8
15
 
9
16
  Wire graph log events and a brs-js 3.0.0 upgrade for native brdb/brz reading.
package/README.md CHANGED
@@ -12,17 +12,18 @@ Omegga can do things like:
12
12
  - Read chat history with timestamps
13
13
  - See kick and ban history
14
14
  - Configure plugins from a web interface
15
+ - Manage permissions and multi-user role based access to the above features on a web ui
15
16
 
16
17
  Omegga plugins can do things like:
17
18
 
19
+ - Interface with in-game wires and react to in-game wire events
18
20
  - Add custom chat !commands and /commands
19
21
  - Respond to and send chat messages
20
- - Load bricks to player's clipboards
21
- - Load/Clear regions of bricks
22
+ - Load bricks onto a player's template
23
+ - Load/Clear regions of bricks, entities
22
24
  - Damage/heal players
23
25
  - Give/remove weapons to players
24
26
  - Change the environment
25
- - Create/delete minigames, and join/leave players from minigames
26
27
  - Teleport players, detect player's positions
27
28
  - Grant players roles
28
29
  - Detect when a brick with an interact component is clicked
@@ -247,8 +247,10 @@ async function importNedb(dataPath, mainSqlite, mainDb) {
247
247
  mainDb.insert(schema.banHistory).values({
248
248
  banned: b.banned ?? "",
249
249
  bannerId: b.bannerId ?? "",
250
- created: typeof b.created === "number" ? b.created : 0,
251
- expires: typeof b.expires === "number" ? b.expires : 0,
250
+ // Number.isFinite (not typeof) so a NaN timestamp becomes 0,
251
+ // binding NaN to a NOT NULL integer column stores NULL and throws
252
+ created: Number.isFinite(b.created) ? b.created : 0,
253
+ expires: Number.isFinite(b.expires) ? b.expires : 0,
252
254
  reason: b.reason ?? ""
253
255
  }).onConflictDoNothing().run();
254
256
  }
@@ -1 +1 @@
1
- {"version":3,"file":"nedbImport.js","sources":["../../src/db/nedbImport.ts"],"sourcesContent":["import Logger from '@/logger';\nimport soft from '@/softconfig';\nimport {\n decodePermissions,\n type StoredPermissionSet,\n} from '@webserver/backend/permissions';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { eq } from 'drizzle-orm';\nimport {\n drizzle,\n type BetterSQLite3Database,\n} from 'drizzle-orm/better-sqlite3';\nimport fs from 'fs';\nimport Datastore from 'nedb-promises';\nimport path from 'path';\nimport { openDb } from './connection';\nimport { runMigrations, runPluginMigrations } from './migrate';\nimport * as mainSchema from './schema';\nimport * as pluginSchema from './pluginSchema';\n\nconst MARKER_FILE = 'nedb-imported.marker';\nconst BATCH_SIZE = 500;\n\n// legacy NeDB database filenames; the one-time import is their only consumer\nexport const NEDB_FILES = {\n users: 'users.db',\n chat: 'chat.db',\n players: 'players.db',\n status: 'status.db',\n server: 'store.db',\n plugins: 'plugins.db',\n} as const;\n\n// Apply the NeDB data migrations that the old doMigrations() would have run.\n// Users v1: convert old NeDB permission format to PermissionSet.\n// Server v1: re-encode defaultPermissions from old format.\n// NeDB stored scope keys colon-encoded ('chat:send'); runtime lookups use\n// dotted keys, so every path below must run decodePermissions.\n\n// the v1 format used 'unset' sentinels and 'enabled'/'disabled' scope strings,\n// none of which are valid v2 values. detect by the presence of any sentinel so\n// an empty-scopes v1 doc (which a scopes-only check would miss) is still\n// normalized, while a valid v2 'none' domain is preserved.\nfunction isLegacyPermissions(p: any): boolean {\n if (p.root === 'unset') return true;\n for (const v of Object.values(p.domains ?? {}))\n if (v === 'unset') return true;\n for (const v of Object.values(p.scopes ?? {}))\n if (typeof v === 'string') return true;\n return false;\n}\n\n// normalize a v1 permission doc to a v2 StoredPermissionSet (still colon-keyed)\nfunction normalizeLegacyPermissions(p: any): StoredPermissionSet {\n const root = p.root === 'all' || p.root === 'read' ? p.root : 'off';\n const domains: Record<string, string> = {};\n for (const [k, v] of Object.entries(p.domains ?? {})) {\n if (v !== 'unset' && v !== 'none') domains[k] = v as string;\n }\n const scopes: Record<string, boolean> = {};\n for (const [k, v] of Object.entries(p.scopes ?? {})) {\n if (v === 'enabled' || v === true) scopes[k] = true;\n else if (v === 'disabled' || v === false) scopes[k] = false;\n }\n return { root, domains, scopes } as StoredPermissionSet;\n}\n\nfunction migrateUserPermissions(user: any): any {\n const p = user.permissions;\n if (!p || typeof p !== 'object') return user;\n const stored = isLegacyPermissions(p) ? normalizeLegacyPermissions(p) : p;\n return { ...user, permissions: decodePermissions(stored) };\n}\n\nfunction migrateDefaultPermissions(doc: any): any {\n if (!doc) return doc;\n const stored = isLegacyPermissions(doc)\n ? normalizeLegacyPermissions(doc)\n : doc;\n return { ...doc, ...decodePermissions(stored) };\n}\n\n// legacy nameHistory may predate the {name,displayName,date} object shape (older\n// docs stored bare strings) or be hand-edited; coerce to well-formed objects so\n// the json_each/REGEXP player search can't hit malformed JSON or null keys\nfunction sanitizeNameHistory(raw: any): {\n name: string;\n displayName: string;\n date: number;\n}[] {\n if (!Array.isArray(raw)) return [];\n return raw\n .filter(h => h && typeof h === 'object')\n .map(h => ({\n name: String(h.name ?? ''),\n displayName: String(h.displayName ?? ''),\n date: typeof h.date === 'number' ? h.date : 0,\n }));\n}\n\nasync function loadNedb<T = any>(filepath: string): Promise<T[]> {\n if (!fs.existsSync(filepath)) return [];\n const store = Datastore.create({ filename: filepath, autoload: true });\n return (await store.find({})) as T[];\n}\n\n// invoke fn on BATCH_SIZE-sized slices of docs (keeps multi-row inserts under\n// SQLite's bound-parameter limit). NOT wrapped in its own transaction — callers\n// run all batches inside one outer transaction for atomicity.\nfunction eachBatch(docs: any[], fn: (docs: any[]) => void) {\n for (let i = 0; i < docs.length; i += BATCH_SIZE) {\n fn(docs.slice(i, i + BATCH_SIZE));\n }\n}\n\n// key in server_config marking the main-DB import as committed. checked\n// in-band with the data so a crash between commit and marker-file write can't\n// cause a re-import (which would duplicate the append-only tables).\nconst IMPORT_FLAG_KEY = 'nedbImported';\n\n// coalesce concurrent import calls for the same data dir (the webserver and the\n// plugin loader both bootstrap it). entries are dropped once settled, so this is\n// purely a concurrency guard — repeat suppression is the marker file / DB flag.\nconst importsInFlight = new Map<string, Promise<void>>();\n\nexport function importNedbIfNeeded(\n dataPath: string,\n mainSqlite?: BetterSqlite3.Database,\n mainDb?: BetterSQLite3Database,\n): Promise<void> {\n const key = path.resolve(dataPath);\n let promise = importsInFlight.get(key);\n if (!promise) {\n promise = importNedb(dataPath, mainSqlite, mainDb).finally(() =>\n importsInFlight.delete(key),\n );\n importsInFlight.set(key, promise);\n }\n return promise;\n}\n\nasync function importNedb(\n dataPath: string,\n mainSqlite?: BetterSqlite3.Database,\n mainDb?: BetterSQLite3Database,\n) {\n const markerPath = path.join(dataPath, MARKER_FILE);\n if (fs.existsSync(markerPath)) return;\n\n const filePath = (f: string) => path.join(dataPath, f);\n const mainNedbFiles = [\n NEDB_FILES.users,\n NEDB_FILES.chat,\n NEDB_FILES.players,\n NEDB_FILES.status,\n NEDB_FILES.server,\n ];\n const hasMainNedb = mainNedbFiles.some(f => fs.existsSync(filePath(f)));\n const pluginPath = filePath(NEDB_FILES.plugins);\n const hasPluginNedb = fs.existsSync(pluginPath);\n if (!hasMainNedb && !hasPluginNedb) {\n fs.writeFileSync(markerPath, new Date().toISOString());\n return;\n }\n\n // open a dedicated main-db connection when the caller doesn't have one\n // (the plugin loader bootstraps the import without a webserver)\n let ownConnection = false;\n if (!mainSqlite || !mainDb) {\n ownConnection = true;\n mainSqlite = openDb(filePath(soft.MAIN_DB));\n mainDb = drizzle(mainSqlite);\n runMigrations(mainDb);\n }\n\n try {\n Logger.log('>>'.green, 'Importing NeDB data into SQLite...');\n const counts: Record<string, number> = {};\n\n // ---- Load phase: read every NeDB file up front (async). All writes happen\n // afterwards inside a single synchronous transaction so a partial failure\n // rolls back cleanly and a retry can't double-import append-only tables. ----\n const users = (await loadNedb(filePath(NEDB_FILES.users)))\n .filter((d: any) => d.type === 'user')\n .map(migrateUserPermissions);\n const chatMessages = (await loadNedb(filePath(NEDB_FILES.chat)))\n .filter((d: any) => d.type === 'chat')\n .sort((a: any, b: any) => (a.created ?? 0) - (b.created ?? 0));\n const playerDocs = await loadNedb(filePath(NEDB_FILES.players));\n const histories = playerDocs.filter((d: any) => d.type === 'userHistory');\n const bans = playerDocs.filter((d: any) => d.type === 'banHistory');\n const kicks = playerDocs.filter((d: any) => d.type === 'kickHistory');\n const notes = playerDocs.filter((d: any) => d.type === 'note');\n const statusDocs = await loadNedb(filePath(NEDB_FILES.status));\n const hbDocs = statusDocs\n .filter((d: any) => d.type === 'heartbeat')\n .sort((a: any, b: any) => (a.created ?? 0) - (b.created ?? 0));\n const pcDocs = statusDocs.filter((d: any) => d.type === 'punchcard');\n const serverDocs = await loadNedb(filePath(NEDB_FILES.server));\n const instances = serverDocs.filter((d: any) => d.type === 'app:start');\n const roles = serverDocs.filter((d: any) => d.type === 'webRole');\n const rawDefaultPerms = serverDocs.find(\n (d: any) => d.type === 'defaultPermissions',\n );\n const autoRestart = serverDocs.find(\n (d: any) => d.type === 'autoRestartConfig',\n );\n\n // warn about user rows that will be dropped by the new UNIQUE(username):\n // NeDB had no such constraint, so legacy data can hold duplicates\n const seenUsernames = new Set<string>();\n for (const u of users) {\n const name = u.username ?? '';\n if (seenUsernames.has(name))\n Logger.error(\n '!>'.red,\n `Duplicate username ${JSON.stringify(name)} in legacy data; user ${u._id} will not be imported`,\n );\n else seenUsernames.add(name);\n }\n\n // ---- Plugins: a separate connection/DB, imported first and idempotently\n // (onConflictDoNothing against real unique constraints), so re-running after\n // a main-DB failure is a safe no-op. ----\n if (hasPluginNedb) {\n const pluginSqlite = openDb(filePath(soft.PLUGINS_DB));\n try {\n const pluginDb = drizzle(pluginSqlite);\n runPluginMigrations(pluginDb);\n const pluginDocs = await loadNedb(pluginPath);\n // NeDB allowed store.set(key, null/undefined); the new value column is\n // NOT NULL, so skip those rows\n const storeDocs = pluginDocs.filter(\n (d: any) => d.type === 'store' && d.value != null,\n );\n const skipped =\n pluginDocs.filter((d: any) => d.type === 'store').length -\n storeDocs.length;\n if (skipped > 0)\n Logger.log(\n ':>'.yellow,\n `Skipping ${skipped} plugin store entr${skipped === 1 ? 'y' : 'ies'} with no value`,\n );\n const configDocs = pluginDocs.filter((d: any) => d.type === 'config');\n\n const pluginTx = pluginSqlite.transaction(() => {\n eachBatch(storeDocs, batch =>\n pluginDb\n .insert(pluginSchema.pluginStore)\n .values(\n batch.map((s: any) => ({\n plugin: s.plugin ?? '',\n key: s.key ?? '',\n value: s.value,\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n eachBatch(configDocs, batch =>\n pluginDb\n .insert(pluginSchema.pluginConfig)\n .values(\n batch.map((c: any) => ({\n plugin: c.plugin ?? '',\n value: c.value ?? {},\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n });\n pluginTx();\n if (storeDocs.length) counts.pluginStoreItems = storeDocs.length;\n if (configDocs.length) counts.pluginConfigs = configDocs.length;\n } finally {\n pluginSqlite.close();\n }\n }\n\n // ---- Main DB: one atomic transaction. The completion flag is written in\n // the same transaction as the data, so either both commit or neither does —\n // a crash before the marker file is written can't trigger a re-import. ----\n const alreadyImported = mainDb\n .select()\n .from(mainSchema.serverConfig)\n .where(eq(mainSchema.serverConfig.key, IMPORT_FLAG_KEY))\n .get();\n\n if (!alreadyImported && hasMainNedb) {\n const tx = mainSqlite.transaction(() => {\n eachBatch(users, batch =>\n mainDb\n .insert(mainSchema.users)\n .values(\n batch.map((u: any) => ({\n id: u._id,\n created: u.created ?? Date.now(),\n lastOnline: u.lastOnline ?? 0,\n username: u.username ?? '',\n hash: u.hash ?? '',\n isOwner: !!u.isOwner,\n roles: u.roles ?? [],\n playerId: u.playerId ?? '',\n isBanned: !!u.isBanned,\n permissions: u.permissions ?? null,\n totpSecret: u.totpSecret ?? null,\n totpEnabled: !!u.totpEnabled,\n passkeys: u.passkeys ?? [],\n recoveryCodes: u.recoveryCodes ?? [],\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n if (users.length) counts.users = users.length;\n\n eachBatch(chatMessages, batch =>\n mainDb\n .insert(mainSchema.chatLogs)\n .values(\n batch.map((c: any) => ({\n created: c.created ?? 0,\n instanceId: c.instanceId ?? '',\n action: c.action ?? 'msg',\n user: c.user ?? {},\n message: c.message ?? null,\n })),\n )\n .run(),\n );\n if (chatMessages.length) counts.chatMessages = chatMessages.length;\n\n eachBatch(histories, batch =>\n mainDb\n .insert(mainSchema.playerHistory)\n .values(\n batch.map((p: any) => ({\n id: p.id,\n name: p.name ?? '',\n displayName: p.displayName ?? '',\n nameHistory: sanitizeNameHistory(p.nameHistory),\n ips: Array.isArray(p.ips) ? p.ips : [],\n created: p.created ?? 0,\n lastSeen: p.lastSeen ?? 0,\n lastInstanceId: p.lastInstanceId ?? '',\n heartbeats: p.heartbeats ?? 0,\n sessions: p.sessions ?? 0,\n instances: p.instances ?? 0,\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n if (histories.length) counts.players = histories.length;\n\n for (const b of bans) {\n mainDb\n .insert(mainSchema.banHistory)\n .values({\n banned: b.banned ?? '',\n bannerId: b.bannerId ?? '',\n created: typeof b.created === 'number' ? b.created : 0,\n expires: typeof b.expires === 'number' ? b.expires : 0,\n reason: b.reason ?? '',\n })\n .onConflictDoNothing()\n .run();\n }\n if (bans.length) counts.bans = bans.length;\n\n for (const k of kicks) {\n mainDb\n .insert(mainSchema.kickHistory)\n .values({\n kicked: k.kicked ?? '',\n kickerId: k.kickerId ?? '',\n created: k.created ?? 0,\n reason: k.reason ?? '',\n })\n .onConflictDoNothing()\n .run();\n }\n if (kicks.length) counts.kicks = kicks.length;\n\n eachBatch(notes, batch =>\n mainDb\n .insert(mainSchema.playerNotes)\n .values(\n batch.map((n: any) => ({\n playerId: n.id ?? '',\n note: n.note ?? '',\n })),\n )\n .run(),\n );\n if (notes.length) counts.notes = notes.length;\n\n eachBatch(hbDocs, batch =>\n mainDb\n .insert(mainSchema.heartbeats)\n .values(\n batch.map((h: any) => ({\n created: h.created ?? 0,\n bricks: h.bricks ?? 0,\n players: Array.isArray(h.players) ? h.players : [],\n })),\n )\n .run(),\n );\n if (hbDocs.length) counts.heartbeats = hbDocs.length;\n\n for (const p of pcDocs) {\n mainDb\n .insert(mainSchema.punchcards)\n .values({\n kind: p.kind ?? 'playerCount',\n created: p.created ?? 0,\n updated: p.updated ?? 0,\n month: p.month ?? 0,\n year: p.year ?? 0,\n punchcard: p.punchcard ?? [],\n })\n .onConflictDoNothing()\n .run();\n }\n if (pcDocs.length) counts.punchcards = pcDocs.length;\n\n eachBatch(instances, batch =>\n mainDb\n .insert(mainSchema.serverInstances)\n .values(batch.map((i: any) => ({ id: i._id, date: i.date ?? 0 })))\n .onConflictDoNothing()\n .run(),\n );\n if (instances.length) counts.serverInstances = instances.length;\n\n eachBatch(roles, batch =>\n mainDb\n .insert(mainSchema.webRoles)\n .values(\n batch.map((r: any) => ({\n id: r._id,\n name: r.name ?? '',\n description: r.description ?? '',\n order: r.order ?? 0,\n permissions: decodePermissions(r.permissions),\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n if (roles.length) counts.roles = roles.length;\n\n const defaultPerms = migrateDefaultPermissions(rawDefaultPerms);\n if (defaultPerms) {\n mainDb\n .insert(mainSchema.serverConfig)\n .values({\n key: 'defaultPermissions',\n value: {\n root: defaultPerms.root,\n domains: defaultPerms.domains,\n scopes: defaultPerms.scopes,\n },\n })\n .onConflictDoNothing()\n .run();\n }\n\n if (autoRestart) {\n const { _id: _, type: __, ...config } = autoRestart;\n mainDb\n .insert(mainSchema.serverConfig)\n .values({ key: 'autoRestartConfig', value: config })\n .onConflictDoNothing()\n .run();\n }\n\n // commit the completion flag atomically with the data above\n mainDb\n .insert(mainSchema.serverConfig)\n .values({ key: IMPORT_FLAG_KEY, value: new Date().toISOString() })\n .onConflictDoNothing()\n .run();\n });\n tx();\n }\n\n // marker file enables the cheap fast-path skip on subsequent boots\n fs.writeFileSync(markerPath, new Date().toISOString());\n\n const parts = Object.entries(counts)\n .map(([k, v]) => `${v} ${k}`)\n .join(', ');\n Logger.log('>>'.green, `Imported ${parts || 'no records'}`);\n Logger.log('>>'.green, 'NeDB import complete. Original files preserved.');\n } finally {\n if (ownConnection) mainSqlite.close();\n }\n}\n"],"names":["decodePermissions","openDb","soft","drizzle","runMigrations","Logger","runPluginMigrations","pluginSchema.pluginStore","pluginSchema.pluginConfig","mainSchema.serverConfig","eq","mainSchema.users","mainSchema.chatLogs","mainSchema.playerHistory","mainSchema.banHistory","mainSchema.kickHistory","mainSchema.playerNotes","mainSchema.heartbeats","mainSchema.punchcards","mainSchema.serverInstances","mainSchema.webRoles"],"mappings":"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"}
1
+ {"version":3,"file":"nedbImport.js","sources":["../../src/db/nedbImport.ts"],"sourcesContent":["import Logger from '@/logger';\nimport soft from '@/softconfig';\nimport {\n decodePermissions,\n type StoredPermissionSet,\n} from '@webserver/backend/permissions';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { eq } from 'drizzle-orm';\nimport {\n drizzle,\n type BetterSQLite3Database,\n} from 'drizzle-orm/better-sqlite3';\nimport fs from 'fs';\nimport Datastore from 'nedb-promises';\nimport path from 'path';\nimport { openDb } from './connection';\nimport { runMigrations, runPluginMigrations } from './migrate';\nimport * as mainSchema from './schema';\nimport * as pluginSchema from './pluginSchema';\n\nconst MARKER_FILE = 'nedb-imported.marker';\nconst BATCH_SIZE = 500;\n\n// legacy NeDB database filenames; the one-time import is their only consumer\nexport const NEDB_FILES = {\n users: 'users.db',\n chat: 'chat.db',\n players: 'players.db',\n status: 'status.db',\n server: 'store.db',\n plugins: 'plugins.db',\n} as const;\n\n// Apply the NeDB data migrations that the old doMigrations() would have run.\n// Users v1: convert old NeDB permission format to PermissionSet.\n// Server v1: re-encode defaultPermissions from old format.\n// NeDB stored scope keys colon-encoded ('chat:send'); runtime lookups use\n// dotted keys, so every path below must run decodePermissions.\n\n// the v1 format used 'unset' sentinels and 'enabled'/'disabled' scope strings,\n// none of which are valid v2 values. detect by the presence of any sentinel so\n// an empty-scopes v1 doc (which a scopes-only check would miss) is still\n// normalized, while a valid v2 'none' domain is preserved.\nfunction isLegacyPermissions(p: any): boolean {\n if (p.root === 'unset') return true;\n for (const v of Object.values(p.domains ?? {}))\n if (v === 'unset') return true;\n for (const v of Object.values(p.scopes ?? {}))\n if (typeof v === 'string') return true;\n return false;\n}\n\n// normalize a v1 permission doc to a v2 StoredPermissionSet (still colon-keyed)\nfunction normalizeLegacyPermissions(p: any): StoredPermissionSet {\n const root = p.root === 'all' || p.root === 'read' ? p.root : 'off';\n const domains: Record<string, string> = {};\n for (const [k, v] of Object.entries(p.domains ?? {})) {\n if (v !== 'unset' && v !== 'none') domains[k] = v as string;\n }\n const scopes: Record<string, boolean> = {};\n for (const [k, v] of Object.entries(p.scopes ?? {})) {\n if (v === 'enabled' || v === true) scopes[k] = true;\n else if (v === 'disabled' || v === false) scopes[k] = false;\n }\n return { root, domains, scopes } as StoredPermissionSet;\n}\n\nfunction migrateUserPermissions(user: any): any {\n const p = user.permissions;\n if (!p || typeof p !== 'object') return user;\n const stored = isLegacyPermissions(p) ? normalizeLegacyPermissions(p) : p;\n return { ...user, permissions: decodePermissions(stored) };\n}\n\nfunction migrateDefaultPermissions(doc: any): any {\n if (!doc) return doc;\n const stored = isLegacyPermissions(doc)\n ? normalizeLegacyPermissions(doc)\n : doc;\n return { ...doc, ...decodePermissions(stored) };\n}\n\n// legacy nameHistory may predate the {name,displayName,date} object shape (older\n// docs stored bare strings) or be hand-edited; coerce to well-formed objects so\n// the json_each/REGEXP player search can't hit malformed JSON or null keys\nfunction sanitizeNameHistory(raw: any): {\n name: string;\n displayName: string;\n date: number;\n}[] {\n if (!Array.isArray(raw)) return [];\n return raw\n .filter(h => h && typeof h === 'object')\n .map(h => ({\n name: String(h.name ?? ''),\n displayName: String(h.displayName ?? ''),\n date: typeof h.date === 'number' ? h.date : 0,\n }));\n}\n\nasync function loadNedb<T = any>(filepath: string): Promise<T[]> {\n if (!fs.existsSync(filepath)) return [];\n const store = Datastore.create({ filename: filepath, autoload: true });\n return (await store.find({})) as T[];\n}\n\n// invoke fn on BATCH_SIZE-sized slices of docs (keeps multi-row inserts under\n// SQLite's bound-parameter limit). NOT wrapped in its own transaction — callers\n// run all batches inside one outer transaction for atomicity.\nfunction eachBatch(docs: any[], fn: (docs: any[]) => void) {\n for (let i = 0; i < docs.length; i += BATCH_SIZE) {\n fn(docs.slice(i, i + BATCH_SIZE));\n }\n}\n\n// key in server_config marking the main-DB import as committed. checked\n// in-band with the data so a crash between commit and marker-file write can't\n// cause a re-import (which would duplicate the append-only tables).\nconst IMPORT_FLAG_KEY = 'nedbImported';\n\n// coalesce concurrent import calls for the same data dir (the webserver and the\n// plugin loader both bootstrap it). entries are dropped once settled, so this is\n// purely a concurrency guard — repeat suppression is the marker file / DB flag.\nconst importsInFlight = new Map<string, Promise<void>>();\n\nexport function importNedbIfNeeded(\n dataPath: string,\n mainSqlite?: BetterSqlite3.Database,\n mainDb?: BetterSQLite3Database,\n): Promise<void> {\n const key = path.resolve(dataPath);\n let promise = importsInFlight.get(key);\n if (!promise) {\n promise = importNedb(dataPath, mainSqlite, mainDb).finally(() =>\n importsInFlight.delete(key),\n );\n importsInFlight.set(key, promise);\n }\n return promise;\n}\n\nasync function importNedb(\n dataPath: string,\n mainSqlite?: BetterSqlite3.Database,\n mainDb?: BetterSQLite3Database,\n) {\n const markerPath = path.join(dataPath, MARKER_FILE);\n if (fs.existsSync(markerPath)) return;\n\n const filePath = (f: string) => path.join(dataPath, f);\n const mainNedbFiles = [\n NEDB_FILES.users,\n NEDB_FILES.chat,\n NEDB_FILES.players,\n NEDB_FILES.status,\n NEDB_FILES.server,\n ];\n const hasMainNedb = mainNedbFiles.some(f => fs.existsSync(filePath(f)));\n const pluginPath = filePath(NEDB_FILES.plugins);\n const hasPluginNedb = fs.existsSync(pluginPath);\n if (!hasMainNedb && !hasPluginNedb) {\n fs.writeFileSync(markerPath, new Date().toISOString());\n return;\n }\n\n // open a dedicated main-db connection when the caller doesn't have one\n // (the plugin loader bootstraps the import without a webserver)\n let ownConnection = false;\n if (!mainSqlite || !mainDb) {\n ownConnection = true;\n mainSqlite = openDb(filePath(soft.MAIN_DB));\n mainDb = drizzle(mainSqlite);\n runMigrations(mainDb);\n }\n\n try {\n Logger.log('>>'.green, 'Importing NeDB data into SQLite...');\n const counts: Record<string, number> = {};\n\n // ---- Load phase: read every NeDB file up front (async). All writes happen\n // afterwards inside a single synchronous transaction so a partial failure\n // rolls back cleanly and a retry can't double-import append-only tables. ----\n const users = (await loadNedb(filePath(NEDB_FILES.users)))\n .filter((d: any) => d.type === 'user')\n .map(migrateUserPermissions);\n const chatMessages = (await loadNedb(filePath(NEDB_FILES.chat)))\n .filter((d: any) => d.type === 'chat')\n .sort((a: any, b: any) => (a.created ?? 0) - (b.created ?? 0));\n const playerDocs = await loadNedb(filePath(NEDB_FILES.players));\n const histories = playerDocs.filter((d: any) => d.type === 'userHistory');\n const bans = playerDocs.filter((d: any) => d.type === 'banHistory');\n const kicks = playerDocs.filter((d: any) => d.type === 'kickHistory');\n const notes = playerDocs.filter((d: any) => d.type === 'note');\n const statusDocs = await loadNedb(filePath(NEDB_FILES.status));\n const hbDocs = statusDocs\n .filter((d: any) => d.type === 'heartbeat')\n .sort((a: any, b: any) => (a.created ?? 0) - (b.created ?? 0));\n const pcDocs = statusDocs.filter((d: any) => d.type === 'punchcard');\n const serverDocs = await loadNedb(filePath(NEDB_FILES.server));\n const instances = serverDocs.filter((d: any) => d.type === 'app:start');\n const roles = serverDocs.filter((d: any) => d.type === 'webRole');\n const rawDefaultPerms = serverDocs.find(\n (d: any) => d.type === 'defaultPermissions',\n );\n const autoRestart = serverDocs.find(\n (d: any) => d.type === 'autoRestartConfig',\n );\n\n // warn about user rows that will be dropped by the new UNIQUE(username):\n // NeDB had no such constraint, so legacy data can hold duplicates\n const seenUsernames = new Set<string>();\n for (const u of users) {\n const name = u.username ?? '';\n if (seenUsernames.has(name))\n Logger.error(\n '!>'.red,\n `Duplicate username ${JSON.stringify(name)} in legacy data; user ${u._id} will not be imported`,\n );\n else seenUsernames.add(name);\n }\n\n // ---- Plugins: a separate connection/DB, imported first and idempotently\n // (onConflictDoNothing against real unique constraints), so re-running after\n // a main-DB failure is a safe no-op. ----\n if (hasPluginNedb) {\n const pluginSqlite = openDb(filePath(soft.PLUGINS_DB));\n try {\n const pluginDb = drizzle(pluginSqlite);\n runPluginMigrations(pluginDb);\n const pluginDocs = await loadNedb(pluginPath);\n // NeDB allowed store.set(key, null/undefined); the new value column is\n // NOT NULL, so skip those rows\n const storeDocs = pluginDocs.filter(\n (d: any) => d.type === 'store' && d.value != null,\n );\n const skipped =\n pluginDocs.filter((d: any) => d.type === 'store').length -\n storeDocs.length;\n if (skipped > 0)\n Logger.log(\n ':>'.yellow,\n `Skipping ${skipped} plugin store entr${skipped === 1 ? 'y' : 'ies'} with no value`,\n );\n const configDocs = pluginDocs.filter((d: any) => d.type === 'config');\n\n const pluginTx = pluginSqlite.transaction(() => {\n eachBatch(storeDocs, batch =>\n pluginDb\n .insert(pluginSchema.pluginStore)\n .values(\n batch.map((s: any) => ({\n plugin: s.plugin ?? '',\n key: s.key ?? '',\n value: s.value,\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n eachBatch(configDocs, batch =>\n pluginDb\n .insert(pluginSchema.pluginConfig)\n .values(\n batch.map((c: any) => ({\n plugin: c.plugin ?? '',\n value: c.value ?? {},\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n });\n pluginTx();\n if (storeDocs.length) counts.pluginStoreItems = storeDocs.length;\n if (configDocs.length) counts.pluginConfigs = configDocs.length;\n } finally {\n pluginSqlite.close();\n }\n }\n\n // ---- Main DB: one atomic transaction. The completion flag is written in\n // the same transaction as the data, so either both commit or neither does —\n // a crash before the marker file is written can't trigger a re-import. ----\n const alreadyImported = mainDb\n .select()\n .from(mainSchema.serverConfig)\n .where(eq(mainSchema.serverConfig.key, IMPORT_FLAG_KEY))\n .get();\n\n if (!alreadyImported && hasMainNedb) {\n const tx = mainSqlite.transaction(() => {\n eachBatch(users, batch =>\n mainDb\n .insert(mainSchema.users)\n .values(\n batch.map((u: any) => ({\n id: u._id,\n created: u.created ?? Date.now(),\n lastOnline: u.lastOnline ?? 0,\n username: u.username ?? '',\n hash: u.hash ?? '',\n isOwner: !!u.isOwner,\n roles: u.roles ?? [],\n playerId: u.playerId ?? '',\n isBanned: !!u.isBanned,\n permissions: u.permissions ?? null,\n totpSecret: u.totpSecret ?? null,\n totpEnabled: !!u.totpEnabled,\n passkeys: u.passkeys ?? [],\n recoveryCodes: u.recoveryCodes ?? [],\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n if (users.length) counts.users = users.length;\n\n eachBatch(chatMessages, batch =>\n mainDb\n .insert(mainSchema.chatLogs)\n .values(\n batch.map((c: any) => ({\n created: c.created ?? 0,\n instanceId: c.instanceId ?? '',\n action: c.action ?? 'msg',\n user: c.user ?? {},\n message: c.message ?? null,\n })),\n )\n .run(),\n );\n if (chatMessages.length) counts.chatMessages = chatMessages.length;\n\n eachBatch(histories, batch =>\n mainDb\n .insert(mainSchema.playerHistory)\n .values(\n batch.map((p: any) => ({\n id: p.id,\n name: p.name ?? '',\n displayName: p.displayName ?? '',\n nameHistory: sanitizeNameHistory(p.nameHistory),\n ips: Array.isArray(p.ips) ? p.ips : [],\n created: p.created ?? 0,\n lastSeen: p.lastSeen ?? 0,\n lastInstanceId: p.lastInstanceId ?? '',\n heartbeats: p.heartbeats ?? 0,\n sessions: p.sessions ?? 0,\n instances: p.instances ?? 0,\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n if (histories.length) counts.players = histories.length;\n\n for (const b of bans) {\n mainDb\n .insert(mainSchema.banHistory)\n .values({\n banned: b.banned ?? '',\n bannerId: b.bannerId ?? '',\n // Number.isFinite (not typeof) so a NaN timestamp becomes 0,\n // binding NaN to a NOT NULL integer column stores NULL and throws\n created: Number.isFinite(b.created) ? b.created : 0,\n expires: Number.isFinite(b.expires) ? b.expires : 0,\n reason: b.reason ?? '',\n })\n .onConflictDoNothing()\n .run();\n }\n if (bans.length) counts.bans = bans.length;\n\n for (const k of kicks) {\n mainDb\n .insert(mainSchema.kickHistory)\n .values({\n kicked: k.kicked ?? '',\n kickerId: k.kickerId ?? '',\n created: k.created ?? 0,\n reason: k.reason ?? '',\n })\n .onConflictDoNothing()\n .run();\n }\n if (kicks.length) counts.kicks = kicks.length;\n\n eachBatch(notes, batch =>\n mainDb\n .insert(mainSchema.playerNotes)\n .values(\n batch.map((n: any) => ({\n playerId: n.id ?? '',\n note: n.note ?? '',\n })),\n )\n .run(),\n );\n if (notes.length) counts.notes = notes.length;\n\n eachBatch(hbDocs, batch =>\n mainDb\n .insert(mainSchema.heartbeats)\n .values(\n batch.map((h: any) => ({\n created: h.created ?? 0,\n bricks: h.bricks ?? 0,\n players: Array.isArray(h.players) ? h.players : [],\n })),\n )\n .run(),\n );\n if (hbDocs.length) counts.heartbeats = hbDocs.length;\n\n for (const p of pcDocs) {\n mainDb\n .insert(mainSchema.punchcards)\n .values({\n kind: p.kind ?? 'playerCount',\n created: p.created ?? 0,\n updated: p.updated ?? 0,\n month: p.month ?? 0,\n year: p.year ?? 0,\n punchcard: p.punchcard ?? [],\n })\n .onConflictDoNothing()\n .run();\n }\n if (pcDocs.length) counts.punchcards = pcDocs.length;\n\n eachBatch(instances, batch =>\n mainDb\n .insert(mainSchema.serverInstances)\n .values(batch.map((i: any) => ({ id: i._id, date: i.date ?? 0 })))\n .onConflictDoNothing()\n .run(),\n );\n if (instances.length) counts.serverInstances = instances.length;\n\n eachBatch(roles, batch =>\n mainDb\n .insert(mainSchema.webRoles)\n .values(\n batch.map((r: any) => ({\n id: r._id,\n name: r.name ?? '',\n description: r.description ?? '',\n order: r.order ?? 0,\n permissions: decodePermissions(r.permissions),\n })),\n )\n .onConflictDoNothing()\n .run(),\n );\n if (roles.length) counts.roles = roles.length;\n\n const defaultPerms = migrateDefaultPermissions(rawDefaultPerms);\n if (defaultPerms) {\n mainDb\n .insert(mainSchema.serverConfig)\n .values({\n key: 'defaultPermissions',\n value: {\n root: defaultPerms.root,\n domains: defaultPerms.domains,\n scopes: defaultPerms.scopes,\n },\n })\n .onConflictDoNothing()\n .run();\n }\n\n if (autoRestart) {\n const { _id: _, type: __, ...config } = autoRestart;\n mainDb\n .insert(mainSchema.serverConfig)\n .values({ key: 'autoRestartConfig', value: config })\n .onConflictDoNothing()\n .run();\n }\n\n // commit the completion flag atomically with the data above\n mainDb\n .insert(mainSchema.serverConfig)\n .values({ key: IMPORT_FLAG_KEY, value: new Date().toISOString() })\n .onConflictDoNothing()\n .run();\n });\n tx();\n }\n\n // marker file enables the cheap fast-path skip on subsequent boots\n fs.writeFileSync(markerPath, new Date().toISOString());\n\n const parts = Object.entries(counts)\n .map(([k, v]) => `${v} ${k}`)\n .join(', ');\n Logger.log('>>'.green, `Imported ${parts || 'no records'}`);\n Logger.log('>>'.green, 'NeDB import complete. Original files preserved.');\n } finally {\n if (ownConnection) mainSqlite.close();\n }\n}\n"],"names":["decodePermissions","openDb","soft","drizzle","runMigrations","Logger","runPluginMigrations","pluginSchema.pluginStore","pluginSchema.pluginConfig","mainSchema.serverConfig","eq","mainSchema.users","mainSchema.chatLogs","mainSchema.playerHistory","mainSchema.banHistory","mainSchema.kickHistory","mainSchema.playerNotes","mainSchema.heartbeats","mainSchema.punchcards","mainSchema.serverInstances","mainSchema.webRoles"],"mappings":"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"}
package/dist/util/time.js CHANGED
@@ -27,7 +27,9 @@ function parseDuration(str) {
27
27
  return total;
28
28
  }
29
29
  function parseBrickadiaTime(str) {
30
+ if (typeof str !== "string") return NaN;
30
31
  const [date, time] = str.split("-");
32
+ if (!date || !time) return NaN;
31
33
  return (/* @__PURE__ */ new Date(
32
34
  date.replace(/\./g, "-") + "T" + time.replace(/\./g, ":") + "Z"
33
35
  )).getTime();
@@ -1 +1 @@
1
- {"version":3,"file":"time.js","sources":["../../src/util/time.ts"],"sourcesContent":["import _debounce from 'lodash/debounce';\n\nexport interface DebouncedFunc<T extends (...args: any[]) => any> {\n /**\n * Call the original function, but applying the debounce rules.\n *\n * If the debounced function can be run immediately, this calls it and returns its return\n * value.\n *\n * Otherwise, it returns the return value of the last invocation, or undefined if the debounced\n * function was not invoked yet.\n */\n (...args: Parameters<T>): ReturnType<T> | undefined;\n\n /**\n * Throw away any pending invocation of the debounced function.\n */\n cancel(): void;\n\n /**\n * If there is a pending invocation of the debounced function, invoke it immediately and return\n * its return value.\n *\n * Otherwise, return the value from the last invocation, or undefined if the debounced function\n * was never invoked.\n */\n flush(): ReturnType<T> | undefined;\n}\nexport interface DebouncedFuncLeading<T extends (...args: any[]) => any>\n extends DebouncedFunc<T> {\n (...args: Parameters<T>): ReturnType<T>;\n flush(): ReturnType<T>;\n}\nexport interface DebounceSettings {\n leading?: boolean | undefined;\n maxWait?: number | undefined;\n trailing?: boolean | undefined;\n}\nexport interface DebounceSettingsLeading extends DebounceSettings {\n leading: true;\n}\nexport const debounce:\n | (<T extends (...args: any) => any>(\n func: T,\n wait: number | undefined,\n options?: DebounceSettingsLeading,\n ) => DebouncedFuncLeading<T>)\n | (<T extends (...args: any) => any>(\n func: T,\n wait?: number,\n options?: DebounceSettings,\n ) => DebouncedFunc<T>) = _debounce;\n\nconst UNIT_CONVERSION: Record<string, number> = {\n ms: 1, // milliseconds\n s: 1000, // seconds\n m: 60 * 1000, // minutes\n h: 60 * 60 * 1000, // hours\n d: 24 * 60 * 60 * 1000, // days\n w: 7 * 24 * 60 * 60 * 1000, // weeks\n};\n\n// sum duration from a string like '1d 4h 3m 25s 30ms'\nexport function parseDuration(str: string) {\n let total = 0;\n\n // split string by spaces (0m 35s) -> [0m, 35s]\n const chunks = str.split(' ');\n\n // parse each chunk\n for (const chunk of chunks) {\n // extract the duration and unit from the chunk\n const [, duration, unit] = chunk.match(/^(\\d+)(\\w+)$/) || [];\n\n // ignore invalid chunks\n if (!duration || !unit || !UNIT_CONVERSION[unit]) continue;\n\n // add to the total the duration in MS\n total += Number(duration) * UNIT_CONVERSION[unit];\n }\n\n return total;\n}\n\n// parse brickadia's time format (YYYY.MM.DD-HH-MM-SS) into a time object\nexport function parseBrickadiaTime(str: string) {\n const [date, time] = str.split('-');\n return new Date(\n date.replace(/\\./g, '-') + 'T' + time.replace(/\\./g, ':') + 'Z',\n ).getTime();\n}\n"],"names":[],"mappings":";;;AAyCO,MAAM,WAUgB;AAE7B,MAAM,kBAA0C;AAAA,EAC9C,IAAI;AAAA;AAAA,EACJ,GAAG;AAAA;AAAA,EACH,GAAG,KAAK;AAAA;AAAA,EACR,GAAG,KAAK,KAAK;AAAA;AAAA,EACb,GAAG,KAAK,KAAK,KAAK;AAAA;AAAA,EAClB,GAAG,IAAI,KAAK,KAAK,KAAK;AAAA;AACxB;AAGO,SAAS,cAAc,KAAa;AACzC,MAAI,QAAQ;AAGZ,QAAM,SAAS,IAAI,MAAM,GAAG;AAG5B,aAAW,SAAS,QAAQ;AAE1B,UAAM,CAAA,EAAG,UAAU,IAAI,IAAI,MAAM,MAAM,cAAc,KAAK,CAAA;AAG1D,QAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,gBAAgB,IAAI,EAAG;AAGlD,aAAS,OAAO,QAAQ,IAAI,gBAAgB,IAAI;AAAA,EAClD;AAEA,SAAO;AACT;AAGO,SAAS,mBAAmB,KAAa;AAC9C,QAAM,CAAC,MAAM,IAAI,IAAI,IAAI,MAAM,GAAG;AAClC,UAAO,oBAAI;AAAA,IACT,KAAK,QAAQ,OAAO,GAAG,IAAI,MAAM,KAAK,QAAQ,OAAO,GAAG,IAAI;AAAA,EAAA,GAC5D,QAAA;AACJ;;;;"}
1
+ {"version":3,"file":"time.js","sources":["../../src/util/time.ts"],"sourcesContent":["import _debounce from 'lodash/debounce';\n\nexport interface DebouncedFunc<T extends (...args: any[]) => any> {\n /**\n * Call the original function, but applying the debounce rules.\n *\n * If the debounced function can be run immediately, this calls it and returns its return\n * value.\n *\n * Otherwise, it returns the return value of the last invocation, or undefined if the debounced\n * function was not invoked yet.\n */\n (...args: Parameters<T>): ReturnType<T> | undefined;\n\n /**\n * Throw away any pending invocation of the debounced function.\n */\n cancel(): void;\n\n /**\n * If there is a pending invocation of the debounced function, invoke it immediately and return\n * its return value.\n *\n * Otherwise, return the value from the last invocation, or undefined if the debounced function\n * was never invoked.\n */\n flush(): ReturnType<T> | undefined;\n}\nexport interface DebouncedFuncLeading<T extends (...args: any[]) => any>\n extends DebouncedFunc<T> {\n (...args: Parameters<T>): ReturnType<T>;\n flush(): ReturnType<T>;\n}\nexport interface DebounceSettings {\n leading?: boolean | undefined;\n maxWait?: number | undefined;\n trailing?: boolean | undefined;\n}\nexport interface DebounceSettingsLeading extends DebounceSettings {\n leading: true;\n}\nexport const debounce:\n | (<T extends (...args: any) => any>(\n func: T,\n wait: number | undefined,\n options?: DebounceSettingsLeading,\n ) => DebouncedFuncLeading<T>)\n | (<T extends (...args: any) => any>(\n func: T,\n wait?: number,\n options?: DebounceSettings,\n ) => DebouncedFunc<T>) = _debounce;\n\nconst UNIT_CONVERSION: Record<string, number> = {\n ms: 1, // milliseconds\n s: 1000, // seconds\n m: 60 * 1000, // minutes\n h: 60 * 60 * 1000, // hours\n d: 24 * 60 * 60 * 1000, // days\n w: 7 * 24 * 60 * 60 * 1000, // weeks\n};\n\n// sum duration from a string like '1d 4h 3m 25s 30ms'\nexport function parseDuration(str: string) {\n let total = 0;\n\n // split string by spaces (0m 35s) -> [0m, 35s]\n const chunks = str.split(' ');\n\n // parse each chunk\n for (const chunk of chunks) {\n // extract the duration and unit from the chunk\n const [, duration, unit] = chunk.match(/^(\\d+)(\\w+)$/) || [];\n\n // ignore invalid chunks\n if (!duration || !unit || !UNIT_CONVERSION[unit]) continue;\n\n // add to the total the duration in MS\n total += Number(duration) * UNIT_CONVERSION[unit];\n }\n\n return total;\n}\n\n// parse brickadia's time format (YYYY.MM.DD-HH.MM.SS) into an epoch ms value.\n// returns NaN for malformed/invalid input (e.g. a missing time part, or a date\n// Brickadia has overflowed past a valid range) instead of throwing. Callers\n// must guard with Number.isFinite before persisting, since NaN bound to a\n// NOT NULL integer column is stored as NULL and violates the constraint.\nexport function parseBrickadiaTime(str: string) {\n if (typeof str !== 'string') return NaN;\n const [date, time] = str.split('-');\n if (!date || !time) return NaN;\n return new Date(\n date.replace(/\\./g, '-') + 'T' + time.replace(/\\./g, ':') + 'Z',\n ).getTime();\n}\n"],"names":[],"mappings":";;;AAyCO,MAAM,WAUgB;AAE7B,MAAM,kBAA0C;AAAA,EAC9C,IAAI;AAAA;AAAA,EACJ,GAAG;AAAA;AAAA,EACH,GAAG,KAAK;AAAA;AAAA,EACR,GAAG,KAAK,KAAK;AAAA;AAAA,EACb,GAAG,KAAK,KAAK,KAAK;AAAA;AAAA,EAClB,GAAG,IAAI,KAAK,KAAK,KAAK;AAAA;AACxB;AAGO,SAAS,cAAc,KAAa;AACzC,MAAI,QAAQ;AAGZ,QAAM,SAAS,IAAI,MAAM,GAAG;AAG5B,aAAW,SAAS,QAAQ;AAE1B,UAAM,CAAA,EAAG,UAAU,IAAI,IAAI,MAAM,MAAM,cAAc,KAAK,CAAA;AAG1D,QAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,gBAAgB,IAAI,EAAG;AAGlD,aAAS,OAAO,QAAQ,IAAI,gBAAgB,IAAI;AAAA,EAClD;AAEA,SAAO;AACT;AAOO,SAAS,mBAAmB,KAAa;AAC9C,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,QAAM,CAAC,MAAM,IAAI,IAAI,IAAI,MAAM,GAAG;AAClC,MAAI,CAAC,QAAQ,CAAC,KAAM,QAAO;AAC3B,UAAO,oBAAI;AAAA,IACT,KAAK,QAAQ,OAAO,GAAG,IAAI,MAAM,KAAK,QAAQ,OAAO,GAAG,IAAI;AAAA,EAAA,GAC5D,QAAA;AACJ;;;;"}
@@ -667,8 +667,10 @@ class Database extends EventEmitter {
667
667
  this.db.insert(schema.banHistory).values({
668
668
  banned: entry.banned,
669
669
  bannerId: entry.bannerId ?? "",
670
- created: typeof entry.created === "number" ? entry.created : 0,
671
- expires: typeof entry.expires === "number" ? entry.expires : 0,
670
+ // Number.isFinite (not typeof) so NaN from parseBrickadiaTime becomes
671
+ // 0, binding NaN to a NOT NULL integer column stores NULL and throws
672
+ created: Number.isFinite(entry.created) ? entry.created : 0,
673
+ expires: Number.isFinite(entry.expires) ? entry.expires : 0,
672
674
  reason: entry.reason ?? ""
673
675
  }).onConflictDoNothing().run();
674
676
  }
@@ -1 +1 @@
1
- {"version":3,"file":"database.js","sources":["../../../src/webserver/backend/database.ts"],"sourcesContent":["import { IServerConfig } from '@config/types';\nimport * as schema from '@/db/schema';\nimport type Omegga from '@omegga/server';\nimport { explode } from '@util/pattern';\nimport { parseBrickadiaTime } from '@util/time';\nimport bcrypt from 'bcryptjs';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport chokidar from 'chokidar';\nimport {\n and,\n count,\n desc,\n eq,\n inArray,\n lt,\n gt,\n or,\n sql,\n asc,\n type SQL,\n} from 'drizzle-orm';\nimport type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';\nimport EventEmitter from 'events';\nimport { randomUUID } from 'node:crypto';\nimport path from 'path';\nimport Calendar from './calendar';\nimport { serverEvents } from './events';\nimport {\n EMPTY_PERMISSIONS,\n RootLevel,\n type PermissionSet,\n} from './permissions';\nimport type {\n IPlayer,\n IStoreAutoRestartConfig,\n IStoreBanHistory,\n IStoreDefaultPermissions,\n IStoreKickHistory,\n IStoreRole,\n IStoreUser,\n IUserHistory,\n IWebAuthnCredential,\n} from './types';\n\n// TODO: online users graph\n// TODO: player online times\n// TODO: bricks over time graph\n// TODO: minute server status for metrics\n// TODO: chat messages per min/hour/day check\n\n// generate a punchcard (days x week)\nconst createPunchcard = (): number[][] =>\n Array.from({ length: 7 }).map(() =>\n Array.from<number>({ length: 24 }).fill(0),\n );\n\n// the database keeps track of metrics for omegga\nexport default class Database extends EventEmitter {\n options: IServerConfig;\n omegga: Omegga;\n sqlite: BetterSqlite3.Database;\n db: BetterSQLite3Database;\n defaultPermissionsCache: IStoreDefaultPermissions | null = null;\n rolesCache: (IStoreRole & { id: string })[] | null = null;\n calendar: Calendar;\n private serverInstance: { id: string; date: number } | null = null;\n\n constructor(\n options: IServerConfig,\n omegga: Omegga,\n sqlite: BetterSqlite3.Database,\n db: BetterSQLite3Database,\n ) {\n super();\n this.options = options;\n this.omegga = omegga;\n this.sqlite = sqlite;\n this.db = db;\n this.calendar = new Calendar();\n }\n\n // get the running instance id of this omegga\n async getInstanceId() {\n if (this.serverInstance) return this.serverInstance.id;\n\n const id = randomUUID();\n this.db\n .insert(schema.serverInstances)\n .values({ id, date: Date.now() })\n .run();\n this.serverInstance = { id, date: Date.now() };\n\n // create a calendar of all previous chat messages\n const dates = this.db\n .select({ created: schema.chatLogs.created })\n .from(schema.chatLogs)\n .all();\n for (const d of dates) {\n this.calendar.addDate(d.created);\n }\n\n const watcher = chokidar.watch(\n path.join(this.omegga.configPath, 'BanList.json'),\n { persistent: false },\n );\n watcher\n .on('add', () => setTimeout(this.syncBanList.bind(this), 500))\n .on('change', () =>\n setTimeout(() => {\n this.syncBanList();\n this.emit('update.bans');\n }, 500),\n );\n setTimeout(this.syncBanList.bind(this), 500);\n\n const handleKick = (kicked: IPlayer, kicker: IPlayer, reason: string) => {\n // time the event listener should expire\n const eventExpire = Date.now() + 100;\n // detect a single leave\n this.omegga.once('leave', async leavingPlayer => {\n const now = Date.now();\n // if a player leaves more than 100ms from the kick command, it's bugged\n if (now > eventExpire) return;\n if (leavingPlayer.id === kicked.id) {\n this.upsertKickHistory({\n kicked: kicked.id,\n kickerId: kicker.id,\n created: now,\n reason,\n });\n }\n });\n };\n\n // for a5 is based on the chat event\n this.omegga.on('kick', (kicked, kicker, reason) => {\n const kickedPlayer = this.omegga.findPlayerByName(kicked);\n const kickerPlayer = this.omegga.findPlayerByName(kicker);\n // couldn't find kicked or kicker player\n if (!kickedPlayer || !kickerPlayer) return;\n handleKick(kickedPlayer, kickerPlayer, reason);\n });\n\n return id;\n }\n\n // synchronize the ban list with the one in the database\n syncBanList() {\n const banList = this.omegga.getBanList()?.banList;\n if (!banList) return;\n\n const tx = this.sqlite.transaction(() => {\n // upsert all bans\n for (const banned in banList) {\n this.upsertBanHistory({\n banned,\n bannerId: banList[banned].bannerId ?? '',\n created: parseBrickadiaTime(banList[banned].created) ?? 0,\n expires: parseBrickadiaTime(banList[banned].expires) ?? 0,\n reason: banList[banned].reason,\n });\n }\n });\n tx();\n }\n\n // determine if this user would be the first user (admin user)\n async isFirstUser() {\n const result = this.db.select({ count: count() }).from(schema.users).get();\n return (result?.count ?? 0) === 0;\n }\n\n // hash a password\n async hash(password: string) {\n return await bcrypt.hash(password, 10);\n }\n\n // create an admin user account, username can be blank (no password)\n async createAdminUser(username: string, password: string) {\n const h = await this.hash(password);\n const id = randomUUID();\n // create an owner user\n this.db\n .insert(schema.users)\n .values({\n id,\n created: Date.now(),\n lastOnline: Date.now(),\n username,\n hash: h,\n isOwner: true,\n roles: [],\n permissions: EMPTY_PERMISSIONS,\n totpEnabled: false,\n passkeys: [],\n recoveryCodes: [],\n playerId: '',\n })\n .run();\n return this.findUserById(id);\n }\n\n // determine if a user with this name exists\n async userExists(username: string) {\n const result = this.db\n .select({ count: count() })\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n return (result?.count ?? 0) > 0;\n }\n\n // create a regular user account\n async createUser(username: string, password: string) {\n if (await this.userExists(username)) throw new Error('user already exists');\n const h = await this.hash(password);\n const id = randomUUID();\n this.db\n .insert(schema.users)\n .values({\n id,\n created: Date.now(),\n lastOnline: 0,\n username,\n hash: h,\n isOwner: false,\n roles: [],\n permissions: EMPTY_PERMISSIONS,\n totpEnabled: false,\n passkeys: [],\n recoveryCodes: [],\n playerId: '',\n })\n .run();\n return this.findUserById(id);\n }\n\n // set a user's password\n async userPasswd(username: string, password: string) {\n const h = await this.hash(password);\n if (!(await this.userExists(username)))\n throw new Error('user does not exist');\n this.db\n .update(schema.users)\n .set({ hash: h })\n .where(eq(schema.users.username, username))\n .run();\n }\n\n async banUser(username: string, banned: boolean) {\n this.db\n .update(schema.users)\n .set({ isBanned: banned })\n .where(eq(schema.users.username, username))\n .run();\n if (banned) serverEvents.emit('userInvalidated', username);\n return 1;\n }\n\n async deleteUser(username: string) {\n this.db\n .delete(schema.users)\n .where(eq(schema.users.username, username))\n .run();\n serverEvents.emit('userInvalidated', username);\n return 1;\n }\n\n async setUserPermissions(username: string, permissions: PermissionSet) {\n this.db\n .update(schema.users)\n .set({ permissions: permissions })\n .where(eq(schema.users.username, username))\n .run();\n }\n\n async getDefaultPermissions(): Promise<IStoreDefaultPermissions> {\n if (this.defaultPermissionsCache) return this.defaultPermissionsCache;\n const row = this.db\n .select()\n .from(schema.serverConfig)\n .where(eq(schema.serverConfig.key, 'defaultPermissions'))\n .get();\n if (!row) {\n const defaults: IStoreDefaultPermissions = {\n type: 'defaultPermissions',\n root: RootLevel.Read,\n domains: {},\n scopes: {},\n };\n this.db\n .insert(schema.serverConfig)\n .values({ key: 'defaultPermissions', value: defaults })\n .run();\n this.defaultPermissionsCache = defaults;\n return defaults;\n }\n const val = row.value as any;\n const result: IStoreDefaultPermissions = {\n type: 'defaultPermissions',\n root: val.root ?? RootLevel.Off,\n domains: val.domains ?? {},\n scopes: val.scopes ?? {},\n };\n this.defaultPermissionsCache = result;\n return result;\n }\n\n async setDefaultPermissions(\n permissions: Omit<IStoreDefaultPermissions, 'type'>,\n ) {\n const value = {\n root: permissions.root,\n domains: permissions.domains,\n scopes: permissions.scopes,\n };\n this.db\n .insert(schema.serverConfig)\n .values({ key: 'defaultPermissions', value })\n .onConflictDoUpdate({\n target: schema.serverConfig.key,\n set: { value },\n })\n .run();\n this.defaultPermissionsCache = null;\n }\n\n // get a user from credentials\n async authUser(username: string, password: string) {\n const row = this.db\n .select()\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n // user not found\n if (!row || row.isBanned) return null;\n // make sure the user's password hash is valid\n if (await bcrypt.compare(password, row.hash)) {\n this.db\n .update(schema.users)\n .set({ lastOnline: Date.now() })\n .where(eq(schema.users.id, row.id))\n .run();\n // update last online status\n return this._toStoreUser(row);\n }\n // wrong password\n return null;\n }\n\n // find a user by object id\n async findUserById(id: string) {\n const row = this.db\n .select()\n .from(schema.users)\n .where(\n or(\n // the owner has no username, so everyone is the owner\n and(eq(schema.users.username, ''), eq(schema.users.isOwner, true)),\n // the user exists and has an id\n eq(schema.users.id, id),\n ),\n )\n .get();\n if (!row) return null;\n return this._toStoreUser(row);\n }\n\n async findUserByUsername(\n username: string,\n ): Promise<(IStoreUser & { id: string }) | null> {\n const row = this.db\n .select()\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n if (!row) return null;\n return this._toStoreUser(row);\n }\n\n async findUserByPasskeyId(credentialId: string) {\n const rows = this.db\n .select()\n .from(schema.users)\n .where(\n sql`${schema.users.passkeys} LIKE ${'%\"id\":\"' + credentialId + '\"%'}`,\n )\n .all();\n for (const row of rows) {\n const passkeys = (row.passkeys ?? []) as IWebAuthnCredential[];\n if (passkeys.some(p => p.id === credentialId)) {\n return this._toStoreUser(row);\n }\n }\n return null;\n }\n\n updateUserLastOnline(id: string) {\n this.db\n .update(schema.users)\n .set({ lastOnline: Date.now() })\n .where(eq(schema.users.id, id))\n .run();\n }\n\n updateOwnerCredentials(id: string, username: string, hash: string) {\n this.db\n .update(schema.users)\n .set({ username, hash })\n .where(eq(schema.users.id, id))\n .run();\n }\n\n addUserRole(userId: string, roleId: string) {\n const row = this.db\n .select({ roles: schema.users.roles })\n .from(schema.users)\n .where(eq(schema.users.id, userId))\n .get();\n if (!row) return;\n const roles = (row.roles ?? []) as string[];\n if (!roles.includes(roleId)) {\n roles.push(roleId);\n this.db\n .update(schema.users)\n .set({ roles })\n .where(eq(schema.users.id, userId))\n .run();\n }\n }\n\n removeUserRole(userId: string, roleId: string) {\n const row = this.db\n .select({ roles: schema.users.roles })\n .from(schema.users)\n .where(eq(schema.users.id, userId))\n .get();\n if (!row) return;\n const roles = ((row.roles ?? []) as string[]).filter(r => r !== roleId);\n this.db\n .update(schema.users)\n .set({ roles })\n .where(eq(schema.users.id, userId))\n .run();\n }\n\n async setUserTotp(username: string, secret: string, enabled: boolean) {\n this.db\n .update(schema.users)\n .set({ totpSecret: secret, totpEnabled: enabled })\n .where(eq(schema.users.username, username))\n .run();\n }\n\n async disableUserTotp(username: string) {\n this.db\n .update(schema.users)\n .set({ totpEnabled: false, totpSecret: null })\n .where(eq(schema.users.username, username))\n .run();\n }\n\n async addPasskey(username: string, credential: IWebAuthnCredential) {\n const row = this.db\n .select({ id: schema.users.id, passkeys: schema.users.passkeys })\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n if (!row) return;\n const passkeys = [\n ...((row.passkeys ?? []) as IWebAuthnCredential[]),\n credential,\n ];\n this.db\n .update(schema.users)\n .set({ passkeys })\n .where(eq(schema.users.id, row.id))\n .run();\n }\n\n async removePasskey(username: string, credentialId: string) {\n const row = this.db\n .select({ id: schema.users.id, passkeys: schema.users.passkeys })\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n if (!row) return;\n const passkeys = ((row.passkeys ?? []) as IWebAuthnCredential[]).filter(\n p => p.id !== credentialId,\n );\n this.db\n .update(schema.users)\n .set({ passkeys })\n .where(eq(schema.users.id, row.id))\n .run();\n }\n\n async updatePasskeyCounter(\n username: string,\n credentialId: string,\n counter: number,\n ) {\n const row = this.db\n .select({ id: schema.users.id, passkeys: schema.users.passkeys })\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n if (!row) return;\n const passkeys = ((row.passkeys ?? []) as IWebAuthnCredential[]).map(p =>\n p.id === credentialId ? { ...p, counter, lastUsed: Date.now() } : p,\n );\n this.db\n .update(schema.users)\n .set({ passkeys })\n .where(eq(schema.users.id, row.id))\n .run();\n }\n\n async setRecoveryCodes(username: string, hashedCodes: string[]) {\n this.db\n .update(schema.users)\n .set({ recoveryCodes: hashedCodes })\n .where(eq(schema.users.username, username))\n .run();\n }\n\n async removeRecoveryCode(username: string, hashedCode: string) {\n const row = this.db\n .select({\n id: schema.users.id,\n recoveryCodes: schema.users.recoveryCodes,\n })\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n if (!row) return;\n const codes = ((row.recoveryCodes ?? []) as string[]).filter(\n c => c !== hashedCode,\n );\n this.db\n .update(schema.users)\n .set({ recoveryCodes: codes })\n .where(eq(schema.users.id, row.id))\n .run();\n }\n\n async resetUserMfa(username: string) {\n this.db\n .update(schema.users)\n .set({\n totpEnabled: false,\n totpSecret: null,\n passkeys: [],\n recoveryCodes: [],\n })\n .where(eq(schema.users.username, username))\n .run();\n }\n\n async getAllRoles(): Promise<(IStoreRole & { id: string })[]> {\n if (this.rolesCache) return this.rolesCache;\n const rows = this.db\n .select()\n .from(schema.webRoles)\n .orderBy(desc(schema.webRoles.order))\n .all();\n const roles = rows.map(r => ({\n type: 'webRole' as const,\n id: r.id,\n name: r.name,\n description: r.description,\n order: r.order,\n permissions: r.permissions as PermissionSet,\n }));\n this.rolesCache = roles;\n return roles;\n }\n\n invalidateRolesCache() {\n this.rolesCache = null;\n this.defaultPermissionsCache = null;\n }\n\n async getRole(id: string): Promise<(IStoreRole & { id: string }) | null> {\n const row = this.db\n .select()\n .from(schema.webRoles)\n .where(eq(schema.webRoles.id, id))\n .get();\n if (!row) return null;\n return {\n type: 'webRole',\n id: row.id,\n name: row.name,\n description: row.description,\n order: row.order,\n permissions: row.permissions as PermissionSet,\n };\n }\n\n async createRole(\n name: string,\n description: string,\n permissions: PermissionSet,\n ): Promise<IStoreRole & { id: string }> {\n const id = randomUUID();\n const tx = this.sqlite.transaction(() => {\n this.db\n .update(schema.webRoles)\n .set({ order: sql`${schema.webRoles.order} + 1` })\n .run();\n this.db\n .insert(schema.webRoles)\n .values({\n id,\n name,\n description,\n order: 1,\n permissions: permissions,\n })\n .run();\n });\n tx();\n this.invalidateRolesCache();\n return {\n type: 'webRole',\n id,\n name,\n description,\n order: 1,\n permissions: permissions,\n };\n }\n\n async updateRole(\n id: string,\n updates: Partial<Pick<IStoreRole, 'name' | 'description' | 'order'>> & {\n permissions?: PermissionSet;\n },\n ): Promise<void> {\n const $set: Record<string, any> = {};\n if (updates.name !== undefined) $set.name = updates.name;\n if (updates.description !== undefined)\n $set.description = updates.description;\n if (updates.order !== undefined) $set.order = updates.order;\n if (updates.permissions !== undefined)\n $set.permissions = updates.permissions;\n if (Object.keys($set).length > 0) {\n this.db\n .update(schema.webRoles)\n .set($set)\n .where(eq(schema.webRoles.id, id))\n .run();\n }\n this.invalidateRolesCache();\n }\n\n async deleteRole(id: string): Promise<string> {\n const role = await this.getRole(id);\n if (!role) return 'role not found';\n\n const tx = this.sqlite.transaction(() => {\n this.db.delete(schema.webRoles).where(eq(schema.webRoles.id, id)).run();\n const usersWithRole = this.db\n .select({ id: schema.users.id, roles: schema.users.roles })\n .from(schema.users)\n .all();\n for (const u of usersWithRole) {\n const roles = ((u.roles ?? []) as string[]).filter(r => r !== id);\n if (roles.length !== ((u.roles ?? []) as string[]).length) {\n this.db\n .update(schema.users)\n .set({ roles })\n .where(eq(schema.users.id, u.id))\n .run();\n }\n }\n });\n tx();\n this.invalidateRolesCache();\n return '';\n }\n\n async reorderRoles(entries: { id: string; order: number }[]): Promise<void> {\n const tx = this.sqlite.transaction(() => {\n for (const { id, order } of entries) {\n this.db\n .update(schema.webRoles)\n .set({ order })\n .where(eq(schema.webRoles.id, id))\n .run();\n }\n });\n tx();\n this.invalidateRolesCache();\n }\n\n async getUserRolePermissions(user: IStoreUser): Promise<PermissionSet[]> {\n const defaults = await this.getDefaultPermissions();\n const defaultPerms: PermissionSet = {\n root: defaults.root,\n domains: defaults.domains,\n scopes: defaults.scopes,\n };\n const allRoles = await this.getAllRoles();\n const userRoles = allRoles.filter(r => user.roles.includes(r.id));\n return [defaultPerms, ...userRoles.map(r => r.permissions)];\n }\n\n async getUserAssignedRoles(\n user: IStoreUser,\n ): Promise<(IStoreRole & { id: string })[]> {\n const allRoles = await this.getAllRoles();\n return allRoles.filter(r => user.roles.includes(r.id));\n }\n\n // add a chat message to the chat log store\n async addChatLog(\n action: 'msg' | 'server' | 'leave' | 'join',\n user: IPlayer,\n message?: string,\n ) {\n this.calendar.addDate(Date.now());\n const row = this.db\n .insert(schema.chatLogs)\n .values({\n created: Date.now(),\n instanceId: await this.getInstanceId(),\n action,\n user,\n ...(message ? { message } : {}),\n })\n .returning()\n .get();\n return {\n type: 'chat' as const,\n _id: String(row.id),\n id: row.id,\n created: row.created,\n instanceId: row.instanceId,\n action: row.action,\n user: row.user,\n message: row.message,\n };\n }\n\n // get recent chat activity\n async getChats({\n count: limit = 50,\n sameServer,\n before,\n after,\n }: {\n count?: number;\n sameServer?: boolean;\n before?: number;\n after?: number;\n } = {}) {\n const conditions = [];\n if (sameServer) {\n conditions.push(\n eq(schema.chatLogs.instanceId, await this.getInstanceId()),\n );\n }\n if (before) {\n conditions.push(lt(schema.chatLogs.created, before));\n } else if (after) {\n conditions.push(gt(schema.chatLogs.created, after));\n } else {\n conditions.push(lt(schema.chatLogs.created, Date.now()));\n }\n\n const orderDir = !before && after ? asc : desc;\n const rows = this.db\n .select()\n .from(schema.chatLogs)\n .where(conditions.length > 1 ? and(...conditions) : conditions[0])\n .orderBy(orderDir(schema.chatLogs.created))\n .limit(limit)\n .all();\n\n return rows.map(r => ({\n type: 'chat' as const,\n _id: String(r.id),\n created: r.created,\n instanceId: r.instanceId,\n action: r.action,\n user: r.user,\n message: r.message,\n }));\n }\n\n // get paginated players\n async getPlayers({\n count: limit = 50,\n search = '',\n page = 0,\n sort = 'name',\n direction = 1,\n limitId = undefined,\n }: {\n count?: number;\n search?: string;\n page?: number;\n sort?: string;\n direction?: number;\n limitId?: string[];\n } = {}) {\n const pattern = search.length > 0 ? explode(search) : null;\n const patternSource = pattern?.source;\n\n // an empty filter (e.g. \"banned\" with no active bans) matches nothing\n if (limitId && limitId.length === 0)\n return { pages: 0, total: 0, players: [] };\n\n const conditions: SQL[] = [];\n\n if (limitId) {\n conditions.push(inArray(schema.playerHistory.id, limitId));\n }\n\n if (patternSource) {\n // match name history values via json_each so the pattern can't hit\n // JSON keys or date timestamps\n conditions.push(sql`(\n ${schema.playerHistory.id} = ${search}\n OR ${schema.playerHistory.name} REGEXP ${patternSource}\n OR ${schema.playerHistory.displayName} REGEXP ${patternSource}\n OR EXISTS (\n SELECT 1 FROM json_each(${schema.playerHistory.nameHistory})\n WHERE json_each.type = 'object'\n AND (json_extract(json_each.value, '$.name') REGEXP ${patternSource}\n OR json_extract(json_each.value, '$.displayName') REGEXP ${patternSource})\n )\n )`);\n }\n\n const baseWhere = conditions.length > 0 ? and(...conditions) : undefined;\n\n const sortCol =\n sort === 'lastSeen'\n ? schema.playerHistory.lastSeen\n : sort === 'created'\n ? schema.playerHistory.created\n : sort === 'heartbeats'\n ? schema.playerHistory.heartbeats\n : sort === 'sessions'\n ? schema.playerHistory.sessions\n : sql`${schema.playerHistory.name} COLLATE NOCASE`;\n const orderDir = direction === 1 ? asc : desc;\n\n const totalResult = this.db\n .select({ count: count() })\n .from(schema.playerHistory)\n .where(baseWhere)\n .get();\n const players = this.db\n .select()\n .from(schema.playerHistory)\n .where(baseWhere)\n .orderBy(orderDir(sortCol))\n .limit(limit)\n .offset(limit * page)\n .all();\n\n const total = totalResult?.count ?? 0;\n const result = players.map(p => this._toUserHistory(p));\n\n // exact match detection\n if (search.length > 0 && page === 0) {\n const exactRows = this.db\n .select()\n .from(schema.playerHistory)\n .where(\n or(\n eq(schema.playerHistory.id, search),\n eq(schema.playerHistory.name, search),\n eq(schema.playerHistory.displayName, search),\n ),\n )\n .all();\n if (exactRows.length > 0) {\n for (const res of exactRows) {\n const idx = result.findIndex(p => p.id === res.id);\n if (idx >= 0) result.splice(idx, 1);\n }\n result.splice(0, 0, ...exactRows.map(r => this._toUserHistory(r)));\n }\n }\n\n return { pages: Math.ceil(total / limit), total, players: result };\n }\n\n // get paginated users\n async getUsers({\n count: limit = 50,\n search = '',\n page = 0,\n sort = 'name',\n direction = 1,\n } = {}) {\n const pattern = search.length > 0 ? explode(search) : null;\n\n let baseWhere = sql`1=1`;\n if (pattern) {\n baseWhere = sql`(\n ${schema.users.playerId} = ${search}\n OR ${schema.users.username} REGEXP ${pattern.source}\n )`;\n }\n\n const sortCol =\n sort === 'lastOnline'\n ? schema.users.lastOnline\n : sort === 'created'\n ? schema.users.created\n : sql`${schema.users.username} COLLATE NOCASE`;\n const orderDir = direction === 1 ? asc : desc;\n\n const totalResult = this.db\n .select({ count: count() })\n .from(schema.users)\n .where(baseWhere)\n .get();\n const users = this.db\n .select()\n .from(schema.users)\n .where(baseWhere)\n .orderBy(orderDir(sortCol))\n .limit(limit)\n .offset(limit * page)\n .all();\n\n return {\n pages: Math.ceil((totalResult?.count ?? 0) / limit),\n total: totalResult?.count ?? 0,\n users: users.map(u => this._toStoreUser(u)),\n };\n }\n\n // get an individual player, ban and kick history, and notes\n async getPlayer(id: string) {\n const player = this.db\n .select()\n .from(schema.playerHistory)\n .where(eq(schema.playerHistory.id, id))\n .get();\n if (!player) return null;\n\n const banHist = this.db\n .select()\n .from(schema.banHistory)\n .where(eq(schema.banHistory.banned, id))\n .orderBy(desc(schema.banHistory.created))\n .limit(25)\n .all();\n\n const kickHist = this.db\n .select()\n .from(schema.kickHistory)\n .where(eq(schema.kickHistory.kicked, id))\n .orderBy(desc(schema.kickHistory.created))\n .limit(25)\n .all();\n\n const noteRows = this.db\n .select()\n .from(schema.playerNotes)\n .where(eq(schema.playerNotes.playerId, id))\n .limit(25)\n .all();\n\n return {\n ...this._toUserHistory(player),\n banHistory: banHist.map(b => ({\n type: 'banHistory' as const,\n _id: String(b.id),\n banned: b.banned,\n bannerId: b.bannerId,\n created: b.created,\n expires: b.expires,\n reason: b.reason,\n })),\n kickHistory: kickHist.map(k => ({\n type: 'kickHistory' as const,\n _id: String(k.id),\n kicked: k.kicked,\n kickerId: k.kickerId,\n created: k.created,\n reason: k.reason,\n })),\n notes: noteRows.map(n => ({\n type: 'note' as const,\n _id: String(n.id),\n id: n.playerId,\n note: n.note,\n })),\n };\n }\n\n // add a user to the visit history, returns true if this is a first visit\n async addVisit(user: Required<IPlayer>) {\n const now = Date.now();\n // resolve the instance id before opening the transaction — the tx callback\n // must stay synchronous, and this is the only await in the method\n const instanceId = await this.getInstanceId();\n\n // read-modify-write the row atomically so two near-simultaneous joins for\n // the same id can't both insert (PK conflict) or clobber each other's\n // nameHistory/counter updates from a shared stale snapshot\n const visit = this.sqlite.transaction(() => {\n const existing = this.db\n .select()\n .from(schema.playerHistory)\n .where(eq(schema.playerHistory.id, user.id))\n .get();\n\n if (!existing) {\n this.db\n .insert(schema.playerHistory)\n .values({\n id: user.id,\n name: user.name,\n displayName: user.displayName,\n nameHistory: [\n { name: user.name, displayName: user.displayName, date: now },\n ],\n ips: [],\n created: now,\n lastSeen: now,\n lastInstanceId: instanceId,\n heartbeats: 0,\n sessions: 1,\n instances: 1,\n })\n .run();\n return true;\n }\n\n const nameHistory = (existing.nameHistory ?? []) as {\n name: string;\n displayName: string;\n date: number;\n }[];\n const hasName = nameHistory.some(\n h => h.name === user.name && h.displayName === user.displayName,\n );\n if (!hasName) {\n nameHistory.push({\n name: user.name,\n displayName: user.displayName,\n date: now,\n });\n }\n\n this.db\n .update(schema.playerHistory)\n .set({\n lastSeen: now,\n name: user.name,\n displayName: user.displayName,\n lastInstanceId: instanceId,\n nameHistory,\n sessions: sql`${schema.playerHistory.sessions} + ${existing.lastSeen < now - 60 * 60 * 1000 ? 1 : 0}`,\n instances: sql`${schema.playerHistory.instances} + ${existing.lastInstanceId !== instanceId ? 1 : 0}`,\n })\n .where(eq(schema.playerHistory.id, existing.id))\n .run();\n return false;\n });\n\n return visit();\n }\n\n // use data from minutely heartbeats to fuel metrics\n async addHeartbeat(data: {\n bricks: number;\n players: string[];\n ips: Record<string, string>;\n }) {\n const now = Date.now();\n const tx = this.sqlite.transaction(() => {\n this.db\n .insert(schema.heartbeats)\n .values({\n created: now,\n bricks: data.bricks,\n players: data.players,\n })\n .run();\n\n if (data.players.length > 0) {\n // one bulk UPDATE for the shared lastSeen/heartbeats bump\n this.db\n .update(schema.playerHistory)\n .set({\n lastSeen: now,\n heartbeats: sql`${schema.playerHistory.heartbeats} + 1`,\n })\n .where(inArray(schema.playerHistory.id, data.players))\n .run();\n\n const players = this.db\n .select()\n .from(schema.playerHistory)\n .where(inArray(schema.playerHistory.id, data.players))\n .all();\n\n for (const p of players) {\n const ip = data.ips[p.id];\n if (ip && !((p.ips ?? []) as string[]).includes(ip)) {\n const ips = [...((p.ips ?? []) as string[]), ip];\n this.db\n .update(schema.playerHistory)\n .set({ ips })\n .where(eq(schema.playerHistory.id, p.id))\n .run();\n }\n }\n }\n });\n tx();\n }\n\n upsertBanHistory(entry: Omit<IStoreBanHistory, 'type'>) {\n this.db\n .insert(schema.banHistory)\n .values({\n banned: entry.banned,\n bannerId: entry.bannerId ?? '',\n created: typeof entry.created === 'number' ? entry.created : 0,\n expires: typeof entry.expires === 'number' ? entry.expires : 0,\n reason: entry.reason ?? '',\n })\n .onConflictDoNothing()\n .run();\n }\n\n upsertKickHistory(entry: Omit<IStoreKickHistory, 'type'>) {\n this.db\n .insert(schema.kickHistory)\n .values({\n kicked: entry.kicked,\n kickerId: entry.kickerId ?? '',\n created: entry.created ?? 0,\n reason: entry.reason ?? '',\n })\n .onConflictDoNothing()\n .run();\n }\n\n async getAutoRestartConfig(): Promise<IStoreAutoRestartConfig> {\n const row = this.db\n .select()\n .from(schema.serverConfig)\n .where(eq(schema.serverConfig.key, 'autoRestartConfig'))\n .get();\n if (!row) {\n const config: IStoreAutoRestartConfig = {\n type: 'autoRestartConfig',\n maxUptime: 48,\n maxUptimeEnabled: false,\n emptyUptime: 24,\n emptyUptimeEnabled: false,\n dailyHour: 2,\n dailyHourEnabled: false,\n autoUpdateEnabled: true,\n autoUpdateIntervalMins: 60,\n announcementEnabled: true,\n playersEnabled: true,\n saveWorld: true,\n crashRestartEnabled: true,\n };\n this.db\n .insert(schema.serverConfig)\n .values({ key: 'autoRestartConfig', value: config })\n .run();\n return config;\n }\n const config = row.value as IStoreAutoRestartConfig;\n config.type = 'autoRestartConfig';\n config.autoUpdateEnabled ??= true;\n config.autoUpdateIntervalMins ??= 60;\n config.saveWorld ??= true;\n config.crashRestartEnabled ??= true;\n return config;\n }\n\n async setAutoRestartConfig(config: IStoreAutoRestartConfig): Promise<void> {\n config.maxUptime = Math.round(Math.max(1, Math.min(config.maxUptime, 168)));\n config.emptyUptime = Math.round(\n Math.max(1, Math.min(config.emptyUptime, 168)),\n );\n config.dailyHour = Math.round(Math.max(0, Math.min(config.dailyHour, 23)));\n config.autoUpdateIntervalMins = Math.round(\n Math.max(10, Math.min(config.autoUpdateIntervalMins ?? 60, Infinity)),\n );\n config.autoUpdateEnabled ??= true;\n this.db\n .insert(schema.serverConfig)\n .values({ key: 'autoRestartConfig', value: config })\n .onConflictDoUpdate({\n target: schema.serverConfig.key,\n set: { value: config },\n })\n .run();\n }\n\n // update player online-time punchcard\n async updatePlayerPunchcard(numNewPlayers: number) {\n const now = new Date();\n const time = now.getTime();\n const hour = now.getUTCHours();\n const day = now.getUTCDay();\n const month = now.getUTCMonth();\n const year = now.getUTCFullYear();\n\n const card = this.db\n .select()\n .from(schema.punchcards)\n .where(\n and(\n eq(schema.punchcards.kind, 'playerCount'),\n eq(schema.punchcards.month, month),\n eq(schema.punchcards.year, year),\n ),\n )\n .get();\n\n if (!card) {\n const punchcard = createPunchcard();\n punchcard[day][hour] = numNewPlayers;\n this.db\n .insert(schema.punchcards)\n .values({\n kind: 'playerCount',\n created: time,\n updated: time,\n month,\n year,\n punchcard,\n })\n .run();\n } else {\n const punchcard = card.punchcard as number[][];\n punchcard[day][hour] += numNewPlayers;\n this.db\n .update(schema.punchcards)\n .set({ punchcard, updated: time })\n .where(eq(schema.punchcards.id, card.id))\n .run();\n }\n }\n\n private _toStoreUser(\n row: typeof schema.users.$inferSelect,\n ): IStoreUser & { id: string; _id: string } {\n const user: IStoreUser & { id: string; _id: string } = {\n type: 'user',\n id: row.id,\n _id: row.id,\n created: row.created,\n lastOnline: row.lastOnline,\n username: row.username,\n hash: row.hash,\n isOwner: row.isOwner,\n roles: (row.roles ?? []) as string[],\n playerId: row.playerId,\n isBanned: row.isBanned,\n permissions:\n (row.permissions as PermissionSet | null) ?? EMPTY_PERMISSIONS,\n totpSecret: row.totpSecret ?? undefined,\n totpEnabled: row.totpEnabled,\n passkeys: (row.passkeys ?? []) as IWebAuthnCredential[],\n recoveryCodes: (row.recoveryCodes ?? []) as string[],\n };\n return user;\n }\n\n private _toUserHistory(\n row: typeof schema.playerHistory.$inferSelect,\n ): IUserHistory & { _id: string } {\n return {\n type: 'userHistory',\n _id: row.id,\n id: row.id,\n name: row.name,\n displayName: row.displayName,\n nameHistory: (row.nameHistory ?? []) as {\n displayName: string;\n name: string;\n date: number;\n }[],\n ips: (row.ips ?? []) as string[],\n created: row.created,\n lastSeen: row.lastSeen,\n lastInstanceId: row.lastInstanceId,\n heartbeats: row.heartbeats,\n sessions: row.sessions,\n instances: row.instances,\n };\n }\n}\n"],"names":["Calendar","randomUUID","schema.serverInstances","schema.chatLogs","parseBrickadiaTime","count","schema.users","EMPTY_PERMISSIONS","eq","serverEvents","permissions","schema.serverConfig","RootLevel","or","and","sql","schema.webRoles","desc","lt","gt","asc","pattern","explode","inArray","schema.playerHistory","schema.banHistory","schema.kickHistory","schema.playerNotes","schema.heartbeats","config","time","schema.punchcards"],"mappings":"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"}
1
+ {"version":3,"file":"database.js","sources":["../../../src/webserver/backend/database.ts"],"sourcesContent":["import { IServerConfig } from '@config/types';\nimport * as schema from '@/db/schema';\nimport type Omegga from '@omegga/server';\nimport { explode } from '@util/pattern';\nimport { parseBrickadiaTime } from '@util/time';\nimport bcrypt from 'bcryptjs';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport chokidar from 'chokidar';\nimport {\n and,\n count,\n desc,\n eq,\n inArray,\n lt,\n gt,\n or,\n sql,\n asc,\n type SQL,\n} from 'drizzle-orm';\nimport type { BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';\nimport EventEmitter from 'events';\nimport { randomUUID } from 'node:crypto';\nimport path from 'path';\nimport Calendar from './calendar';\nimport { serverEvents } from './events';\nimport {\n EMPTY_PERMISSIONS,\n RootLevel,\n type PermissionSet,\n} from './permissions';\nimport type {\n IPlayer,\n IStoreAutoRestartConfig,\n IStoreBanHistory,\n IStoreDefaultPermissions,\n IStoreKickHistory,\n IStoreRole,\n IStoreUser,\n IUserHistory,\n IWebAuthnCredential,\n} from './types';\n\n// TODO: online users graph\n// TODO: player online times\n// TODO: bricks over time graph\n// TODO: minute server status for metrics\n// TODO: chat messages per min/hour/day check\n\n// generate a punchcard (days x week)\nconst createPunchcard = (): number[][] =>\n Array.from({ length: 7 }).map(() =>\n Array.from<number>({ length: 24 }).fill(0),\n );\n\n// the database keeps track of metrics for omegga\nexport default class Database extends EventEmitter {\n options: IServerConfig;\n omegga: Omegga;\n sqlite: BetterSqlite3.Database;\n db: BetterSQLite3Database;\n defaultPermissionsCache: IStoreDefaultPermissions | null = null;\n rolesCache: (IStoreRole & { id: string })[] | null = null;\n calendar: Calendar;\n private serverInstance: { id: string; date: number } | null = null;\n\n constructor(\n options: IServerConfig,\n omegga: Omegga,\n sqlite: BetterSqlite3.Database,\n db: BetterSQLite3Database,\n ) {\n super();\n this.options = options;\n this.omegga = omegga;\n this.sqlite = sqlite;\n this.db = db;\n this.calendar = new Calendar();\n }\n\n // get the running instance id of this omegga\n async getInstanceId() {\n if (this.serverInstance) return this.serverInstance.id;\n\n const id = randomUUID();\n this.db\n .insert(schema.serverInstances)\n .values({ id, date: Date.now() })\n .run();\n this.serverInstance = { id, date: Date.now() };\n\n // create a calendar of all previous chat messages\n const dates = this.db\n .select({ created: schema.chatLogs.created })\n .from(schema.chatLogs)\n .all();\n for (const d of dates) {\n this.calendar.addDate(d.created);\n }\n\n const watcher = chokidar.watch(\n path.join(this.omegga.configPath, 'BanList.json'),\n { persistent: false },\n );\n watcher\n .on('add', () => setTimeout(this.syncBanList.bind(this), 500))\n .on('change', () =>\n setTimeout(() => {\n this.syncBanList();\n this.emit('update.bans');\n }, 500),\n );\n setTimeout(this.syncBanList.bind(this), 500);\n\n const handleKick = (kicked: IPlayer, kicker: IPlayer, reason: string) => {\n // time the event listener should expire\n const eventExpire = Date.now() + 100;\n // detect a single leave\n this.omegga.once('leave', async leavingPlayer => {\n const now = Date.now();\n // if a player leaves more than 100ms from the kick command, it's bugged\n if (now > eventExpire) return;\n if (leavingPlayer.id === kicked.id) {\n this.upsertKickHistory({\n kicked: kicked.id,\n kickerId: kicker.id,\n created: now,\n reason,\n });\n }\n });\n };\n\n // for a5 is based on the chat event\n this.omegga.on('kick', (kicked, kicker, reason) => {\n const kickedPlayer = this.omegga.findPlayerByName(kicked);\n const kickerPlayer = this.omegga.findPlayerByName(kicker);\n // couldn't find kicked or kicker player\n if (!kickedPlayer || !kickerPlayer) return;\n handleKick(kickedPlayer, kickerPlayer, reason);\n });\n\n return id;\n }\n\n // synchronize the ban list with the one in the database\n syncBanList() {\n const banList = this.omegga.getBanList()?.banList;\n if (!banList) return;\n\n const tx = this.sqlite.transaction(() => {\n // upsert all bans\n for (const banned in banList) {\n this.upsertBanHistory({\n banned,\n bannerId: banList[banned].bannerId ?? '',\n created: parseBrickadiaTime(banList[banned].created) ?? 0,\n expires: parseBrickadiaTime(banList[banned].expires) ?? 0,\n reason: banList[banned].reason,\n });\n }\n });\n tx();\n }\n\n // determine if this user would be the first user (admin user)\n async isFirstUser() {\n const result = this.db.select({ count: count() }).from(schema.users).get();\n return (result?.count ?? 0) === 0;\n }\n\n // hash a password\n async hash(password: string) {\n return await bcrypt.hash(password, 10);\n }\n\n // create an admin user account, username can be blank (no password)\n async createAdminUser(username: string, password: string) {\n const h = await this.hash(password);\n const id = randomUUID();\n // create an owner user\n this.db\n .insert(schema.users)\n .values({\n id,\n created: Date.now(),\n lastOnline: Date.now(),\n username,\n hash: h,\n isOwner: true,\n roles: [],\n permissions: EMPTY_PERMISSIONS,\n totpEnabled: false,\n passkeys: [],\n recoveryCodes: [],\n playerId: '',\n })\n .run();\n return this.findUserById(id);\n }\n\n // determine if a user with this name exists\n async userExists(username: string) {\n const result = this.db\n .select({ count: count() })\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n return (result?.count ?? 0) > 0;\n }\n\n // create a regular user account\n async createUser(username: string, password: string) {\n if (await this.userExists(username)) throw new Error('user already exists');\n const h = await this.hash(password);\n const id = randomUUID();\n this.db\n .insert(schema.users)\n .values({\n id,\n created: Date.now(),\n lastOnline: 0,\n username,\n hash: h,\n isOwner: false,\n roles: [],\n permissions: EMPTY_PERMISSIONS,\n totpEnabled: false,\n passkeys: [],\n recoveryCodes: [],\n playerId: '',\n })\n .run();\n return this.findUserById(id);\n }\n\n // set a user's password\n async userPasswd(username: string, password: string) {\n const h = await this.hash(password);\n if (!(await this.userExists(username)))\n throw new Error('user does not exist');\n this.db\n .update(schema.users)\n .set({ hash: h })\n .where(eq(schema.users.username, username))\n .run();\n }\n\n async banUser(username: string, banned: boolean) {\n this.db\n .update(schema.users)\n .set({ isBanned: banned })\n .where(eq(schema.users.username, username))\n .run();\n if (banned) serverEvents.emit('userInvalidated', username);\n return 1;\n }\n\n async deleteUser(username: string) {\n this.db\n .delete(schema.users)\n .where(eq(schema.users.username, username))\n .run();\n serverEvents.emit('userInvalidated', username);\n return 1;\n }\n\n async setUserPermissions(username: string, permissions: PermissionSet) {\n this.db\n .update(schema.users)\n .set({ permissions: permissions })\n .where(eq(schema.users.username, username))\n .run();\n }\n\n async getDefaultPermissions(): Promise<IStoreDefaultPermissions> {\n if (this.defaultPermissionsCache) return this.defaultPermissionsCache;\n const row = this.db\n .select()\n .from(schema.serverConfig)\n .where(eq(schema.serverConfig.key, 'defaultPermissions'))\n .get();\n if (!row) {\n const defaults: IStoreDefaultPermissions = {\n type: 'defaultPermissions',\n root: RootLevel.Read,\n domains: {},\n scopes: {},\n };\n this.db\n .insert(schema.serverConfig)\n .values({ key: 'defaultPermissions', value: defaults })\n .run();\n this.defaultPermissionsCache = defaults;\n return defaults;\n }\n const val = row.value as any;\n const result: IStoreDefaultPermissions = {\n type: 'defaultPermissions',\n root: val.root ?? RootLevel.Off,\n domains: val.domains ?? {},\n scopes: val.scopes ?? {},\n };\n this.defaultPermissionsCache = result;\n return result;\n }\n\n async setDefaultPermissions(\n permissions: Omit<IStoreDefaultPermissions, 'type'>,\n ) {\n const value = {\n root: permissions.root,\n domains: permissions.domains,\n scopes: permissions.scopes,\n };\n this.db\n .insert(schema.serverConfig)\n .values({ key: 'defaultPermissions', value })\n .onConflictDoUpdate({\n target: schema.serverConfig.key,\n set: { value },\n })\n .run();\n this.defaultPermissionsCache = null;\n }\n\n // get a user from credentials\n async authUser(username: string, password: string) {\n const row = this.db\n .select()\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n // user not found\n if (!row || row.isBanned) return null;\n // make sure the user's password hash is valid\n if (await bcrypt.compare(password, row.hash)) {\n this.db\n .update(schema.users)\n .set({ lastOnline: Date.now() })\n .where(eq(schema.users.id, row.id))\n .run();\n // update last online status\n return this._toStoreUser(row);\n }\n // wrong password\n return null;\n }\n\n // find a user by object id\n async findUserById(id: string) {\n const row = this.db\n .select()\n .from(schema.users)\n .where(\n or(\n // the owner has no username, so everyone is the owner\n and(eq(schema.users.username, ''), eq(schema.users.isOwner, true)),\n // the user exists and has an id\n eq(schema.users.id, id),\n ),\n )\n .get();\n if (!row) return null;\n return this._toStoreUser(row);\n }\n\n async findUserByUsername(\n username: string,\n ): Promise<(IStoreUser & { id: string }) | null> {\n const row = this.db\n .select()\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n if (!row) return null;\n return this._toStoreUser(row);\n }\n\n async findUserByPasskeyId(credentialId: string) {\n const rows = this.db\n .select()\n .from(schema.users)\n .where(\n sql`${schema.users.passkeys} LIKE ${'%\"id\":\"' + credentialId + '\"%'}`,\n )\n .all();\n for (const row of rows) {\n const passkeys = (row.passkeys ?? []) as IWebAuthnCredential[];\n if (passkeys.some(p => p.id === credentialId)) {\n return this._toStoreUser(row);\n }\n }\n return null;\n }\n\n updateUserLastOnline(id: string) {\n this.db\n .update(schema.users)\n .set({ lastOnline: Date.now() })\n .where(eq(schema.users.id, id))\n .run();\n }\n\n updateOwnerCredentials(id: string, username: string, hash: string) {\n this.db\n .update(schema.users)\n .set({ username, hash })\n .where(eq(schema.users.id, id))\n .run();\n }\n\n addUserRole(userId: string, roleId: string) {\n const row = this.db\n .select({ roles: schema.users.roles })\n .from(schema.users)\n .where(eq(schema.users.id, userId))\n .get();\n if (!row) return;\n const roles = (row.roles ?? []) as string[];\n if (!roles.includes(roleId)) {\n roles.push(roleId);\n this.db\n .update(schema.users)\n .set({ roles })\n .where(eq(schema.users.id, userId))\n .run();\n }\n }\n\n removeUserRole(userId: string, roleId: string) {\n const row = this.db\n .select({ roles: schema.users.roles })\n .from(schema.users)\n .where(eq(schema.users.id, userId))\n .get();\n if (!row) return;\n const roles = ((row.roles ?? []) as string[]).filter(r => r !== roleId);\n this.db\n .update(schema.users)\n .set({ roles })\n .where(eq(schema.users.id, userId))\n .run();\n }\n\n async setUserTotp(username: string, secret: string, enabled: boolean) {\n this.db\n .update(schema.users)\n .set({ totpSecret: secret, totpEnabled: enabled })\n .where(eq(schema.users.username, username))\n .run();\n }\n\n async disableUserTotp(username: string) {\n this.db\n .update(schema.users)\n .set({ totpEnabled: false, totpSecret: null })\n .where(eq(schema.users.username, username))\n .run();\n }\n\n async addPasskey(username: string, credential: IWebAuthnCredential) {\n const row = this.db\n .select({ id: schema.users.id, passkeys: schema.users.passkeys })\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n if (!row) return;\n const passkeys = [\n ...((row.passkeys ?? []) as IWebAuthnCredential[]),\n credential,\n ];\n this.db\n .update(schema.users)\n .set({ passkeys })\n .where(eq(schema.users.id, row.id))\n .run();\n }\n\n async removePasskey(username: string, credentialId: string) {\n const row = this.db\n .select({ id: schema.users.id, passkeys: schema.users.passkeys })\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n if (!row) return;\n const passkeys = ((row.passkeys ?? []) as IWebAuthnCredential[]).filter(\n p => p.id !== credentialId,\n );\n this.db\n .update(schema.users)\n .set({ passkeys })\n .where(eq(schema.users.id, row.id))\n .run();\n }\n\n async updatePasskeyCounter(\n username: string,\n credentialId: string,\n counter: number,\n ) {\n const row = this.db\n .select({ id: schema.users.id, passkeys: schema.users.passkeys })\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n if (!row) return;\n const passkeys = ((row.passkeys ?? []) as IWebAuthnCredential[]).map(p =>\n p.id === credentialId ? { ...p, counter, lastUsed: Date.now() } : p,\n );\n this.db\n .update(schema.users)\n .set({ passkeys })\n .where(eq(schema.users.id, row.id))\n .run();\n }\n\n async setRecoveryCodes(username: string, hashedCodes: string[]) {\n this.db\n .update(schema.users)\n .set({ recoveryCodes: hashedCodes })\n .where(eq(schema.users.username, username))\n .run();\n }\n\n async removeRecoveryCode(username: string, hashedCode: string) {\n const row = this.db\n .select({\n id: schema.users.id,\n recoveryCodes: schema.users.recoveryCodes,\n })\n .from(schema.users)\n .where(eq(schema.users.username, username))\n .get();\n if (!row) return;\n const codes = ((row.recoveryCodes ?? []) as string[]).filter(\n c => c !== hashedCode,\n );\n this.db\n .update(schema.users)\n .set({ recoveryCodes: codes })\n .where(eq(schema.users.id, row.id))\n .run();\n }\n\n async resetUserMfa(username: string) {\n this.db\n .update(schema.users)\n .set({\n totpEnabled: false,\n totpSecret: null,\n passkeys: [],\n recoveryCodes: [],\n })\n .where(eq(schema.users.username, username))\n .run();\n }\n\n async getAllRoles(): Promise<(IStoreRole & { id: string })[]> {\n if (this.rolesCache) return this.rolesCache;\n const rows = this.db\n .select()\n .from(schema.webRoles)\n .orderBy(desc(schema.webRoles.order))\n .all();\n const roles = rows.map(r => ({\n type: 'webRole' as const,\n id: r.id,\n name: r.name,\n description: r.description,\n order: r.order,\n permissions: r.permissions as PermissionSet,\n }));\n this.rolesCache = roles;\n return roles;\n }\n\n invalidateRolesCache() {\n this.rolesCache = null;\n this.defaultPermissionsCache = null;\n }\n\n async getRole(id: string): Promise<(IStoreRole & { id: string }) | null> {\n const row = this.db\n .select()\n .from(schema.webRoles)\n .where(eq(schema.webRoles.id, id))\n .get();\n if (!row) return null;\n return {\n type: 'webRole',\n id: row.id,\n name: row.name,\n description: row.description,\n order: row.order,\n permissions: row.permissions as PermissionSet,\n };\n }\n\n async createRole(\n name: string,\n description: string,\n permissions: PermissionSet,\n ): Promise<IStoreRole & { id: string }> {\n const id = randomUUID();\n const tx = this.sqlite.transaction(() => {\n this.db\n .update(schema.webRoles)\n .set({ order: sql`${schema.webRoles.order} + 1` })\n .run();\n this.db\n .insert(schema.webRoles)\n .values({\n id,\n name,\n description,\n order: 1,\n permissions: permissions,\n })\n .run();\n });\n tx();\n this.invalidateRolesCache();\n return {\n type: 'webRole',\n id,\n name,\n description,\n order: 1,\n permissions: permissions,\n };\n }\n\n async updateRole(\n id: string,\n updates: Partial<Pick<IStoreRole, 'name' | 'description' | 'order'>> & {\n permissions?: PermissionSet;\n },\n ): Promise<void> {\n const $set: Record<string, any> = {};\n if (updates.name !== undefined) $set.name = updates.name;\n if (updates.description !== undefined)\n $set.description = updates.description;\n if (updates.order !== undefined) $set.order = updates.order;\n if (updates.permissions !== undefined)\n $set.permissions = updates.permissions;\n if (Object.keys($set).length > 0) {\n this.db\n .update(schema.webRoles)\n .set($set)\n .where(eq(schema.webRoles.id, id))\n .run();\n }\n this.invalidateRolesCache();\n }\n\n async deleteRole(id: string): Promise<string> {\n const role = await this.getRole(id);\n if (!role) return 'role not found';\n\n const tx = this.sqlite.transaction(() => {\n this.db.delete(schema.webRoles).where(eq(schema.webRoles.id, id)).run();\n const usersWithRole = this.db\n .select({ id: schema.users.id, roles: schema.users.roles })\n .from(schema.users)\n .all();\n for (const u of usersWithRole) {\n const roles = ((u.roles ?? []) as string[]).filter(r => r !== id);\n if (roles.length !== ((u.roles ?? []) as string[]).length) {\n this.db\n .update(schema.users)\n .set({ roles })\n .where(eq(schema.users.id, u.id))\n .run();\n }\n }\n });\n tx();\n this.invalidateRolesCache();\n return '';\n }\n\n async reorderRoles(entries: { id: string; order: number }[]): Promise<void> {\n const tx = this.sqlite.transaction(() => {\n for (const { id, order } of entries) {\n this.db\n .update(schema.webRoles)\n .set({ order })\n .where(eq(schema.webRoles.id, id))\n .run();\n }\n });\n tx();\n this.invalidateRolesCache();\n }\n\n async getUserRolePermissions(user: IStoreUser): Promise<PermissionSet[]> {\n const defaults = await this.getDefaultPermissions();\n const defaultPerms: PermissionSet = {\n root: defaults.root,\n domains: defaults.domains,\n scopes: defaults.scopes,\n };\n const allRoles = await this.getAllRoles();\n const userRoles = allRoles.filter(r => user.roles.includes(r.id));\n return [defaultPerms, ...userRoles.map(r => r.permissions)];\n }\n\n async getUserAssignedRoles(\n user: IStoreUser,\n ): Promise<(IStoreRole & { id: string })[]> {\n const allRoles = await this.getAllRoles();\n return allRoles.filter(r => user.roles.includes(r.id));\n }\n\n // add a chat message to the chat log store\n async addChatLog(\n action: 'msg' | 'server' | 'leave' | 'join',\n user: IPlayer,\n message?: string,\n ) {\n this.calendar.addDate(Date.now());\n const row = this.db\n .insert(schema.chatLogs)\n .values({\n created: Date.now(),\n instanceId: await this.getInstanceId(),\n action,\n user,\n ...(message ? { message } : {}),\n })\n .returning()\n .get();\n return {\n type: 'chat' as const,\n _id: String(row.id),\n id: row.id,\n created: row.created,\n instanceId: row.instanceId,\n action: row.action,\n user: row.user,\n message: row.message,\n };\n }\n\n // get recent chat activity\n async getChats({\n count: limit = 50,\n sameServer,\n before,\n after,\n }: {\n count?: number;\n sameServer?: boolean;\n before?: number;\n after?: number;\n } = {}) {\n const conditions = [];\n if (sameServer) {\n conditions.push(\n eq(schema.chatLogs.instanceId, await this.getInstanceId()),\n );\n }\n if (before) {\n conditions.push(lt(schema.chatLogs.created, before));\n } else if (after) {\n conditions.push(gt(schema.chatLogs.created, after));\n } else {\n conditions.push(lt(schema.chatLogs.created, Date.now()));\n }\n\n const orderDir = !before && after ? asc : desc;\n const rows = this.db\n .select()\n .from(schema.chatLogs)\n .where(conditions.length > 1 ? and(...conditions) : conditions[0])\n .orderBy(orderDir(schema.chatLogs.created))\n .limit(limit)\n .all();\n\n return rows.map(r => ({\n type: 'chat' as const,\n _id: String(r.id),\n created: r.created,\n instanceId: r.instanceId,\n action: r.action,\n user: r.user,\n message: r.message,\n }));\n }\n\n // get paginated players\n async getPlayers({\n count: limit = 50,\n search = '',\n page = 0,\n sort = 'name',\n direction = 1,\n limitId = undefined,\n }: {\n count?: number;\n search?: string;\n page?: number;\n sort?: string;\n direction?: number;\n limitId?: string[];\n } = {}) {\n const pattern = search.length > 0 ? explode(search) : null;\n const patternSource = pattern?.source;\n\n // an empty filter (e.g. \"banned\" with no active bans) matches nothing\n if (limitId && limitId.length === 0)\n return { pages: 0, total: 0, players: [] };\n\n const conditions: SQL[] = [];\n\n if (limitId) {\n conditions.push(inArray(schema.playerHistory.id, limitId));\n }\n\n if (patternSource) {\n // match name history values via json_each so the pattern can't hit\n // JSON keys or date timestamps\n conditions.push(sql`(\n ${schema.playerHistory.id} = ${search}\n OR ${schema.playerHistory.name} REGEXP ${patternSource}\n OR ${schema.playerHistory.displayName} REGEXP ${patternSource}\n OR EXISTS (\n SELECT 1 FROM json_each(${schema.playerHistory.nameHistory})\n WHERE json_each.type = 'object'\n AND (json_extract(json_each.value, '$.name') REGEXP ${patternSource}\n OR json_extract(json_each.value, '$.displayName') REGEXP ${patternSource})\n )\n )`);\n }\n\n const baseWhere = conditions.length > 0 ? and(...conditions) : undefined;\n\n const sortCol =\n sort === 'lastSeen'\n ? schema.playerHistory.lastSeen\n : sort === 'created'\n ? schema.playerHistory.created\n : sort === 'heartbeats'\n ? schema.playerHistory.heartbeats\n : sort === 'sessions'\n ? schema.playerHistory.sessions\n : sql`${schema.playerHistory.name} COLLATE NOCASE`;\n const orderDir = direction === 1 ? asc : desc;\n\n const totalResult = this.db\n .select({ count: count() })\n .from(schema.playerHistory)\n .where(baseWhere)\n .get();\n const players = this.db\n .select()\n .from(schema.playerHistory)\n .where(baseWhere)\n .orderBy(orderDir(sortCol))\n .limit(limit)\n .offset(limit * page)\n .all();\n\n const total = totalResult?.count ?? 0;\n const result = players.map(p => this._toUserHistory(p));\n\n // exact match detection\n if (search.length > 0 && page === 0) {\n const exactRows = this.db\n .select()\n .from(schema.playerHistory)\n .where(\n or(\n eq(schema.playerHistory.id, search),\n eq(schema.playerHistory.name, search),\n eq(schema.playerHistory.displayName, search),\n ),\n )\n .all();\n if (exactRows.length > 0) {\n for (const res of exactRows) {\n const idx = result.findIndex(p => p.id === res.id);\n if (idx >= 0) result.splice(idx, 1);\n }\n result.splice(0, 0, ...exactRows.map(r => this._toUserHistory(r)));\n }\n }\n\n return { pages: Math.ceil(total / limit), total, players: result };\n }\n\n // get paginated users\n async getUsers({\n count: limit = 50,\n search = '',\n page = 0,\n sort = 'name',\n direction = 1,\n } = {}) {\n const pattern = search.length > 0 ? explode(search) : null;\n\n let baseWhere = sql`1=1`;\n if (pattern) {\n baseWhere = sql`(\n ${schema.users.playerId} = ${search}\n OR ${schema.users.username} REGEXP ${pattern.source}\n )`;\n }\n\n const sortCol =\n sort === 'lastOnline'\n ? schema.users.lastOnline\n : sort === 'created'\n ? schema.users.created\n : sql`${schema.users.username} COLLATE NOCASE`;\n const orderDir = direction === 1 ? asc : desc;\n\n const totalResult = this.db\n .select({ count: count() })\n .from(schema.users)\n .where(baseWhere)\n .get();\n const users = this.db\n .select()\n .from(schema.users)\n .where(baseWhere)\n .orderBy(orderDir(sortCol))\n .limit(limit)\n .offset(limit * page)\n .all();\n\n return {\n pages: Math.ceil((totalResult?.count ?? 0) / limit),\n total: totalResult?.count ?? 0,\n users: users.map(u => this._toStoreUser(u)),\n };\n }\n\n // get an individual player, ban and kick history, and notes\n async getPlayer(id: string) {\n const player = this.db\n .select()\n .from(schema.playerHistory)\n .where(eq(schema.playerHistory.id, id))\n .get();\n if (!player) return null;\n\n const banHist = this.db\n .select()\n .from(schema.banHistory)\n .where(eq(schema.banHistory.banned, id))\n .orderBy(desc(schema.banHistory.created))\n .limit(25)\n .all();\n\n const kickHist = this.db\n .select()\n .from(schema.kickHistory)\n .where(eq(schema.kickHistory.kicked, id))\n .orderBy(desc(schema.kickHistory.created))\n .limit(25)\n .all();\n\n const noteRows = this.db\n .select()\n .from(schema.playerNotes)\n .where(eq(schema.playerNotes.playerId, id))\n .limit(25)\n .all();\n\n return {\n ...this._toUserHistory(player),\n banHistory: banHist.map(b => ({\n type: 'banHistory' as const,\n _id: String(b.id),\n banned: b.banned,\n bannerId: b.bannerId,\n created: b.created,\n expires: b.expires,\n reason: b.reason,\n })),\n kickHistory: kickHist.map(k => ({\n type: 'kickHistory' as const,\n _id: String(k.id),\n kicked: k.kicked,\n kickerId: k.kickerId,\n created: k.created,\n reason: k.reason,\n })),\n notes: noteRows.map(n => ({\n type: 'note' as const,\n _id: String(n.id),\n id: n.playerId,\n note: n.note,\n })),\n };\n }\n\n // add a user to the visit history, returns true if this is a first visit\n async addVisit(user: Required<IPlayer>) {\n const now = Date.now();\n // resolve the instance id before opening the transaction — the tx callback\n // must stay synchronous, and this is the only await in the method\n const instanceId = await this.getInstanceId();\n\n // read-modify-write the row atomically so two near-simultaneous joins for\n // the same id can't both insert (PK conflict) or clobber each other's\n // nameHistory/counter updates from a shared stale snapshot\n const visit = this.sqlite.transaction(() => {\n const existing = this.db\n .select()\n .from(schema.playerHistory)\n .where(eq(schema.playerHistory.id, user.id))\n .get();\n\n if (!existing) {\n this.db\n .insert(schema.playerHistory)\n .values({\n id: user.id,\n name: user.name,\n displayName: user.displayName,\n nameHistory: [\n { name: user.name, displayName: user.displayName, date: now },\n ],\n ips: [],\n created: now,\n lastSeen: now,\n lastInstanceId: instanceId,\n heartbeats: 0,\n sessions: 1,\n instances: 1,\n })\n .run();\n return true;\n }\n\n const nameHistory = (existing.nameHistory ?? []) as {\n name: string;\n displayName: string;\n date: number;\n }[];\n const hasName = nameHistory.some(\n h => h.name === user.name && h.displayName === user.displayName,\n );\n if (!hasName) {\n nameHistory.push({\n name: user.name,\n displayName: user.displayName,\n date: now,\n });\n }\n\n this.db\n .update(schema.playerHistory)\n .set({\n lastSeen: now,\n name: user.name,\n displayName: user.displayName,\n lastInstanceId: instanceId,\n nameHistory,\n sessions: sql`${schema.playerHistory.sessions} + ${existing.lastSeen < now - 60 * 60 * 1000 ? 1 : 0}`,\n instances: sql`${schema.playerHistory.instances} + ${existing.lastInstanceId !== instanceId ? 1 : 0}`,\n })\n .where(eq(schema.playerHistory.id, existing.id))\n .run();\n return false;\n });\n\n return visit();\n }\n\n // use data from minutely heartbeats to fuel metrics\n async addHeartbeat(data: {\n bricks: number;\n players: string[];\n ips: Record<string, string>;\n }) {\n const now = Date.now();\n const tx = this.sqlite.transaction(() => {\n this.db\n .insert(schema.heartbeats)\n .values({\n created: now,\n bricks: data.bricks,\n players: data.players,\n })\n .run();\n\n if (data.players.length > 0) {\n // one bulk UPDATE for the shared lastSeen/heartbeats bump\n this.db\n .update(schema.playerHistory)\n .set({\n lastSeen: now,\n heartbeats: sql`${schema.playerHistory.heartbeats} + 1`,\n })\n .where(inArray(schema.playerHistory.id, data.players))\n .run();\n\n const players = this.db\n .select()\n .from(schema.playerHistory)\n .where(inArray(schema.playerHistory.id, data.players))\n .all();\n\n for (const p of players) {\n const ip = data.ips[p.id];\n if (ip && !((p.ips ?? []) as string[]).includes(ip)) {\n const ips = [...((p.ips ?? []) as string[]), ip];\n this.db\n .update(schema.playerHistory)\n .set({ ips })\n .where(eq(schema.playerHistory.id, p.id))\n .run();\n }\n }\n }\n });\n tx();\n }\n\n upsertBanHistory(entry: Omit<IStoreBanHistory, 'type'>) {\n this.db\n .insert(schema.banHistory)\n .values({\n banned: entry.banned,\n bannerId: entry.bannerId ?? '',\n // Number.isFinite (not typeof) so NaN from parseBrickadiaTime becomes\n // 0, binding NaN to a NOT NULL integer column stores NULL and throws\n created: Number.isFinite(entry.created) ? entry.created : 0,\n expires: Number.isFinite(entry.expires) ? entry.expires : 0,\n reason: entry.reason ?? '',\n })\n .onConflictDoNothing()\n .run();\n }\n\n upsertKickHistory(entry: Omit<IStoreKickHistory, 'type'>) {\n this.db\n .insert(schema.kickHistory)\n .values({\n kicked: entry.kicked,\n kickerId: entry.kickerId ?? '',\n created: entry.created ?? 0,\n reason: entry.reason ?? '',\n })\n .onConflictDoNothing()\n .run();\n }\n\n async getAutoRestartConfig(): Promise<IStoreAutoRestartConfig> {\n const row = this.db\n .select()\n .from(schema.serverConfig)\n .where(eq(schema.serverConfig.key, 'autoRestartConfig'))\n .get();\n if (!row) {\n const config: IStoreAutoRestartConfig = {\n type: 'autoRestartConfig',\n maxUptime: 48,\n maxUptimeEnabled: false,\n emptyUptime: 24,\n emptyUptimeEnabled: false,\n dailyHour: 2,\n dailyHourEnabled: false,\n autoUpdateEnabled: true,\n autoUpdateIntervalMins: 60,\n announcementEnabled: true,\n playersEnabled: true,\n saveWorld: true,\n crashRestartEnabled: true,\n };\n this.db\n .insert(schema.serverConfig)\n .values({ key: 'autoRestartConfig', value: config })\n .run();\n return config;\n }\n const config = row.value as IStoreAutoRestartConfig;\n config.type = 'autoRestartConfig';\n config.autoUpdateEnabled ??= true;\n config.autoUpdateIntervalMins ??= 60;\n config.saveWorld ??= true;\n config.crashRestartEnabled ??= true;\n return config;\n }\n\n async setAutoRestartConfig(config: IStoreAutoRestartConfig): Promise<void> {\n config.maxUptime = Math.round(Math.max(1, Math.min(config.maxUptime, 168)));\n config.emptyUptime = Math.round(\n Math.max(1, Math.min(config.emptyUptime, 168)),\n );\n config.dailyHour = Math.round(Math.max(0, Math.min(config.dailyHour, 23)));\n config.autoUpdateIntervalMins = Math.round(\n Math.max(10, Math.min(config.autoUpdateIntervalMins ?? 60, Infinity)),\n );\n config.autoUpdateEnabled ??= true;\n this.db\n .insert(schema.serverConfig)\n .values({ key: 'autoRestartConfig', value: config })\n .onConflictDoUpdate({\n target: schema.serverConfig.key,\n set: { value: config },\n })\n .run();\n }\n\n // update player online-time punchcard\n async updatePlayerPunchcard(numNewPlayers: number) {\n const now = new Date();\n const time = now.getTime();\n const hour = now.getUTCHours();\n const day = now.getUTCDay();\n const month = now.getUTCMonth();\n const year = now.getUTCFullYear();\n\n const card = this.db\n .select()\n .from(schema.punchcards)\n .where(\n and(\n eq(schema.punchcards.kind, 'playerCount'),\n eq(schema.punchcards.month, month),\n eq(schema.punchcards.year, year),\n ),\n )\n .get();\n\n if (!card) {\n const punchcard = createPunchcard();\n punchcard[day][hour] = numNewPlayers;\n this.db\n .insert(schema.punchcards)\n .values({\n kind: 'playerCount',\n created: time,\n updated: time,\n month,\n year,\n punchcard,\n })\n .run();\n } else {\n const punchcard = card.punchcard as number[][];\n punchcard[day][hour] += numNewPlayers;\n this.db\n .update(schema.punchcards)\n .set({ punchcard, updated: time })\n .where(eq(schema.punchcards.id, card.id))\n .run();\n }\n }\n\n private _toStoreUser(\n row: typeof schema.users.$inferSelect,\n ): IStoreUser & { id: string; _id: string } {\n const user: IStoreUser & { id: string; _id: string } = {\n type: 'user',\n id: row.id,\n _id: row.id,\n created: row.created,\n lastOnline: row.lastOnline,\n username: row.username,\n hash: row.hash,\n isOwner: row.isOwner,\n roles: (row.roles ?? []) as string[],\n playerId: row.playerId,\n isBanned: row.isBanned,\n permissions:\n (row.permissions as PermissionSet | null) ?? EMPTY_PERMISSIONS,\n totpSecret: row.totpSecret ?? undefined,\n totpEnabled: row.totpEnabled,\n passkeys: (row.passkeys ?? []) as IWebAuthnCredential[],\n recoveryCodes: (row.recoveryCodes ?? []) as string[],\n };\n return user;\n }\n\n private _toUserHistory(\n row: typeof schema.playerHistory.$inferSelect,\n ): IUserHistory & { _id: string } {\n return {\n type: 'userHistory',\n _id: row.id,\n id: row.id,\n name: row.name,\n displayName: row.displayName,\n nameHistory: (row.nameHistory ?? []) as {\n displayName: string;\n name: string;\n date: number;\n }[],\n ips: (row.ips ?? []) as string[],\n created: row.created,\n lastSeen: row.lastSeen,\n lastInstanceId: row.lastInstanceId,\n heartbeats: row.heartbeats,\n sessions: row.sessions,\n instances: row.instances,\n };\n }\n}\n"],"names":["Calendar","randomUUID","schema.serverInstances","schema.chatLogs","parseBrickadiaTime","count","schema.users","EMPTY_PERMISSIONS","eq","serverEvents","permissions","schema.serverConfig","RootLevel","or","and","sql","schema.webRoles","desc","lt","gt","asc","pattern","explode","inArray","schema.playerHistory","schema.banHistory","schema.kickHistory","schema.playerNotes","schema.heartbeats","config","time","schema.punchcards"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omegga",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "Brickadia server installer, updater, manager, wrapper, configurator, and automator",
5
5
  "engines": {
6
6
  "node": ">=23.0.0"
@@ -21,7 +21,7 @@
21
21
  "better-sqlite3": "^12.2.0",
22
22
  "better-sqlite3-session-store": "^0.1.0",
23
23
  "body-parser": "^1.19.0",
24
- "brs-js": "^3.2.1",
24
+ "brs-js": "^3.2.4",
25
25
  "child_process": "^1.0.2",
26
26
  "chokidar": "^3.4.2",
27
27
  "colors": "^1.4.0",