mirror of
https://github.com/spacebarchat/server.git
synced 2026-03-30 13:55:39 +00:00
Add stub POSt report endpoint
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -37,6 +37,7 @@ router.post(
|
||||
}),
|
||||
async (req: Request, res: Response) => {
|
||||
const body = req.body as PreloadMessagesRequestSchema;
|
||||
body.channels ??= body.channel_ids ?? [];
|
||||
if (body.channels.length > Config.get().limits.message.maxPreloadCount)
|
||||
return res.status(400).send({
|
||||
code: 400,
|
||||
|
||||
202
src/api/routes/reporting/index.ts
Normal file
202
src/api/routes/reporting/index.ts
Normal file
@@ -0,0 +1,202 @@
|
||||
/*
|
||||
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 { Request, Response, Router } from "express";
|
||||
import { ReportMenuType, ReportMenuTypeNames } from "../../../schemas/api/reports/ReportMenu";
|
||||
import path from "path";
|
||||
import { HTTPError } from "lambert-server";
|
||||
import { CreateReportSchema } from "../../../schemas/api/reports/CreateReport";
|
||||
import { FieldErrors } from "@spacebar/util";
|
||||
import fs from "fs";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
console.log("[Server] Registering reporting menu routes...");
|
||||
router.get(
|
||||
"/",
|
||||
route({
|
||||
description: "[EXT] Get available reporting menu types.",
|
||||
responses: {
|
||||
200: {
|
||||
body: "Array<ReportMenuTypeNames>",
|
||||
},
|
||||
},
|
||||
}),
|
||||
(req: Request, res: Response) => {
|
||||
res.json(Object.values(ReportMenuTypeNames));
|
||||
},
|
||||
);
|
||||
|
||||
for (const type of Object.values(ReportMenuTypeNames)) {
|
||||
router.get(
|
||||
`/menu/${type}`,
|
||||
route({
|
||||
description: `Get reporting menu options for ${type} reports.`,
|
||||
query: {
|
||||
variant: { type: "string", required: false, description: "Version variant of the menu to retrieve (max 256 characters, default active)" },
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
body: "ReportingMenuResponse",
|
||||
},
|
||||
204: {},
|
||||
},
|
||||
}),
|
||||
(req: Request, res: Response) => {
|
||||
// TODO: implement
|
||||
// res.send([] as ReportingMenuResponseSchema);
|
||||
res.sendFile(path.join(__dirname, "..", "..", "..", "..", "assets", "temp_report_menu_responses", `${type}.json`));
|
||||
},
|
||||
);
|
||||
console.log(`[Server] Route /reporting/menu/${type} registered (reports).`);
|
||||
router.post(
|
||||
`/${type}`,
|
||||
route({
|
||||
description: `Get reporting menu options for ${type} reports.`,
|
||||
requestBody: "CreateReportSchema",
|
||||
responses: {
|
||||
200: {
|
||||
body: "ReportingMenuResponse",
|
||||
},
|
||||
204: {},
|
||||
},
|
||||
}),
|
||||
(req: Request, res: Response) => {
|
||||
// TODO: implement
|
||||
const body = req.body as CreateReportSchema;
|
||||
if (body.name !== type)
|
||||
throw FieldErrors({
|
||||
name: {
|
||||
message: `Expected report type ${type} but got ${body.name}`,
|
||||
code: "INVALID_REPORT_TYPE",
|
||||
},
|
||||
});
|
||||
|
||||
const menuPath = path.join(__dirname, "..", "..", "..", "..", "assets", "temp_report_menu_responses", `${type}.json`);
|
||||
const menuData = JSON.parse(fs.readFileSync(menuPath, "utf-8"));
|
||||
if (body.version !== menuData.version) {
|
||||
throw FieldErrors({
|
||||
version: {
|
||||
message: `Expected report menu version ${menuData.version} but got ${body.version}`,
|
||||
code: "INVALID_REPORT_MENU_VERSION",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (body.variant !== menuData.variant) {
|
||||
throw FieldErrors({
|
||||
variant: {
|
||||
message: `Expected report menu variant ${menuData.variant} but got ${body.variant}`,
|
||||
code: "INVALID_REPORT_MENU_VARIANT",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (body.breadcrumbs != menuData.breadcrumbs) {
|
||||
throw FieldErrors({
|
||||
breadcrumbs: {
|
||||
message: `Invalid report menu breadcrumbs.`,
|
||||
code: "INVALID_REPORT_MENU_BREADCRUMBS",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const validateBreadcrumbs = (currentNode: unknown, breadcrumbs: number[]): boolean => {
|
||||
// navigate via node.children ([name, id][]) according to breadcrumbs
|
||||
let node: { children: [string, number][] } = currentNode as { children: [string, number][] };
|
||||
for (const crumb of breadcrumbs) {
|
||||
if (!node || !node.children || !Array.isArray(node.children)) return false;
|
||||
const nextNode = node.children.find((child: [string, number]) => child[1] === crumb);
|
||||
if (!nextNode) return false;
|
||||
// load next node
|
||||
const nextNodeData = menuData.nodes[crumb];
|
||||
if (!nextNodeData) return false;
|
||||
node = nextNodeData;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (!validateBreadcrumbs(menuData.nodes[menuData.root_node_id], body.breadcrumbs))
|
||||
throw FieldErrors({
|
||||
breadcrumbs: {
|
||||
message: `Invalid report menu breadcrumbs path.`,
|
||||
code: "INVALID_REPORT_MENU_BREADCRUMBS_PATH",
|
||||
},
|
||||
});
|
||||
|
||||
const requireFields = (obj: CreateReportSchema, fields: string[]) => {
|
||||
const missingFields: string[] = [];
|
||||
for (const field of fields) if (!(field in obj)) missingFields.push(field);
|
||||
|
||||
if (missingFields.length > 0)
|
||||
throw FieldErrors(
|
||||
Object.fromEntries(
|
||||
missingFields.map((f) => [
|
||||
f,
|
||||
{
|
||||
message: `Missing required field ${f}.`,
|
||||
code: "MISSING_FIELD",
|
||||
},
|
||||
]),
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const t = Number(Object.entries(ReportMenuTypeNames).find((x) => x[1] === type)?.[0]) as ReportMenuType;
|
||||
// TODO: did i miss anything?
|
||||
switch (t) {
|
||||
case ReportMenuType.GUILD:
|
||||
case ReportMenuType.GUILD_DISCOVERY:
|
||||
requireFields(body, ["guild_id"]);
|
||||
break;
|
||||
case ReportMenuType.GUILD_DIRECTORY_ENTRY:
|
||||
requireFields(body, ["guild_id", "channel_id"]);
|
||||
break;
|
||||
case ReportMenuType.GUILD_SCHEDULED_EVENT:
|
||||
requireFields(body, ["guild_id", "scheduled_event_id"]);
|
||||
break;
|
||||
case ReportMenuType.MESSAGE:
|
||||
requireFields(body, ["channel_id", "message_id"]);
|
||||
// NOTE: is body.guild_id set if the channel is in a guild? is body.user_id ever set????
|
||||
break;
|
||||
case ReportMenuType.STAGE_CHANNEL:
|
||||
requireFields(body, ["channel_id", "guild_id", "stage_instance_id"]);
|
||||
break;
|
||||
case ReportMenuType.FIRST_DM:
|
||||
requireFields(body, ["user_id", "channel_id"]);
|
||||
break;
|
||||
case ReportMenuType.USER:
|
||||
requireFields(body, ["reported_user_id"]);
|
||||
break;
|
||||
case ReportMenuType.APPLICATION:
|
||||
requireFields(body, ["application_id"]);
|
||||
break;
|
||||
case ReportMenuType.WIDGET:
|
||||
requireFields(body, ["user_id", "widget_id"]);
|
||||
break;
|
||||
default:
|
||||
throw new HTTPError("Unknown report menu type", 400);
|
||||
}
|
||||
|
||||
throw new HTTPError("Validation success - implementation TODO", 418);
|
||||
},
|
||||
);
|
||||
console.log(`[Server] Route /reporting/${type} registered (reports).`);
|
||||
}
|
||||
export default router;
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
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 { Request, Response, Router } from "express";
|
||||
import { ReportMenuTypeNames } from "../../../schemas/api/reports/ReportMenu";
|
||||
import path from "path";
|
||||
|
||||
const router = Router({ mergeParams: true });
|
||||
|
||||
console.log("[Server] Registering reporting menu routes...");
|
||||
router.get(
|
||||
"/",
|
||||
route({
|
||||
description: "[EXT] Get available reporting menu types.",
|
||||
responses: {
|
||||
200: {
|
||||
body: "Array<ReportMenuTypeNames>",
|
||||
},
|
||||
},
|
||||
}),
|
||||
(req: Request, res: Response) => {
|
||||
res.json(Object.values(ReportMenuTypeNames));
|
||||
},
|
||||
);
|
||||
|
||||
for (const type of Object.values(ReportMenuTypeNames)) {
|
||||
console.log(`[Server] Route /reporting/menu/${type} registered (reports).`);
|
||||
router.get(
|
||||
`/${type}`,
|
||||
route({
|
||||
description: `Get reporting menu options for ${type} reports.`,
|
||||
query: {
|
||||
variant: { type: "string", required: false, description: "Version variant of the menu to retrieve (max 256 characters, default active)" },
|
||||
},
|
||||
responses: {
|
||||
200: {
|
||||
// body: "ReportingMenuResponse",
|
||||
},
|
||||
204: {},
|
||||
},
|
||||
}),
|
||||
(req: Request, res: Response) => {
|
||||
// TODO: implement
|
||||
// res.send([] as ReportingMenuResponseSchema);
|
||||
res.sendFile(path.join(__dirname, "..", "..", "..", "..", "assets", "temp_report_menu_responses", `${type}.json`));
|
||||
},
|
||||
);
|
||||
}
|
||||
export default router;
|
||||
36
src/schemas/api/reports/CreateReport.ts
Normal file
36
src/schemas/api/reports/CreateReport.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
// TODO: check
|
||||
export interface CreateReportSchema {
|
||||
version: string;
|
||||
variant: string;
|
||||
name: string;
|
||||
language: string;
|
||||
breadcrumbs: number[];
|
||||
elements?: { [key: string]: string[] };
|
||||
channel_id?: string; // snowflake
|
||||
message_id?: string; // snowflake
|
||||
guild_id?: string; // snowflake
|
||||
stage_instance_id?: string; // snowflake
|
||||
guild_scheduled_event_id?: string; // snowflake
|
||||
reported_user_id?: string; // snowflake
|
||||
application_id?: string; // snowflake
|
||||
user_id?: string; // snowflake
|
||||
widget_id?: string; // snowflake
|
||||
}
|
||||
@@ -17,5 +17,6 @@
|
||||
*/
|
||||
|
||||
export interface PreloadMessagesRequestSchema {
|
||||
channels: string[];
|
||||
channels?: string[];
|
||||
channel_ids?: string[];
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { BaseEntity, BeforeInsert, BeforeUpdate, FindOptionsWhere, InsertResult, ObjectIdColumn, ObjectLiteral, PrimaryColumn } from "typeorm";
|
||||
import { BaseEntity, BeforeInsert, BeforeUpdate, Column, ColumnOptions, FindOptionsWhere, InsertResult, ObjectIdColumn, ObjectLiteral, PrimaryColumn } from "typeorm";
|
||||
import { Snowflake } from "../util/Snowflake";
|
||||
import { getDatabase } from "../util/Database";
|
||||
import { OrmUtils } from "../imports/OrmUtils";
|
||||
@@ -83,3 +83,5 @@ export class BaseClass extends BaseClassWithoutId {
|
||||
if (!this.id) this.id = Snowflake.generate();
|
||||
}
|
||||
}
|
||||
|
||||
export const ArrayColumn = (opts: ColumnOptions) => (process.env.DATABASE?.startsWith("postgres") ? Column({ ...opts, array: true }) : Column({ ...opts, type: "simple-array" }));
|
||||
|
||||
43
src/util/entities/ReportMenu.ts
Normal file
43
src/util/entities/ReportMenu.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
|
||||
Copyright (C) 2024 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 { BaseClass, BaseClassWithoutId } from "./BaseClass";
|
||||
import { Entity, JoinColumn, ManyToOne, Column } from "typeorm";
|
||||
import { User } from "./User";
|
||||
import { AutomodAction, AutomodRuleActionType, AutomodRuleEventType, AutomodRuleTriggerMetadata, AutomodRuleTriggerType } from "@spacebar/schemas";
|
||||
import { ReportMenuType } from "../../schemas/api/reports/ReportMenu";
|
||||
|
||||
@Entity({
|
||||
name: "report_menus",
|
||||
})
|
||||
export class ReportMenu extends BaseClass {
|
||||
@Column()
|
||||
type: ReportMenuType;
|
||||
|
||||
@Column()
|
||||
variant: string;
|
||||
|
||||
@Column()
|
||||
isCurrent: boolean;
|
||||
|
||||
@Column({ nullable: true })
|
||||
inherits?: string;
|
||||
|
||||
@Column({ type: "jsonb" })
|
||||
content: unknown;
|
||||
}
|
||||
Reference in New Issue
Block a user