summary refs log tree commit diff
diff options
context:
space:
mode:
authorMathMan05 <mathmanrm@gmail.com>2025-11-26 15:56:25 -0600
committerMathMan05 <mathmanrm@gmail.com>2025-11-26 15:56:25 -0600
commitb19e3d84191d2c7dcefd0f9b00885fa8e127502d (patch)
tree8b334d4be490cd211dceaa2ae79d6a8595cc3e65
parentremove remove (diff)
parentMerge branch 'spacebarchat:master' into removeStupidPolies (diff)
downloadserver-ts-b19e3d84191d2c7dcefd0f9b00885fa8e127502d.tar.xz
Merge branch 'removeStupidPolies' of https://github.com/MathMan05/server into removeStupidPolies
-rw-r--r--.idea/data_source_mapping.xml6
-rw-r--r--.idea/workspace.xml95
-rw-r--r--assets/openapi.json184
-rw-r--r--assets/schemas.json423
-rw-r--r--scripts/openapi.js3
-rw-r--r--scripts/util/getRouteDescriptions.js3
-rw-r--r--src/api/routes/users/@me/mentions.ts206
-rw-r--r--src/schemas/api/users/Member.ts12
-rw-r--r--src/util/entities/Member.ts6
-rw-r--r--src/util/util/Token.ts26
10 files changed, 686 insertions, 278 deletions
diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml
deleted file mode 100644

