omegga 1.17.2 → 1.18.0
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/README.md +7 -6
- package/dist/db/migrations/0001_shocking_the_captain.sql +4 -0
- package/dist/db/migrations/meta/0001_snapshot.json +753 -0
- package/dist/db/migrations/meta/_journal.json +7 -0
- package/dist/db/schema.js +10 -1
- package/dist/db/schema.js.map +1 -1
- package/dist/omegga/server.js +6 -4
- package/dist/omegga/server.js.map +1 -1
- package/dist/plugin.js.map +1 -1
- package/dist/webserver/backend/chatQuery.js +98 -0
- package/dist/webserver/backend/chatQuery.js.map +1 -0
- package/dist/webserver/backend/database.js +270 -5
- package/dist/webserver/backend/database.js.map +1 -1
- package/dist/webserver/backend/router/chat.js +21 -0
- package/dist/webserver/backend/router/chat.js.map +1 -1
- package/dist/webserver/backend/router/player.js +1 -0
- package/dist/webserver/backend/router/player.js.map +1 -1
- package/package.json +4 -3
- package/public/app.html +291 -48
- package/public/assets/IconShieldCheck-e9eEH-KF.js +2 -0
- package/public/assets/{IconWorld-DgiOjCuo.js → IconWorld-CMSFtQg-.js} +2 -2
- package/public/assets/IconWorldPlus-B3tINWGI.js +2 -0
- package/public/assets/IconX-Dnl42MJL.js +2 -0
- package/public/assets/app-Dfcj6I4Z.js +7 -0
- package/public/assets/auth-CNs90huc.js +2 -0
- package/public/assets/{index-B0LowBt1.js → index-B6mWKnn3.js} +2 -2
- package/public/assets/index-C6nSzQey.js +12 -0
- package/public/assets/{index-2cgXLhrt.js → index-NK6TBVas.js} +2 -2
- package/public/assets/shared/@components-DxLSzrzW.js +2 -0
- package/public/assets/shared/{@hooks-DlZ8TOOZ.js → @hooks-Df79JLiI.js} +2 -2
- package/public/assets/shared/@tabler_icons-react-CM_Q7eQ6.js +3 -0
- package/public/assets/shared/wouter-CPV7_rSk.js +2 -0
- package/public/auth.html +80 -5
- package/public/assets/IconShieldCheck-Bjlm8mX2.js +0 -2
- package/public/assets/IconWorldPlus-DJJNrT87.js +0 -2
- package/public/assets/IconX-CvmO0bbE.js +0 -2
- package/public/assets/app-Mh--ChqF.js +0 -7
- package/public/assets/auth-DQtYicf-.js +0 -2
- package/public/assets/index-BTSbuqTr.js +0 -12
- package/public/assets/shared/@components-DBMXoJMT.js +0 -2
- package/public/assets/shared/@tabler_icons-react-i-Q2c_A_.js +0 -3
- package/public/assets/shared/wouter-CUC1caxu.js +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sources":["../../../src/webserver/backend/database.ts"],"sourcesContent":["import { type 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 = (\n kicked: IPlayer & { id: string },\n kicker: IPlayer & { id: string },\n reason: string,\n ) => {\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 // check if a username is taken (case-insensitive, so users can't\n // impersonate each other with case variants), optionally excluding a user id\n async usernameTaken(username: string, excludeId?: string) {\n const rows = this.db\n .select({ id: schema.users.id })\n .from(schema.users)\n .where(sql`${schema.users.username} = ${username} COLLATE NOCASE`)\n .all();\n return rows.some(r => r.id !== excludeId);\n }\n\n // rename a user by id. throws when the name is invalid or already taken\n async renameUser(id: string, username: string) {\n if (!username.match(/^\\w{1,32}$/))\n throw new Error('username is not allowed');\n if (await this.usernameTaken(username, id))\n throw new Error('username is already taken');\n try {\n this.db\n .update(schema.users)\n .set({ username })\n .where(eq(schema.users.id, id))\n .run();\n } catch (e) {\n // unique constraint race between the taken check and the update\n if ((e as { code?: string }).code?.startsWith('SQLITE_CONSTRAINT'))\n throw new Error('username is already taken');\n throw e;\n }\n }\n\n // grant ownership to a user. throws when the user is missing, disabled, or\n // already an owner\n async grantOwner(username: string) {\n const user = await this.findUserByUsername(username);\n if (!user) throw new Error('user does not exist');\n if (user.isOwner) throw new Error('user is already an owner');\n if (user.isBanned)\n throw new Error('cannot grant ownership to a disabled user');\n this.db\n .update(schema.users)\n .set({ isOwner: true })\n .where(eq(schema.users.id, user.id))\n .run();\n }\n\n // revoke ownership from a user. throws when the user is missing, not an\n // owner, or the last remaining owner\n async revokeOwner(username: string) {\n const user = await this.findUserByUsername(username);\n if (!user) throw new Error('user does not exist');\n if (!user.isOwner) throw new Error('user is not an owner');\n const owners = this.db\n .select({ count: count() })\n .from(schema.users)\n .where(eq(schema.users.isOwner, true))\n .get();\n if ((owners?.count ?? 0) <= 1)\n throw new Error('cannot revoke the last owner');\n this.db\n .update(schema.users)\n .set({ isOwner: false })\n .where(eq(schema.users.id, user.id))\n .run();\n // abort any live sessions' subscriptions that were authorized as owner\n serverEvents.emit('userInvalidated', username);\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 })\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 Partial<Omit<IStoreDefaultPermissions, 'type'>>;\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; an undefined id (no session) can still resolve\n // to the passwordless owner user\n async findUserById(id: string | undefined) {\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 ...(id != null ? [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,\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,\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: Partial<typeof schema.webRoles.$inferInsert> = {};\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","sql","serverEvents","permissions","schema.serverConfig","RootLevel","or","and","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 * as schema from '@/db/schema';\nimport { type IServerConfig } from '@config/types';\nimport type Omegga from '@omegga/server';\nimport { explode } from '@util/pattern';\nimport { parseBrickadiaTime } from '@util/time';\nimport * as uuid from '@util/uuid';\nimport bcrypt from 'bcryptjs';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport chokidar from 'chokidar';\nimport {\n and,\n asc,\n count,\n desc,\n eq,\n gt,\n inArray,\n lt,\n or,\n sql,\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 {\n isEmptyChatQuery,\n parseChatQuery,\n type ChatSearchAction,\n} from './chatQuery';\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\n/**\n * Most searches return far fewer matches than this, so the reported total is\n * exact; past it the ui says \"1000+\" rather than paying for a full scan.\n */\nconst SEARCH_COUNT_CAP = 1000;\n\n/** What a crash looked like in the log before `crash` was an action. */\nconst LEGACY_CRASH_MESSAGES = ['Server error', 'Server crashed, restarting...'];\n\n/** Escapes a value so its wildcards are matched literally inside LIKE. */\nconst escapeLike = (value: string) => value.replace(/[\\\\%_]/g, c => '\\\\' + c);\nconst likePrefix = (value: string) => `${escapeLike(value)}%`;\nconst likeAnywhere = (value: string) => `%${escapeLike(value)}%`;\n\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 = (\n kicked: IPlayer & { id: string },\n kicker: IPlayer & { id: string },\n reason: string,\n ) => {\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 // check if a username is taken (case-insensitive, so users can't\n // impersonate each other with case variants), optionally excluding a user id\n async usernameTaken(username: string, excludeId?: string) {\n const rows = this.db\n .select({ id: schema.users.id })\n .from(schema.users)\n .where(sql`${schema.users.username} = ${username} COLLATE NOCASE`)\n .all();\n return rows.some(r => r.id !== excludeId);\n }\n\n // rename a user by id. throws when the name is invalid or already taken\n async renameUser(id: string, username: string) {\n if (!username.match(/^\\w{1,32}$/))\n throw new Error('username is not allowed');\n if (await this.usernameTaken(username, id))\n throw new Error('username is already taken');\n try {\n this.db\n .update(schema.users)\n .set({ username })\n .where(eq(schema.users.id, id))\n .run();\n } catch (e) {\n // unique constraint race between the taken check and the update\n if ((e as { code?: string }).code?.startsWith('SQLITE_CONSTRAINT'))\n throw new Error('username is already taken');\n throw e;\n }\n }\n\n // grant ownership to a user. throws when the user is missing, disabled, or\n // already an owner\n async grantOwner(username: string) {\n const user = await this.findUserByUsername(username);\n if (!user) throw new Error('user does not exist');\n if (user.isOwner) throw new Error('user is already an owner');\n if (user.isBanned)\n throw new Error('cannot grant ownership to a disabled user');\n this.db\n .update(schema.users)\n .set({ isOwner: true })\n .where(eq(schema.users.id, user.id))\n .run();\n }\n\n // revoke ownership from a user. throws when the user is missing, not an\n // owner, or the last remaining owner\n async revokeOwner(username: string) {\n const user = await this.findUserByUsername(username);\n if (!user) throw new Error('user does not exist');\n if (!user.isOwner) throw new Error('user is not an owner');\n const owners = this.db\n .select({ count: count() })\n .from(schema.users)\n .where(eq(schema.users.isOwner, true))\n .get();\n if ((owners?.count ?? 0) <= 1)\n throw new Error('cannot revoke the last owner');\n this.db\n .update(schema.users)\n .set({ isOwner: false })\n .where(eq(schema.users.id, user.id))\n .run();\n // abort any live sessions' subscriptions that were authorized as owner\n serverEvents.emit('userInvalidated', username);\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 })\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 Partial<Omit<IStoreDefaultPermissions, 'type'>>;\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; an undefined id (no session) can still resolve\n // to the passwordless owner user\n async findUserById(id: string | undefined) {\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 ...(id != null ? [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,\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,\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: Partial<typeof schema.webRoles.$inferInsert> = {};\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' | 'crash',\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 /**\n * Number of chat messages a player has sent. Join, leave, and server entries\n * are not counted. Console and web messages from accounts with no linked\n * player id are stored with an empty id, so they are never attributed.\n */\n async getPlayerMessageCount(id: string) {\n if (!id) return 0;\n const row = this.db\n .select({ count: count() })\n .from(schema.chatLogs)\n .where(\n and(\n // spelled to match chat_logs_user_id_action_created_idx, which is\n // an expression index and is skipped if the expression differs\n eq(sql`json_extract(${schema.chatLogs.user}, '$.id')`, id),\n eq(schema.chatLogs.action, 'msg'),\n ),\n )\n .get();\n return row?.count ?? 0;\n }\n\n /**\n * Resolve `from:` terms to players. Matches ids exactly and names fuzzily,\n * including names a player has since changed away from, so searching by the\n * name someone used at the time still finds them.\n */\n private resolveChatSenders(names: string[]) {\n const clauses = names.map(name => {\n // a pick from the completion dropdown carries the player id, and must\n // resolve to that one player: matching it by name too would pull in\n // everyone whose name happens to contain the same letters\n if (uuid.match(name)) return eq(schema.playerHistory.id, name);\n\n const pattern = explode(name).source;\n return sql`(\n ${schema.playerHistory.id} = ${name}\n OR ${schema.playerHistory.name} REGEXP ${pattern}\n OR ${schema.playerHistory.displayName} REGEXP ${pattern}\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 ${pattern}\n OR json_extract(json_each.value, '$.displayName') REGEXP ${pattern})\n )\n )`;\n });\n\n return this.db\n .select({\n id: schema.playerHistory.id,\n name: schema.playerHistory.name,\n displayName: schema.playerHistory.displayName,\n })\n .from(schema.playerHistory)\n .where(clauses.length > 1 ? or(...clauses) : clauses[0])\n .limit(25)\n .all();\n }\n\n /**\n * Player ids currently holding any of the named roles, matched without\n * regard to case.\n *\n * Role assignments are the server's present-day config, not a record of who\n * held what when: someone promoted yesterday matches everything they ever\n * said, and someone demoted matches none of it.\n */\n private resolveRoleMembers(roleNames: string[]) {\n const wanted = new Set(roleNames.map(r => r.toLowerCase()));\n const assignments =\n this.omegga.getRoleAssignments?.()?.savedPlayerRoles ?? {};\n\n return Object.entries(assignments)\n .filter(([, entry]) =>\n (entry?.roles ?? []).some(role => wanted.has(role.toLowerCase())),\n )\n .map(([id]) => id);\n }\n\n /**\n * Search the chat log with the query language in {@link parseChatQuery}.\n * Terms are ANDed and match anywhere in a message.\n *\n * A query with words in it searches chat messages, since nothing else has\n * words to match. A query built only of filters scopes the log instead, and\n * returns joins, leaves and server entries alongside messages. `action:`\n * overrides either way.\n *\n * Paginates on `created` alone, as {@link getChats} does, so two entries\n * logged in the same millisecond can straddle a page boundary.\n */\n async searchChats({\n query,\n count: limit = 50,\n cursor,\n sort = 'newest',\n }: {\n query: string;\n count?: number;\n cursor?: number;\n sort?: 'newest' | 'oldest';\n }) {\n const parsed = parseChatQuery(query);\n const empty = {\n chats: [] as Awaited<ReturnType<Database['getChats']>>,\n senders: [] as { id: string; name: string; displayName: string }[],\n filters: parsed.filters,\n hasMore: false,\n total: 0,\n totalCapped: false,\n context: {} as Record<\n string,\n {\n before: Awaited<ReturnType<Database['getChatContext']>>;\n after: Awaited<ReturnType<Database['getChatContext']>>;\n }\n >,\n };\n if (isEmptyChatQuery(parsed)) return empty;\n\n // searching for words only makes sense against something that has words,\n // so terms imply messages. a query built purely of filters is scoping the\n // log rather than searching it, and keeps joins, leaves and server entries\n const actions: ChatSearchAction[] =\n parsed.actions.length > 0\n ? parsed.actions\n : parsed.terms.length > 0\n ? ['msg']\n : ['msg', 'server', 'leave', 'join', 'crash'];\n const byAction = inArray(schema.chatLogs.action, actions);\n const conditions: SQL[] = [\n // crashes were logged as ordinary server entries before they had an\n // action of their own, so searching for them has to reach back to those\n // too or a server's existing history looks crash-free\n actions.includes('crash')\n ? or(\n byAction,\n and(\n eq(schema.chatLogs.action, 'server'),\n inArray(schema.chatLogs.message, LEGACY_CRASH_MESSAGES),\n ),\n )!\n : byAction,\n ];\n\n let senders: { id: string; name: string; displayName: string }[] = [];\n if (parsed.from.length > 0) {\n senders = this.resolveChatSenders(parsed.from);\n // a from: nobody matches must return nothing, not everything\n if (senders.length === 0) return empty;\n conditions.push(\n inArray(\n sql`json_extract(${schema.chatLogs.user}, '$.id')`,\n senders.map(s => s.id),\n ),\n );\n }\n\n if (parsed.roles.length > 0) {\n const members = this.resolveRoleMembers(parsed.roles);\n // a role nobody holds must match nothing, not everything\n if (members.length === 0) return empty;\n conditions.push(\n inArray(sql`json_extract(${schema.chatLogs.user}, '$.id')`, members),\n );\n }\n\n if (parsed.adminAny || parsed.admin.length > 0) {\n // omegga stamps `web` on anything it sends itself and stores the name\n // behind it: a web user's login, or SERVER for the console. the id is\n // matched too so a pasted player id works as it does for from:\n const web = sql`json_extract(${schema.chatLogs.user}, '$.web')`;\n const name = sql`lower(json_extract(${schema.chatLogs.user}, '$.name'))`;\n const id = sql`json_extract(${schema.chatLogs.user}, '$.id')`;\n conditions.push(\n parsed.admin.length === 0\n ? sql`${web} = 1`\n : and(\n sql`${web} = 1`,\n or(\n inArray(\n name,\n parsed.admin.map(a => a.toLowerCase()),\n ),\n inArray(id, parsed.admin),\n ),\n )!,\n );\n }\n\n for (const term of parsed.terms) {\n conditions.push(\n sql`${schema.chatLogs.message} LIKE ${likeAnywhere(term)} ESCAPE '\\\\'`,\n );\n }\n\n if (parsed.before)\n conditions.push(lt(schema.chatLogs.created, parsed.before));\n if (parsed.after)\n conditions.push(gt(schema.chatLogs.created, parsed.after));\n\n // the total counts every match, so it is taken before the cursor narrows\n // the query to one page. counting stops at the cap: on a large log a\n // common word can match most of the table, and an exact total is not worth\n // scanning all of it\n const cappedMatches = this.db\n .select({ one: sql`1` })\n .from(schema.chatLogs)\n .where(and(...conditions))\n .limit(SEARCH_COUNT_CAP + 1)\n .as('capped');\n const counted =\n this.db.select({ total: count() }).from(cappedMatches).get()?.total ?? 0;\n\n const newest = sort === 'newest';\n if (cursor !== undefined) {\n conditions.push(\n newest\n ? lt(schema.chatLogs.created, cursor)\n : gt(schema.chatLogs.created, cursor),\n );\n }\n\n // one extra row answers whether another page exists without a second count\n const rows = this.db\n .select()\n .from(schema.chatLogs)\n .where(and(...conditions))\n .orderBy((newest ? desc : asc)(schema.chatLogs.created))\n .limit(limit + 1)\n .all();\n\n const hasMore = rows.length > limit;\n const page = rows.slice(0, limit);\n\n // one neighbour each way ships with the results so every match arrives\n // with a line of context. these are primary key seeks, so the loop costs\n // far less than the round trip per result it saves\n const context: (typeof empty)['context'] = {};\n for (const row of page) {\n context[String(row.id)] = {\n before: await this.getChatContext({\n id: row.id,\n direction: 'before',\n count: 1,\n }),\n after: await this.getChatContext({\n id: row.id,\n direction: 'after',\n count: 1,\n }),\n };\n }\n\n return {\n context,\n total: Math.min(counted, SEARCH_COUNT_CAP),\n totalCapped: counted > SEARCH_COUNT_CAP,\n chats: page.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 senders,\n filters: parsed.filters,\n hasMore,\n };\n }\n\n /**\n * The entries immediately before or after one chat log row, for expanding a\n * search result outward a few lines at a time. Keyed on the row id rather\n * than `created` so entries logged in the same millisecond stay in insertion\n * order and none are skipped or repeated while paging outward.\n *\n * Returns oldest first in both directions. A short result means that end of\n * the log has been reached.\n */\n async getChatContext({\n id,\n direction,\n count = 5,\n }: {\n id: number;\n direction: 'before' | 'after';\n count?: number;\n }) {\n const rows =\n direction === 'before'\n ? this.db\n .select()\n .from(schema.chatLogs)\n .where(lt(schema.chatLogs.id, id))\n .orderBy(desc(schema.chatLogs.id))\n .limit(count)\n .all()\n .reverse()\n : this.db\n .select()\n .from(schema.chatLogs)\n .where(gt(schema.chatLogs.id, id))\n .orderBy(asc(schema.chatLogs.id))\n .limit(count)\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 // the search pattern matches letters in order anywhere in a name, so \"ori\"\n // finds pAlaCe_Echo as readily as Acetone. completion needs the name someone\n // is most plausibly typing first, which is the closest match, not the most\n // recently seen one\n const relevance =\n sort === 'relevance' && search.length > 0\n ? sql`CASE\n WHEN ${schema.playerHistory.name} = ${search} COLLATE NOCASE THEN 0\n WHEN ${schema.playerHistory.displayName} = ${search} COLLATE NOCASE THEN 1\n WHEN ${schema.playerHistory.name} LIKE ${likePrefix(search)} ESCAPE '\\\\' THEN 2\n WHEN ${schema.playerHistory.displayName} LIKE ${likePrefix(search)} ESCAPE '\\\\' THEN 3\n WHEN ${schema.playerHistory.name} LIKE ${likeAnywhere(search)} ESCAPE '\\\\' THEN 4\n WHEN ${schema.playerHistory.displayName} LIKE ${likeAnywhere(search)} ESCAPE '\\\\' THEN 5\n ELSE 6\n END`\n : null;\n\n const totalResult = this.db\n .select({ count: count() })\n .from(schema.playerHistory)\n .where(baseWhere)\n .get();\n // counted per row rather than joined, so the subquery runs for the page\n // being shown unless the sort itself needs every count\n // built rather than written out: the query builder qualifies both tables,\n // which a hand-written subquery has to do itself or its bare \"id\" resolves\n // to chat_logs.id and matches nobody. json_extract stays raw because\n // drizzle has no helper for sqlite's json functions\n const messageCount = sql<number>`${this.db\n .select({ n: count() })\n .from(schema.chatLogs)\n .where(\n and(\n eq(\n sql`json_extract(${schema.chatLogs.user}, '$.id')`,\n schema.playerHistory.id,\n ),\n eq(schema.chatLogs.action, 'msg'),\n ),\n )}`;\n\n const players = this.db\n .select({\n id: schema.playerHistory.id,\n name: schema.playerHistory.name,\n displayName: schema.playerHistory.displayName,\n nameHistory: schema.playerHistory.nameHistory,\n ips: schema.playerHistory.ips,\n created: schema.playerHistory.created,\n lastSeen: schema.playerHistory.lastSeen,\n lastInstanceId: schema.playerHistory.lastInstanceId,\n heartbeats: schema.playerHistory.heartbeats,\n sessions: schema.playerHistory.sessions,\n instances: schema.playerHistory.instances,\n messageCount,\n })\n .from(schema.playerHistory)\n .where(baseWhere)\n .orderBy(\n ...(relevance\n ? [relevance, desc(schema.playerHistory.lastSeen)]\n : sort === 'messages'\n ? [orderDir(messageCount)]\n : [orderDir(sortCol)]),\n )\n .limit(limit)\n .offset(limit * page)\n .all();\n\n const total = totalResult?.count ?? 0;\n const result = players.map(p => ({\n ...this._toUserHistory(p),\n messageCount: p.messageCount,\n }));\n\n // exact match detection\n if (search.length > 0 && page === 0) {\n const exactRows = this.db\n .select({ row: schema.playerHistory, messageCount })\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.row.id);\n if (idx >= 0) result.splice(idx, 1);\n }\n result.splice(\n 0,\n 0,\n ...exactRows.map(r => ({\n ...this._toUserHistory(r.row),\n messageCount: r.messageCount,\n })),\n );\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","sql","serverEvents","permissions","schema.serverConfig","RootLevel","or","and","schema.webRoles","desc","lt","gt","asc","uuid.match","schema.playerHistory","pattern","explode","parseChatQuery","isEmptyChatQuery","inArray","schema.banHistory","schema.kickHistory","schema.playerNotes","schema.heartbeats","config","time","schema.punchcards"],"mappings":"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"}
|
|
@@ -40,6 +40,27 @@ const chatRouter = trpc.router({
|
|
|
40
40
|
const { database } = trpc.getContextDeps();
|
|
41
41
|
return database.getChats({ after: input.after, before: input.before });
|
|
42
42
|
}),
|
|
43
|
+
search: trpc.protectedProcedure(scopes.ScopeName.ChatHistory).input(
|
|
44
|
+
v4.z.object({
|
|
45
|
+
query: v4.z.string().max(256),
|
|
46
|
+
// `created` of the last entry on the previous page
|
|
47
|
+
cursor: v4.z.number().optional(),
|
|
48
|
+
sort: v4.z.enum(["newest", "oldest"]).optional()
|
|
49
|
+
})
|
|
50
|
+
).query(({ input }) => {
|
|
51
|
+
const { database } = trpc.getContextDeps();
|
|
52
|
+
return database.searchChats(input);
|
|
53
|
+
}),
|
|
54
|
+
context: trpc.protectedProcedure(scopes.ScopeName.ChatHistory).input(
|
|
55
|
+
v4.z.object({
|
|
56
|
+
id: v4.z.number(),
|
|
57
|
+
direction: v4.z.enum(["before", "after"]),
|
|
58
|
+
count: v4.z.number().min(1).max(25).optional()
|
|
59
|
+
})
|
|
60
|
+
).query(({ input }) => {
|
|
61
|
+
const { database } = trpc.getContextDeps();
|
|
62
|
+
return database.getChatContext(input);
|
|
63
|
+
}),
|
|
43
64
|
calendar: trpc.protectedProcedure(scopes.ScopeName.ChatCalendar).query(() => {
|
|
44
65
|
const { database } = trpc.getContextDeps();
|
|
45
66
|
return database.calendar.years;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chat.js","sources":["../../../../src/webserver/backend/router/chat.ts"],"sourcesContent":["import { z } from 'zod/v4';\nimport { on } from 'events';\nimport { router, protectedProcedure, getContextDeps } from '../trpc';\nimport { ScopeName } from '../scopes';\nimport { serverEvents } from '../events';\nimport Logger from '@/logger';\nimport { parseLinks, sanitize } from '@util/chat';\n\nexport const chatRouter = router({\n chat: router({\n send: protectedProcedure(ScopeName.ChatSend)\n .input(z.string())\n .mutation(async ({ input: message, ctx }) => {\n const { database, omegga } = getContextDeps();\n\n if (typeof message !== 'string') return;\n if (message.length > 140) message = message.slice(0, 140);\n\n const user = {\n name: ctx.user.username || 'Admin',\n id: ctx.user.playerId,\n color: 'ff00ff',\n web: true,\n };\n\n const chatLog = await database.addChatLog('msg', user, message);\n serverEvents.emit('chat', chatLog);\n\n omegga.broadcast(\n `\"[<b><color=\\\\\"ff00ff\\\\\">${user.name}</></>]: ${parseLinks(sanitize(message))}\"`,\n );\n Logger.log(`[${user.name.brightMagenta.underline}]: ${message}`);\n\n return 'ok';\n }),\n\n recent: protectedProcedure(ScopeName.ChatRecent).query(() => {\n const { database } = getContextDeps();\n return database.getChats({ sameServer: true });\n }),\n\n history: protectedProcedure(ScopeName.ChatHistory)\n .input(\n z.object({\n after: z.number().optional(),\n before: z.number().optional(),\n }),\n )\n .query(({ input }) => {\n const { database } = getContextDeps();\n return database.getChats({ after: input.after, before: input.before });\n }),\n\n calendar: protectedProcedure(ScopeName.ChatCalendar).query(() => {\n const { database } = getContextDeps();\n return database.calendar.years;\n }),\n\n onMessage: protectedProcedure(ScopeName.ChatRecent).subscription(\n async function* ({ signal, ctx }) {\n const combined = AbortSignal.any([signal!, ctx.userAbort().signal]);\n for await (const [chatLog] of on(serverEvents, 'chat', {\n signal: combined,\n })) {\n yield chatLog;\n }\n },\n ),\n }),\n});\n"],"names":["router","protectedProcedure","ScopeName","z","getContextDeps","serverEvents","parseLinks","sanitize","Logger","on"],"mappings":";;;;;;;;;AAQO,MAAM,aAAaA,KAAAA,OAAO;AAAA,EAC/B,MAAMA,KAAAA,OAAO;AAAA,IACX,MAAMC,KAAAA,mBAAmBC,OAAAA,UAAU,QAAQ,EACxC,MAAMC,GAAAA,EAAE,OAAA,CAAQ,EAChB,SAAS,OAAO,EAAE,OAAO,SAAS,UAAU;AAC3C,YAAM,EAAE,UAAU,OAAA,IAAWC,oBAAA;AAE7B,UAAI,OAAO,YAAY,SAAU;AACjC,UAAI,QAAQ,SAAS,eAAe,QAAQ,MAAM,GAAG,GAAG;AAExD,YAAM,OAAO;AAAA,QACX,MAAM,IAAI,KAAK,YAAY;AAAA,QAC3B,IAAI,IAAI,KAAK;AAAA,QACb,OAAO;AAAA,QACP,KAAK;AAAA,MAAA;AAGP,YAAM,UAAU,MAAM,SAAS,WAAW,OAAO,MAAM,OAAO;AAC9DC,0BAAa,KAAK,QAAQ,OAAO;AAEjC,aAAO;AAAA,QACL,4BAA4B,KAAK,IAAI,YAAYC,KAAAA,WAAWC,KAAAA,SAAS,OAAO,CAAC,CAAC;AAAA,MAAA;AAEhFC,aAAAA,QAAO,IAAI,IAAI,KAAK,KAAK,cAAc,SAAS,MAAM,OAAO,EAAE;AAE/D,aAAO;AAAA,IACT,CAAC;AAAA,IAEH,QAAQP,KAAAA,mBAAmBC,OAAAA,UAAU,UAAU,EAAE,MAAM,MAAM;AAC3D,YAAM,EAAE,SAAA,IAAaE,oBAAA;AACrB,aAAO,SAAS,SAAS,EAAE,YAAY,MAAM;AAAA,IAC/C,CAAC;AAAA,IAED,SAASH,KAAAA,mBAAmBC,iBAAU,WAAW,EAC9C;AAAA,MACCC,GAAAA,EAAE,OAAO;AAAA,QACP,OAAOA,GAAAA,EAAE,OAAA,EAAS,SAAA;AAAA,QAClB,QAAQA,GAAAA,EAAE,OAAA,EAAS,SAAA;AAAA,MAAS,CAC7B;AAAA,IAAA,EAEF,MAAM,CAAC,EAAE,YAAY;AACpB,YAAM,EAAE,SAAA,IAAaC,oBAAA;AACrB,aAAO,SAAS,SAAS,EAAE,OAAO,MAAM,OAAO,QAAQ,MAAM,QAAQ;AAAA,IACvE,CAAC;AAAA,IAEH,UAAUH,KAAAA,mBAAmBC,OAAAA,UAAU,YAAY,EAAE,MAAM,MAAM;AAC/D,YAAM,EAAE,SAAA,IAAaE,oBAAA;AACrB,aAAO,SAAS,SAAS;AAAA,IAC3B,CAAC;AAAA,IAED,WAAWH,KAAAA,mBAAmBC,iBAAU,UAAU,EAAE;AAAA,MAClD,iBAAiB,EAAE,QAAQ,OAAO;AAChC,cAAM,WAAW,YAAY,IAAI,CAAC,QAAS,IAAI,YAAY,MAAM,CAAC;AAClE,yBAAiB,CAAC,OAAO,KAAKO,aAAAA,GAAGJ,OAAAA,cAAc,QAAQ;AAAA,UACrD,QAAQ;AAAA,QAAA,CACT,GAAG;AACF,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IAAA;AAAA,EACF,CACD;AACH,CAAC;;"}
|
|
1
|
+
{"version":3,"file":"chat.js","sources":["../../../../src/webserver/backend/router/chat.ts"],"sourcesContent":["import { z } from 'zod/v4';\nimport { on } from 'events';\nimport { router, protectedProcedure, getContextDeps } from '../trpc';\nimport { ScopeName } from '../scopes';\nimport { serverEvents } from '../events';\nimport Logger from '@/logger';\nimport { parseLinks, sanitize } from '@util/chat';\n\nexport const chatRouter = router({\n chat: router({\n send: protectedProcedure(ScopeName.ChatSend)\n .input(z.string())\n .mutation(async ({ input: message, ctx }) => {\n const { database, omegga } = getContextDeps();\n\n if (typeof message !== 'string') return;\n if (message.length > 140) message = message.slice(0, 140);\n\n const user = {\n name: ctx.user.username || 'Admin',\n id: ctx.user.playerId,\n color: 'ff00ff',\n web: true,\n };\n\n const chatLog = await database.addChatLog('msg', user, message);\n serverEvents.emit('chat', chatLog);\n\n omegga.broadcast(\n `\"[<b><color=\\\\\"ff00ff\\\\\">${user.name}</></>]: ${parseLinks(sanitize(message))}\"`,\n );\n Logger.log(`[${user.name.brightMagenta.underline}]: ${message}`);\n\n return 'ok';\n }),\n\n recent: protectedProcedure(ScopeName.ChatRecent).query(() => {\n const { database } = getContextDeps();\n return database.getChats({ sameServer: true });\n }),\n\n history: protectedProcedure(ScopeName.ChatHistory)\n .input(\n z.object({\n after: z.number().optional(),\n before: z.number().optional(),\n }),\n )\n .query(({ input }) => {\n const { database } = getContextDeps();\n return database.getChats({ after: input.after, before: input.before });\n }),\n\n search: protectedProcedure(ScopeName.ChatHistory)\n .input(\n z.object({\n query: z.string().max(256),\n // `created` of the last entry on the previous page\n cursor: z.number().optional(),\n sort: z.enum(['newest', 'oldest']).optional(),\n }),\n )\n .query(({ input }) => {\n const { database } = getContextDeps();\n return database.searchChats(input);\n }),\n\n context: protectedProcedure(ScopeName.ChatHistory)\n .input(\n z.object({\n id: z.number(),\n direction: z.enum(['before', 'after']),\n count: z.number().min(1).max(25).optional(),\n }),\n )\n .query(({ input }) => {\n const { database } = getContextDeps();\n return database.getChatContext(input);\n }),\n\n calendar: protectedProcedure(ScopeName.ChatCalendar).query(() => {\n const { database } = getContextDeps();\n return database.calendar.years;\n }),\n\n onMessage: protectedProcedure(ScopeName.ChatRecent).subscription(\n async function* ({ signal, ctx }) {\n const combined = AbortSignal.any([signal!, ctx.userAbort().signal]);\n for await (const [chatLog] of on(serverEvents, 'chat', {\n signal: combined,\n })) {\n yield chatLog;\n }\n },\n ),\n }),\n});\n"],"names":["router","protectedProcedure","ScopeName","z","getContextDeps","serverEvents","parseLinks","sanitize","Logger","on"],"mappings":"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"}
|
|
@@ -124,6 +124,7 @@ const playerRouter = trpc.router({
|
|
|
124
124
|
nameHistory,
|
|
125
125
|
banHistory,
|
|
126
126
|
kickHistory,
|
|
127
|
+
messageCount: await database.getPlayerMessageCount(id),
|
|
127
128
|
seenAgo: now - entry.lastSeen,
|
|
128
129
|
createdAgo: now - entry.created,
|
|
129
130
|
isHost: omegga.getHostId() === id,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"player.js","sources":["../../../../src/webserver/backend/router/player.ts"],"sourcesContent":["import Player from '@omegga/player';\nimport { rgbToHex } from '@util/color';\nimport { parseBrickadiaTime } from '@util/time';\nimport * as uuid from '@util/uuid';\nimport _ from 'lodash';\nimport { z } from 'zod/v4';\nimport { ScopeName } from '../scopes';\nimport { getContextDeps, protectedProcedure, router } from '../trpc';\nimport { waitForEvent } from '../util';\n\nexport const playerRouter = router({\n player: router({\n list: protectedProcedure(ScopeName.PlayerList)\n .input(\n z.object({\n page: z.number().optional().default(0),\n search: z.string().optional().default(''),\n sort: z.string().optional().default('name'),\n direction: z.number().optional().default(1),\n filter: z.string().optional().default(''),\n }),\n )\n .query(async ({ input }) => {\n const { database, omegga } = getContextDeps();\n const { page, search, sort, direction, filter } = input;\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n const now = Date.now();\n let limitId: string[] | undefined;\n if (filter === 'banned') {\n limitId = Object.keys(banList).filter(\n b =>\n banList[b].expires <= banList[b].created ||\n parseBrickadiaTime(banList[b].expires) > Date.now(),\n );\n }\n const resp = await database.getPlayers({\n page,\n search,\n sort,\n direction,\n limitId,\n });\n const players = resp.players.map(player => {\n const foundBan = banList[player.id];\n let ban: {\n bannerId: string;\n reason: string;\n created: number;\n expires: number;\n duration: number;\n remainingTime: number;\n bannerName: string;\n } | null = null;\n if (foundBan) {\n const created = parseBrickadiaTime(foundBan.created);\n const expires = parseBrickadiaTime(foundBan.expires);\n const duration = expires - created;\n if (!(expires < now && duration > 0)) {\n ban = {\n bannerId: foundBan.bannerId,\n reason: foundBan.reason,\n created,\n expires,\n duration,\n remainingTime: expires - now,\n bannerName: _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', foundBan.bannerId],\n '',\n ),\n };\n }\n }\n return {\n ...player,\n seenAgo: now - player.lastSeen,\n createdAgo: now - player.created,\n ban,\n };\n });\n return { ...resp, players };\n }),\n\n get: protectedProcedure(ScopeName.PlayerGet)\n .input(z.string())\n .query(async ({ input: id }) => {\n const { database, omegga } = getContextDeps();\n const entry = await database.getPlayer(id);\n if (!entry) return null;\n const now = Date.now();\n const nameHistory = entry.nameHistory.map(n => ({\n ...n,\n ago: now - n.date,\n }));\n const banHistory = entry.banHistory.map(b => ({\n ...b,\n duration: (b.expires as number) - (b.created as number),\n bannerName: _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', b.bannerId],\n '',\n ) as string,\n }));\n const kickHistory = entry.kickHistory.map(b => ({\n ...b,\n kickerName: _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', b.kickerId],\n '',\n ) as string,\n }));\n const playerRoles = Player.getRoles(omegga, id) || [];\n const { roles: serverRoles = [] } = omegga.getRoleSetup() ?? {};\n const foundBan = (omegga.getBanList() || { banList: {} }).banList[id];\n let currentBan: {\n bannerId: string;\n reason: string;\n created: number;\n expires: number;\n duration: number;\n remainingTime: number;\n bannerName: string;\n } | null = null;\n if (foundBan) {\n const created = parseBrickadiaTime(foundBan.created);\n const expires = parseBrickadiaTime(foundBan.expires);\n const duration = expires - created;\n if (!(expires < now && duration > 0)) {\n currentBan = {\n bannerId: foundBan.bannerId,\n reason: foundBan.reason,\n created,\n expires,\n duration,\n remainingTime: expires - now,\n bannerName: _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', foundBan.bannerId],\n '',\n ),\n };\n }\n }\n return {\n ...entry,\n nameHistory,\n banHistory,\n kickHistory,\n seenAgo: now - entry.lastSeen,\n createdAgo: now - entry.created,\n isHost: omegga.getHostId() === id,\n isOnline: omegga.players.some(p => p.id === id),\n currentBan,\n roles: playerRoles.map(r => {\n let color = 'ffffff';\n const role = serverRoles.find(\n sr => sr.name.toLowerCase() === r.toLowerCase(),\n );\n if (role && role.bHasColor) color = rgbToHex(role.color);\n return { name: r, color };\n }),\n };\n }),\n\n ban: protectedProcedure(ScopeName.PlayerBan)\n .input(\n z.object({\n id: z.string(),\n duration: z.number().optional().default(-1),\n reason: z.string().max(128).optional().default('No Reason'),\n }),\n )\n .mutation(async ({ input, ctx }) => {\n const { database, omegga } = getContextDeps();\n const { id, duration } = input;\n let { reason } = input;\n if (typeof duration !== 'number') return false;\n if (!uuid.match(id)) return false;\n reason = reason.replace(/\\n/g, ' ').replace(/\"/g, \"'\");\n ctx.log(\n 'Banning player',\n omegga.getNameCache()?.savedPlayerNames?.[id].yellow ??\n 'with id ' + id.yellow,\n );\n omegga.writeln(\n `${omegga.Console.Chat.Command} /Ban \"${id}\" ${duration} \"${reason}\"`,\n );\n const ban = await waitForEvent(database, 'update.bans', () => {\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n if (!banList[id]) return false;\n return banList[id];\n });\n if (ban) {\n database.upsertBanHistory({\n banned: id,\n bannerId: null,\n created: parseBrickadiaTime(ban.created),\n expires: parseBrickadiaTime(ban.expires),\n reason: ban.reason,\n });\n }\n return !!ban;\n }),\n\n kick: protectedProcedure(ScopeName.PlayerKick)\n .input(\n z.object({\n id: z.string(),\n reason: z.string().max(128).optional().default('No Reason'),\n }),\n )\n .mutation(async ({ input, ctx }) => {\n const { database, omegga } = getContextDeps();\n const { id } = input;\n let { reason } = input;\n if (!uuid.match(id)) return false;\n reason = reason.replace(/\\n/g, ' ').replace(/\"/g, \"'\");\n const player = omegga.players.find(p => p.id === id);\n if (!player) return false;\n ctx.log('Kicking player', player.name.yellow);\n omegga.writeln(\n `${omegga.Console.Chat.Command} /Kick \"${id}\" \"${reason}\"`,\n );\n const ok = await waitForEvent(\n omegga,\n 'leave',\n (leavingPlayer: { id: string }) => leavingPlayer.id === id,\n );\n if (ok) {\n database.upsertKickHistory({\n kicked: id,\n kickerId: null,\n created: Date.now(),\n reason,\n });\n }\n return ok;\n }),\n\n unban: protectedProcedure(ScopeName.PlayerUnban)\n .input(z.object({ id: z.string() }))\n .mutation(async ({ input, ctx }) => {\n const { database, omegga } = getContextDeps();\n const { id } = input;\n if (!uuid.match(id)) return false;\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n if (!banList[id]) return false;\n ctx.log(\n 'Unbanning player',\n omegga.getNameCache()?.savedPlayerNames?.[id].yellow ??\n 'with id ' + id.yellow,\n );\n omegga.writeln(`${omegga.Console.Chat.Command} /Unban \"${id}\"`);\n const ok = await waitForEvent(database, 'update.bans', () => {\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n return (\n !banList[id] || parseBrickadiaTime(banList[id].expires) < Date.now()\n );\n });\n return ok;\n }),\n\n clearBricks: protectedProcedure(ScopeName.PlayerClearBricks)\n .input(z.object({ id: z.string() }))\n .mutation(async ({ input, ctx }) => {\n const { omegga } = getContextDeps();\n const { id } = input;\n if (!uuid.match(id)) return false;\n ctx.log(\n 'Clearing bricks for player',\n omegga.getNameCache()?.savedPlayerNames?.[id]?.yellow ??\n 'with id ' + id.yellow,\n );\n omegga.writeln(`${omegga.Console.Bricks.Clear} \"${id}\"`);\n return true;\n }),\n\n roles: router({\n list: protectedProcedure(ScopeName.PlayerList).query(() => {\n const { omegga } = getContextDeps();\n return _.sortBy(omegga.getRoleSetup()?.roles ?? [], p =>\n p.name.toLowerCase(),\n );\n }),\n }),\n }),\n});\n"],"names":["router","protectedProcedure","ScopeName","z","getContextDeps","parseBrickadiaTime","player","Player","color","rgbToHex","uuid.match","waitForEvent","banList"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"player.js","sources":["../../../../src/webserver/backend/router/player.ts"],"sourcesContent":["import Player from '@omegga/player';\nimport { rgbToHex } from '@util/color';\nimport { parseBrickadiaTime } from '@util/time';\nimport * as uuid from '@util/uuid';\nimport _ from 'lodash';\nimport { z } from 'zod/v4';\nimport { ScopeName } from '../scopes';\nimport { getContextDeps, protectedProcedure, router } from '../trpc';\nimport { waitForEvent } from '../util';\n\nexport const playerRouter = router({\n player: router({\n list: protectedProcedure(ScopeName.PlayerList)\n .input(\n z.object({\n page: z.number().optional().default(0),\n search: z.string().optional().default(''),\n sort: z.string().optional().default('name'),\n direction: z.number().optional().default(1),\n filter: z.string().optional().default(''),\n }),\n )\n .query(async ({ input }) => {\n const { database, omegga } = getContextDeps();\n const { page, search, sort, direction, filter } = input;\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n const now = Date.now();\n let limitId: string[] | undefined;\n if (filter === 'banned') {\n limitId = Object.keys(banList).filter(\n b =>\n banList[b].expires <= banList[b].created ||\n parseBrickadiaTime(banList[b].expires) > Date.now(),\n );\n }\n const resp = await database.getPlayers({\n page,\n search,\n sort,\n direction,\n limitId,\n });\n const players = resp.players.map(player => {\n const foundBan = banList[player.id];\n let ban: {\n bannerId: string;\n reason: string;\n created: number;\n expires: number;\n duration: number;\n remainingTime: number;\n bannerName: string;\n } | null = null;\n if (foundBan) {\n const created = parseBrickadiaTime(foundBan.created);\n const expires = parseBrickadiaTime(foundBan.expires);\n const duration = expires - created;\n if (!(expires < now && duration > 0)) {\n ban = {\n bannerId: foundBan.bannerId,\n reason: foundBan.reason,\n created,\n expires,\n duration,\n remainingTime: expires - now,\n bannerName: _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', foundBan.bannerId],\n '',\n ),\n };\n }\n }\n return {\n ...player,\n seenAgo: now - player.lastSeen,\n createdAgo: now - player.created,\n ban,\n };\n });\n return { ...resp, players };\n }),\n\n get: protectedProcedure(ScopeName.PlayerGet)\n .input(z.string())\n .query(async ({ input: id }) => {\n const { database, omegga } = getContextDeps();\n const entry = await database.getPlayer(id);\n if (!entry) return null;\n const now = Date.now();\n const nameHistory = entry.nameHistory.map(n => ({\n ...n,\n ago: now - n.date,\n }));\n const banHistory = entry.banHistory.map(b => ({\n ...b,\n duration: (b.expires as number) - (b.created as number),\n bannerName: _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', b.bannerId],\n '',\n ) as string,\n }));\n const kickHistory = entry.kickHistory.map(b => ({\n ...b,\n kickerName: _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', b.kickerId],\n '',\n ) as string,\n }));\n const playerRoles = Player.getRoles(omegga, id) || [];\n const { roles: serverRoles = [] } = omegga.getRoleSetup() ?? {};\n const foundBan = (omegga.getBanList() || { banList: {} }).banList[id];\n let currentBan: {\n bannerId: string;\n reason: string;\n created: number;\n expires: number;\n duration: number;\n remainingTime: number;\n bannerName: string;\n } | null = null;\n if (foundBan) {\n const created = parseBrickadiaTime(foundBan.created);\n const expires = parseBrickadiaTime(foundBan.expires);\n const duration = expires - created;\n if (!(expires < now && duration > 0)) {\n currentBan = {\n bannerId: foundBan.bannerId,\n reason: foundBan.reason,\n created,\n expires,\n duration,\n remainingTime: expires - now,\n bannerName: _.get(\n omegga.getNameCache(),\n ['savedPlayerNames', foundBan.bannerId],\n '',\n ),\n };\n }\n }\n return {\n ...entry,\n nameHistory,\n banHistory,\n kickHistory,\n messageCount: await database.getPlayerMessageCount(id),\n seenAgo: now - entry.lastSeen,\n createdAgo: now - entry.created,\n isHost: omegga.getHostId() === id,\n isOnline: omegga.players.some(p => p.id === id),\n currentBan,\n roles: playerRoles.map(r => {\n let color = 'ffffff';\n const role = serverRoles.find(\n sr => sr.name.toLowerCase() === r.toLowerCase(),\n );\n if (role && role.bHasColor) color = rgbToHex(role.color);\n return { name: r, color };\n }),\n };\n }),\n\n ban: protectedProcedure(ScopeName.PlayerBan)\n .input(\n z.object({\n id: z.string(),\n duration: z.number().optional().default(-1),\n reason: z.string().max(128).optional().default('No Reason'),\n }),\n )\n .mutation(async ({ input, ctx }) => {\n const { database, omegga } = getContextDeps();\n const { id, duration } = input;\n let { reason } = input;\n if (typeof duration !== 'number') return false;\n if (!uuid.match(id)) return false;\n reason = reason.replace(/\\n/g, ' ').replace(/\"/g, \"'\");\n ctx.log(\n 'Banning player',\n omegga.getNameCache()?.savedPlayerNames?.[id].yellow ??\n 'with id ' + id.yellow,\n );\n omegga.writeln(\n `${omegga.Console.Chat.Command} /Ban \"${id}\" ${duration} \"${reason}\"`,\n );\n const ban = await waitForEvent(database, 'update.bans', () => {\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n if (!banList[id]) return false;\n return banList[id];\n });\n if (ban) {\n database.upsertBanHistory({\n banned: id,\n bannerId: null,\n created: parseBrickadiaTime(ban.created),\n expires: parseBrickadiaTime(ban.expires),\n reason: ban.reason,\n });\n }\n return !!ban;\n }),\n\n kick: protectedProcedure(ScopeName.PlayerKick)\n .input(\n z.object({\n id: z.string(),\n reason: z.string().max(128).optional().default('No Reason'),\n }),\n )\n .mutation(async ({ input, ctx }) => {\n const { database, omegga } = getContextDeps();\n const { id } = input;\n let { reason } = input;\n if (!uuid.match(id)) return false;\n reason = reason.replace(/\\n/g, ' ').replace(/\"/g, \"'\");\n const player = omegga.players.find(p => p.id === id);\n if (!player) return false;\n ctx.log('Kicking player', player.name.yellow);\n omegga.writeln(\n `${omegga.Console.Chat.Command} /Kick \"${id}\" \"${reason}\"`,\n );\n const ok = await waitForEvent(\n omegga,\n 'leave',\n (leavingPlayer: { id: string }) => leavingPlayer.id === id,\n );\n if (ok) {\n database.upsertKickHistory({\n kicked: id,\n kickerId: null,\n created: Date.now(),\n reason,\n });\n }\n return ok;\n }),\n\n unban: protectedProcedure(ScopeName.PlayerUnban)\n .input(z.object({ id: z.string() }))\n .mutation(async ({ input, ctx }) => {\n const { database, omegga } = getContextDeps();\n const { id } = input;\n if (!uuid.match(id)) return false;\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n if (!banList[id]) return false;\n ctx.log(\n 'Unbanning player',\n omegga.getNameCache()?.savedPlayerNames?.[id].yellow ??\n 'with id ' + id.yellow,\n );\n omegga.writeln(`${omegga.Console.Chat.Command} /Unban \"${id}\"`);\n const ok = await waitForEvent(database, 'update.bans', () => {\n const banList = (omegga.getBanList() || { banList: {} }).banList;\n return (\n !banList[id] || parseBrickadiaTime(banList[id].expires) < Date.now()\n );\n });\n return ok;\n }),\n\n clearBricks: protectedProcedure(ScopeName.PlayerClearBricks)\n .input(z.object({ id: z.string() }))\n .mutation(async ({ input, ctx }) => {\n const { omegga } = getContextDeps();\n const { id } = input;\n if (!uuid.match(id)) return false;\n ctx.log(\n 'Clearing bricks for player',\n omegga.getNameCache()?.savedPlayerNames?.[id]?.yellow ??\n 'with id ' + id.yellow,\n );\n omegga.writeln(`${omegga.Console.Bricks.Clear} \"${id}\"`);\n return true;\n }),\n\n roles: router({\n list: protectedProcedure(ScopeName.PlayerList).query(() => {\n const { omegga } = getContextDeps();\n return _.sortBy(omegga.getRoleSetup()?.roles ?? [], p =>\n p.name.toLowerCase(),\n );\n }),\n }),\n }),\n});\n"],"names":["router","protectedProcedure","ScopeName","z","getContextDeps","parseBrickadiaTime","player","Player","color","rgbToHex","uuid.match","waitForEvent","banList"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omegga",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"description": "Brickadia server installer, updater, manager, wrapper, configurator, and automator",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=23.0.0"
|
|
@@ -140,7 +140,8 @@
|
|
|
140
140
|
"prettier:fix": "npx prettier --write \"src/\" \"frontend/src/\"",
|
|
141
141
|
"start": "node --enable-source-maps index.js",
|
|
142
142
|
"test:backend": "vitest --config vitest.backend.config.mts --dir ./src",
|
|
143
|
-
"test": "
|
|
143
|
+
"test:frontend": "vitest --config vitest.frontend.config.mts --dir ./frontend/src",
|
|
144
|
+
"test": "npm run test:backend && npm run test:frontend",
|
|
144
145
|
"typecheck": "npm run typecheck:backend && npm run typecheck:frontend",
|
|
145
146
|
"typecheck:backend": "npx tsc --noEmit -p src",
|
|
146
147
|
"typecheck:frontend": "npx tsc --noEmit -p frontend",
|
|
@@ -156,4 +157,4 @@
|
|
|
156
157
|
"url": "https://github.com/brickadia-community/omegga/issues"
|
|
157
158
|
},
|
|
158
159
|
"homepage": "https://github.com/brickadia-community/omegga#readme"
|
|
159
|
-
}
|
|
160
|
+
}
|