mirror of
https://github.com/spacebarchat/server.git
synced 2026-03-30 16:05:41 +00:00
Mention inbox, hopefully
This commit is contained in:
6
.idea/data_source_mapping.xml
generated
6
.idea/data_source_mapping.xml
generated
@@ -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>
|
||||
95
.idea/workspace.xml
generated
95
.idea/workspace.xml
generated
@@ -43,46 +43,46 @@
|
||||
<option name="hideEmptyMiddlePackages" value="true" />
|
||||
<option name="showLibraryContents" value="true" />
|
||||
</component>
|
||||
<component name="PropertiesComponent">{
|
||||
"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": "preferences.pluginManager",
|
||||
"ts.external.directory.path": "/home/Rory/git/spacebar/server-master/node_modules/typescript/lib"
|
||||
<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"
|
||||
},
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
"keyToStringList": {
|
||||
"DatabaseDriversLRU": [
|
||||
"postgresql"
|
||||
],
|
||||
"GitStage.ChangesTree.GroupingKeys": [
|
||||
"directory",
|
||||
"module",
|
||||
"repository"
|
||||
"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>
|
||||
|
||||
Binary file not shown.
@@ -24,6 +24,7 @@ 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");
|
||||
@@ -84,7 +85,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 || {};
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
161
src/api/routes/users/@me/mentions.ts
Normal file
161
src/api/routes/users/@me/mentions.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
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 } 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 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, 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.guild.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 ? Number(limit) : 50,
|
||||
}),
|
||||
),
|
||||
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 ? Number(limit) : 50,
|
||||
}),
|
||||
),
|
||||
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 ? Number(limit) : 50,
|
||||
}),
|
||||
),
|
||||
]);
|
||||
|
||||
const allMentions = [...userMentions, ...roleMentions, ...everyoneMentions];
|
||||
console.log(`[Inbox/mentions] User ${user.id} query results: totalRecs=${allMentions.length} | user=${userMentions.length} (took ${userMentionQueryTime}ms), role=${roleMentions.length} (took ${roleMentionQueryTime}ms), everyone=${everyoneMentions.length} (took ${everyoneMentionQueryTime}ms)`);
|
||||
|
||||
return res.json(
|
||||
allMentions
|
||||
.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
|
||||
.distinctBy((m) => m.id)
|
||||
.slice(0, limit),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default router;
|
||||
Reference in New Issue
Block a user