index c2a7ad0f..00000000 --- a/.idea/data_source_mapping.xml +++ /dev/null
@@ -1,6 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<project version="4"> - <component name="DataSourcePerFileMappings"> - <file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/61e66448-285a-4205-a1d7-c2704d5afb2c/console.sql" value="61e66448-285a-4205-a1d7-c2704d5afb2c" /> - </component> -</project> \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml
index 11e173ef..f43b9986 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml
@@ -43,46 +43,46 @@ <option name="hideEmptyMiddlePackages" value="true" /> <option name="showLibraryContents" value="true" /> </component> - <component name="PropertiesComponent">{ - &quot;keyToString&quot;: { - &quot;NIXITCH_NIXPKGS_CONFIG&quot;: &quot;/etc/nix/nixpkgs-config.nix&quot;, - &quot;NIXITCH_NIX_CONF_DIR&quot;: &quot;&quot;, - &quot;NIXITCH_NIX_OTHER_STORES&quot;: &quot;&quot;, - &quot;NIXITCH_NIX_PATH&quot;: &quot;/home/Rory/.nix-defexpr/channels:nixpkgs=/nix/store/wb6agba4kfsxpbnb5hzlq58vkjzvbsk6-source&quot;, - &quot;NIXITCH_NIX_PROFILES&quot;: &quot;/run/current-system/sw /nix/var/nix/profiles/default /etc/profiles/per-user/Rory /home/Rory/.local/state/nix/profile /nix/profile /home/Rory/.nix-profile&quot;, - &quot;NIXITCH_NIX_REMOTE&quot;: &quot;&quot;, - &quot;NIXITCH_NIX_USER_PROFILE_DIR&quot;: &quot;/nix/var/nix/profiles/per-user/Rory&quot;, - &quot;Node.js.Server.ts.executor&quot;: &quot;Debug&quot;, - &quot;RunOnceActivity.ShowReadmeOnStart&quot;: &quot;true&quot;, - &quot;RunOnceActivity.git.unshallow&quot;: &quot;true&quot;, - &quot;javascript.nodejs.core.library.configured.version&quot;: &quot;24.8.0&quot;, - &quot;javascript.nodejs.core.library.typings.version&quot;: &quot;24.7.0&quot;, - &quot;last_opened_file_path&quot;: &quot;/home/Rory/git/spacebar/server-master/src/util/migration/postgres&quot;, - &quot;node.js.detected.package.eslint&quot;: &quot;true&quot;, - &quot;node.js.selected.package.eslint&quot;: &quot;(autodetect)&quot;, - &quot;node.js.selected.package.tslint&quot;: &quot;(autodetect)&quot;, - &quot;nodejs_interpreter_path&quot;: &quot;node&quot;, - &quot;nodejs_package_manager_path&quot;: &quot;npm&quot;, - &quot;npm.Start API.executor&quot;: &quot;Run&quot;, - &quot;npm.Start CDN.executor&quot;: &quot;Run&quot;, - &quot;npm.Start Gateway.executor&quot;: &quot;Run&quot;, - &quot;npm.build.executor&quot;: &quot;Run&quot;, - &quot;npm.start.executor&quot;: &quot;Debug&quot;, - &quot;prettierjs.PrettierConfiguration.Package&quot;: &quot;/home/Rory/git/spacebar/server-master/node_modules/prettier&quot;, - &quot;settings.editor.selected.configurable&quot;: &quot;preferences.pluginManager&quot;, - &quot;ts.external.directory.path&quot;: &quot;/home/Rory/git/spacebar/server-master/node_modules/typescript/lib&quot; + <component name="PropertiesComponent"><![CDATA[{ + "keyToString": { + "NIXITCH_NIXPKGS_CONFIG": "/etc/nix/nixpkgs-config.nix", + "NIXITCH_NIX_CONF_DIR": "", + "NIXITCH_NIX_OTHER_STORES": "", + "NIXITCH_NIX_PATH": "/home/Rory/.nix-defexpr/channels:nixpkgs=/nix/store/wb6agba4kfsxpbnb5hzlq58vkjzvbsk6-source", + "NIXITCH_NIX_PROFILES": "/run/current-system/sw /nix/var/nix/profiles/default /etc/profiles/per-user/Rory /home/Rory/.local/state/nix/profile /nix/profile /home/Rory/.nix-profile", + "NIXITCH_NIX_REMOTE": "", + "NIXITCH_NIX_USER_PROFILE_DIR": "/nix/var/nix/profiles/per-user/Rory", + "Node.js.Server.ts.executor": "Debug", + "RunOnceActivity.ShowReadmeOnStart": "true", + "RunOnceActivity.git.unshallow": "true", + "javascript.nodejs.core.library.configured.version": "24.8.0", + "javascript.nodejs.core.library.typings.version": "24.7.0", + "last_opened_file_path": "/home/Rory/git/spacebar/server-master/src/util/migration/postgres", + "node.js.detected.package.eslint": "true", + "node.js.selected.package.eslint": "(autodetect)", + "node.js.selected.package.tslint": "(autodetect)", + "nodejs_interpreter_path": "node", + "nodejs_package_manager_path": "npm", + "npm.Start API.executor": "Run", + "npm.Start CDN.executor": "Run", + "npm.Start Gateway.executor": "Run", + "npm.build.executor": "Run", + "npm.start.executor": "Debug", + "prettierjs.PrettierConfiguration.Package": "/home/Rory/git/spacebar/server-master/node_modules/prettier", + "settings.editor.selected.configurable": "settings.javascript.linters.tslint", + "ts.external.directory.path": "/home/Rory/git/spacebar/server-master/node_modules/typescript/lib" }, - &quot;keyToStringList&quot;: { - &quot;DatabaseDriversLRU&quot;: [ - &quot;postgresql&quot; + "keyToStringList": { + "DatabaseDriversLRU": [ + "postgresql" ], - &quot;GitStage.ChangesTree.GroupingKeys&quot;: [ - &quot;directory&quot;, - &quot;module&quot;, - &quot;repository&quot; + "GitStage.ChangesTree.GroupingKeys": [ + "directory", + "module", + "repository" ] } -}</component> +}]]></component> <component name="RecentsManager"> <key name="CopyFile.RECENT_KEYS"> <recent name="$PROJECT_DIR$/src/util/migration/postgres" /> @@ -111,14 +111,24 @@ <value> <map> <entry key="Start API" value="STOPPED" /> + <entry key="Start CDN" value="STOPPED" /> <entry key="Start Gateway" value="STOPPED" /> - <entry key="build" value="STOPPED" /> + <entry key="Start bundle" value="STOPPED" /> </map> </value> </entry> </map> </option> </component> + <component name="RunManager" selected="Compound.Start separated"> + <list> + <item itemvalue="Compound.Start separated" /> + <item itemvalue="npm.Start API" /> + <item itemvalue="npm.Start CDN" /> + <item itemvalue="npm.Start Gateway" /> + <item itemvalue="npm.Start bundle" /> + </list> + </component> <component name="SharedIndexes"> <attachedChunks> <set> @@ -155,6 +165,17 @@ <component name="TypeScriptGeneratedFilesManager"> <option name="version" value="3" /> </component> + <component name="Vcs.Log.Tabs.Properties"> + <option name="TAB_STATES"> + <map> + <entry key="MAIN"> + <value> + <State /> + </value> + </entry> + </map> + </option> + </component> <component name="XDebuggerManager"> <breakpoint-manager> <breakpoints> diff --git a/assets/openapi.json b/assets/openapi.json
index 0e21164c..c5e92764 100644 --- a/assets/openapi.json +++ b/assets/openapi.json
@@ -4045,6 +4045,32 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, + "communication_disabled_until": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, "user": { "$ref": "#/components/schemas/PublicUser" }, @@ -4056,6 +4082,9 @@ } }, "required": [ + "banner", + "bio", + "communication_disabled_until", "deaf", "guild_id", "id", @@ -4080,6 +4109,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -4092,27 +4136,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -8303,6 +8332,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -8315,27 +8359,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -8377,6 +8406,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -8389,27 +8433,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -8589,6 +8618,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -8601,27 +8645,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -8852,6 +8881,32 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, + "communication_disabled_until": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, "user": { "$ref": "#/components/schemas/PublicUser" }, @@ -8863,6 +8918,9 @@ } }, "required": [ + "banner", + "bio", + "communication_disabled_until", "deaf", "guild_id", "id", @@ -9481,9 +9539,6 @@ "UserProfile": { "type": "object", "properties": { - "accent_color": { - "type": "integer" - }, "banner": { "type": "string" }, @@ -9498,6 +9553,9 @@ }, "pronouns": { "type": "string" + }, + "accent_color": { + "type": "integer" } }, "required": [ diff --git a/assets/schemas.json b/assets/schemas.json
index bbf6461f..e53b7b52 100644 --- a/assets/schemas.json +++ b/assets/schemas.json
@@ -7764,6 +7764,32 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, + "communication_disabled_until": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, "user": { "$ref": "#/definitions/PublicUser" }, @@ -7775,6 +7801,9 @@ } }, "required": [ + "banner", + "bio", + "communication_disabled_until", "deaf", "guild_id", "id", @@ -7799,6 +7828,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -7811,27 +7855,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -22315,6 +22344,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -22327,27 +22371,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -46993,6 +47022,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -47005,27 +47049,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -47069,6 +47098,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -47081,27 +47125,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -52203,6 +52232,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -52215,27 +52259,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -76979,6 +77008,32 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, + "communication_disabled_until": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, "user": { "$ref": "#/definitions/PublicUser" }, @@ -76990,6 +77045,9 @@ } }, "required": [ + "banner", + "bio", + "communication_disabled_until", "deaf", "guild_id", "id", @@ -77014,6 +77072,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -77026,27 +77099,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -91682,6 +91740,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -91694,27 +91767,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -91756,9 +91814,6 @@ "UserProfile": { "type": "object", "properties": { - "accent_color": { - "type": "integer" - }, "banner": { "type": "string" }, @@ -91773,6 +91828,9 @@ }, "pronouns": { "type": "string" + }, + "accent_color": { + "type": "integer" } }, "additionalProperties": false, @@ -91812,6 +91870,32 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, + "communication_disabled_until": { + "anyOf": [ + { + "type": "string", + "format": "date-time" + }, + { + "type": "null" + } + ] + }, "user": { "$ref": "#/definitions/PublicUser" }, @@ -91823,6 +91907,9 @@ } }, "required": [ + "banner", + "bio", + "communication_disabled_until", "deaf", "guild_id", "id", @@ -91961,6 +92048,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -91973,27 +92075,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -98471,6 +98558,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -98483,27 +98585,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -98634,6 +98721,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -98646,27 +98748,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { @@ -99514,6 +99601,21 @@ "avatar": { "type": "string" }, + "banner": { + "type": "string" + }, + "bio": { + "type": "string" + }, + "theme_colors": { + "type": "array", + "items": { + "type": "integer" + } + }, + "pronouns": { + "type": "string" + }, "username": { "type": "string" }, @@ -99526,27 +99628,12 @@ "accent_color": { "type": "integer" }, - "banner": { - "type": "string" - }, - "bio": { - "type": "string" - }, "bot": { "type": "boolean" }, "premium_type": { "type": "integer" }, - "theme_colors": { - "type": "array", - "items": { - "type": "integer" - } - }, - "pronouns": { - "type": "string" - }, "badge_ids": { "type": "array", "items": { diff --git a/scripts/openapi.js b/scripts/openapi.js
index bec7ef1c..a1188c54 100644 --- a/scripts/openapi.js +++ b/scripts/openapi.js
@@ -22,6 +22,7 @@ const path = require("path"); const fs = require("fs"); const { NO_AUTHORIZATION_ROUTES } = require("../dist/api/middlewares/Authentication"); require("../dist/util/util/extensions"); +const { bgRedBright } = require("picocolors"); const openapiPath = path.join(__dirname, "..", "assets", "openapi.json"); const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json"); @@ -82,7 +83,7 @@ function combineSchemas(schemas) { for (const key in definitions) { if (!schemaRegEx.test(key)) { - console.error(`Invalid schema name: ${key}, context:`, definitions[key]); + console.error(`${bgRedBright("ERROR")} Invalid schema name: ${key}, context:`, definitions[key]); continue; } specification.components = specification.components || {}; diff --git a/scripts/util/getRouteDescriptions.js b/scripts/util/getRouteDescriptions.js
index 54b5e1bc..54c8fe19 100644 --- a/scripts/util/getRouteDescriptions.js +++ b/scripts/util/getRouteDescriptions.js
@@ -2,6 +2,7 @@ const express = require("express"); const path = require("path"); const { traverseDirectory } = require("lambert-server"); const RouteUtility = require("../../dist/api/util/handlers/route.js"); +const { bgRedBright } = require("picocolors"); const methods = ["get", "post", "put", "delete", "patch"]; const routes = new Map(); @@ -24,7 +25,7 @@ function proxy(file, method, prefix, path, ...args) { const opts = args.find((x) => x?.prototype?.OPTS_MARKER == true); if (!opts) return console.error( - `${file} has route without route() description middleware`, + `${bgRedBright("ERROR")} ${file} has route without route() description middleware`, ); console.log(`${method.toUpperCase().padStart("OPTIONS".length)} ${prefix + path}`); diff --git a/src/api/routes/users/@me/mentions.ts b/src/api/routes/users/@me/mentions.ts new file mode 100644
index 00000000..cd3bb684 --- /dev/null +++ b/src/api/routes/users/@me/mentions.ts
@@ -0,0 +1,206 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2025 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ + +import { route } from "@spacebar/api"; +import { Snowflake, User, Message, Member, Channel, Permissions, timePromise, NewUrlUserSignatureData, Stopwatch, Attachment } from "@spacebar/util"; +import { Request, Response, Router } from "express"; +import { In } from "typeorm"; + +const router: Router = Router({ mergeParams: true }); + +router.get( + "", + route({ + responses: { + 200: { + body: "MessageListResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + // AFAICT this endpoint doesn't list DMs + async (req: Request, res: Response) => { + const limit = req.query.limit && !isNaN(Number(req.query.limit)) ? Number(req.query.limit) : 50; + const everyone = !!req.query.everyone; + const roles = !!req.query.roles; + const before = req.query.before && BigInt(req.query.before as string); + + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + }); + + const memberships = await Member.find({ + where: { id: req.user_id }, + select: { + guild_id: true, + id: true, + communication_disabled_until: true, + roles: { + // We don't want to include all guild roles, as this could cause a lot more explosive behavior + id: true, + position: true, + permissions: true, + mentionable: true, // cause we can skip querying for unmentionable roles + }, + guild: { + id: true, + owner_id: true, + }, + }, + relations: ["guild", "roles"], + }); + + const channels = await Channel.find({ + where: { + guild_id: In(memberships.map((m) => m.guild_id)), + }, + select: { id: true, guild_id: true, permission_overwrites: true }, + }); + + const visibleChannels = channels.filter((c) => { + const member = memberships.find((m) => m.guild_id === c.guild_id)!; + return Permissions.finalPermission({ + user: { id: member.id, roles: member.roles.map((r) => r.id), communication_disabled_until: member.communication_disabled_until, flags: 0 }, + guild: { id: member.guild.id, owner_id: member.guild.owner_id!, roles: member.roles }, + channel: c, + }).has("VIEW_CHANNEL"); + }); + + const visibleChannelIds = visibleChannels.map((c) => c.id); + const ownedMentionableRoleIds = memberships.reduce((acc, m) => { + acc.push(...m.roles.filter((r) => r.mentionable).map((r) => r.id)); + return acc; + }, [] as Snowflake[]); + + const [ + { result: userMentions, elapsed: userMentionQueryTime }, + { result: roleMentions, elapsed: roleMentionQueryTime }, + { result: everyoneMentions, elapsed: everyoneMentionQueryTime }, + ] = await Promise.all([ + await timePromise(() => + Message.find({ + where: { + channel_id: In(visibleChannelIds), + mentions: { id: user.id }, + }, + select: { + id: true, + timestamp: true, + }, + order: { + timestamp: "DESC", + }, + take: limit, + }), + ), + await timePromise(() => + !roles + ? Promise.resolve([]) + : Message.find({ + where: { + channel_id: In(visibleChannelIds), + mention_roles: { id: In(ownedMentionableRoleIds) }, + }, + select: { + id: true, + timestamp: true, + }, + order: { + timestamp: "DESC", + }, + take: limit, + }), + ), + await timePromise(() => + !everyone + ? Promise.resolve([]) + : Message.find({ + where: { + channel_id: In(visibleChannelIds), + mention_everyone: true, + }, + select: { + id: true, + timestamp: true, + }, + order: { + timestamp: "DESC", + }, + take: limit, + }), + ), + ]); + + const allMentions = [...userMentions, ...roleMentions, ...everyoneMentions]; + console.log( + `[Inbox/mentions] User ${user.id} query results: totalRecs=${allMentions.length} | user=${userMentions.length} (took ${userMentionQueryTime.totalMilliseconds}ms), role=${roleMentions.length} (took ${roleMentionQueryTime.totalMilliseconds}ms), everyone=${everyoneMentions.length} (took ${everyoneMentionQueryTime.totalMilliseconds}ms)`, + ); + const messageIdsToReturn = allMentions + .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime()) + .distinctBy((m) => m.id) + .slice(0, limit); + + const sw = Stopwatch.startNew(); + const finalMessages = ( + await Message.find({ + where: { id: In(messageIdsToReturn.map((m) => m.id)) }, + order: { timestamp: "DESC" }, + relations: [ + "author", + "webhook", + "application", + "mentions", + "mention_roles", + "mention_channels", + "sticker_items", + "attachments", + "referenced_message", + "referenced_message.author", + "referenced_message.webhook", + "referenced_message.application", + "referenced_message.mentions", + "referenced_message.mention_roles", + "referenced_message.mention_channels", + "referenced_message.sticker_items", + "referenced_message.attachments", + ], + }) + ).map((m) => { + return { + ...m.toJSON(), + attachments: m.attachments?.map((attachment: Attachment) => + Attachment.prototype.signUrls.call( + attachment, + new NewUrlUserSignatureData({ + ip: req.ip, + userAgent: req.headers["user-agent"] as string, + }), + ), + ), + }; + }); + + console.log(`[Inbox/mentions] User ${user.id} fetched full message data for ${finalMessages.length} messages in ${sw.elapsed().totalMilliseconds}ms`); + + return res.json(finalMessages); + }, +); + +export default router; diff --git a/src/schemas/api/users/Member.ts b/src/schemas/api/users/Member.ts
index 8436e62a..1acd441c 100644 --- a/src/schemas/api/users/Member.ts +++ b/src/schemas/api/users/Member.ts
@@ -64,7 +64,12 @@ export type PublicMemberKeys = | "deaf" | "mute" | "premium_since" - | "avatar"; + | "avatar" + | "banner" + | "bio" + | "theme_colors" + | "pronouns" + | "communication_disabled_until"; export const PublicMemberProjection: PublicMemberKeys[] = [ "id", @@ -77,6 +82,11 @@ export const PublicMemberProjection: PublicMemberKeys[] = [ "mute", "premium_since", "avatar", + "banner", + "bio", + "theme_colors", + "pronouns", + "communication_disabled_until", ]; export type PublicMember = Omit<Pick<Member, PublicMemberKeys>, "roles"> & { diff --git a/src/util/entities/Member.ts b/src/util/entities/Member.ts
index 2cabd858..404d2ce7 100644 --- a/src/util/entities/Member.ts +++ b/src/util/entities/Member.ts
@@ -44,6 +44,12 @@ export const MemberPrivateProjection: (keyof Member)[] = [ "roles", "settings", "user", + "avatar", + "banner", + "bio", + "theme_colors", + "pronouns", + "communication_disabled_until", ]; @Entity({ diff --git a/src/util/util/Token.ts b/src/util/util/Token.ts
index 3de05aa2..57634953 100644 --- a/src/util/util/Token.ts +++ b/src/util/util/Token.ts
@@ -147,8 +147,31 @@ export async function generateToken(id: string) { }); } +let lastFsCheck: number; +let cachedKeypair: { + privateKey: crypto.KeyObject; + publicKey: crypto.KeyObject; + fingerprint: string; +} + // Get ECDSA keypair from file or generate it export async function loadOrGenerateKeypair() { + if (cachedKeypair) { + // check for file deletion every minute + if (Date.now() - lastFsCheck > 60000) { + if (!existsSync("jwt.key") || !existsSync("jwt.key.pub")) { + console.log("[JWT] Keypair files disappeared... Saving them again."); + await Promise.all([ + fs.writeFile("jwt.key", cachedKeypair.privateKey.export({ format: "pem", type: "sec1" })), + fs.writeFile("jwt.key.pub", cachedKeypair.publicKey.export({ format: "pem", type: "spki" })), + ]); + } + lastFsCheck = Date.now(); + } + + return cachedKeypair; + } + let privateKey: crypto.KeyObject; let publicKey: crypto.KeyObject; @@ -185,5 +208,6 @@ export async function loadOrGenerateKeypair() { .update(publicKey.export({ format: "pem", type: "spki" })) .digest("hex"); - return { privateKey, publicKey, fingerprint }; + lastFsCheck = Date.now(); + return cachedKeypair = { privateKey, publicKey, fingerprint }; }