Merge pull request #1032 from spacebarchat/openapi

Better OpenAPI
This commit is contained in:
Madeline 2023-04-29 01:11:22 +10:00 committed by GitHub
commit 009a3ad27f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
167 changed files with 4704 additions and 2260 deletions

Binary file not shown.

Binary file not shown.

View File

@ -27,34 +27,46 @@ require("missing-native-js-functions");
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
let schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
for (var schema in schemas) {
const part = schemas[schema];
for (var key in part.properties) {
if (part.properties[key].anyOf) {
const nullIndex = part.properties[key].anyOf.findIndex(
(x) => x.type == "null",
);
if (nullIndex != -1) {
part.properties[key].nullable = true;
part.properties[key].anyOf.splice(nullIndex, 1);
if (part.properties[key].anyOf.length == 1) {
Object.assign(
part.properties[key],
part.properties[key].anyOf[0],
);
delete part.properties[key].anyOf;
}
}
}
}
}
const specification = JSON.parse(
fs.readFileSync(openapiPath, { encoding: "utf8" }),
);
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
// const specification = JSON.parse(
// fs.readFileSync(openapiPath, { encoding: "utf8" }),
// );
let specification = {
openapi: "3.1.0",
info: {
title: "Spacebar Server",
description:
"Spacebar is a free open source selfhostable discord compatible chat, voice and video platform",
license: {
name: "AGPLV3",
url: "https://www.gnu.org/licenses/agpl-3.0.en.html",
},
version: "1.0.0",
},
externalDocs: {
description: "Spacebar Docs",
url: "https://docs.spacebar.chat",
},
servers: [
{
url: "https://old.server.spacebar.chat/api/",
description: "Official Spacebar Instance",
},
],
components: {
securitySchemes: {
bearer: {
type: "http",
scheme: "bearer",
description: "Bearer/Bot prefixes are not required.",
bearerFormat: "JWT",
in: "header",
},
},
},
tags: [],
paths: {},
};
function combineSchemas(schemas) {
var definitions = {};
@ -72,6 +84,11 @@ function combineSchemas(schemas) {
}
for (const key in definitions) {
const reg = new RegExp(/^[a-zA-Z0-9.\-_]+$/, "gm");
if (!reg.test(key)) {
console.error(`Invalid schema name: ${key} (${reg.test(key)})`);
continue;
}
specification.components = specification.components || {};
specification.components.schemas =
specification.components.schemas || {};
@ -102,30 +119,20 @@ function getTag(key) {
function apiRoutes() {
const routes = getRouteDescriptions();
const tags = Array.from(routes.keys()).map((x) => getTag(x));
specification.tags = specification.tags || [];
specification.tags = [...specification.tags.map((x) => x.name), ...tags]
.unique()
.map((x) => ({ name: x }));
specification.components = specification.components || {};
specification.components.securitySchemes = {
bearer: {
type: "http",
scheme: "bearer",
description: "Bearer/Bot prefixes are not required.",
},
};
// populate tags
const tags = Array.from(routes.keys())
.map((x) => getTag(x))
.sort((a, b) => a.localeCompare(b));
specification.tags = tags.unique().map((x) => ({ name: x }));
routes.forEach((route, pathAndMethod) => {
const [p, method] = pathAndMethod.split("|");
const path = p.replace(/:(\w+)/g, "{$1}");
specification.paths = specification.paths || {};
let obj = specification.paths[path]?.[method] || {};
obj["x-right-required"] = route.right;
obj["x-permission-required"] = route.permission;
obj["x-fires-event"] = route.test?.event;
obj["x-fires-event"] = route.event;
if (
!NO_AUTHORIZATION_ROUTES.some((x) => {
@ -136,48 +143,56 @@ function apiRoutes() {
obj.security = [{ bearer: [] }];
}
if (route.body) {
if (route.description) obj.description = route.description;
if (route.summary) obj.summary = route.summary;
if (route.deprecated) obj.deprecated = route.deprecated;
if (route.requestBody) {
obj.requestBody = {
required: true,
content: {
"application/json": {
schema: { $ref: `#/components/schemas/${route.body}` },
schema: {
$ref: `#/components/schemas/${route.requestBody}`,
},
},
},
}.merge(obj.requestBody);
}
if (route.test?.response) {
const status = route.test.response.status || 200;
if (route.responses) {
for (const [k, v] of Object.entries(route.responses)) {
let schema = {
allOf: [
{
$ref: `#/components/schemas/${route.test.response.body}`,
},
{
example: route.test.body,
},
],
$ref: `#/components/schemas/${v.body}`,
};
if (!route.test.body) schema = schema.allOf[0];
obj.responses = {
[status]: {
...(route.test.response.body
[k]: {
...(v.body
? {
description:
obj?.responses?.[status]?.description || "",
obj?.responses?.[k]?.description || "",
content: {
"application/json": {
schema: schema,
},
},
}
: {}),
: {
description: "No description available",
}),
},
}.merge(obj.responses);
delete obj.responses.default;
}
} else {
obj.responses = {
default: {
description: "No description available",
},
};
}
// handles path parameters
if (p.includes(":")) {
obj.parameters = p.match(/:\w+/g)?.map((x) => ({
name: x.replace(":", ""),
@ -187,16 +202,33 @@ function apiRoutes() {
description: x.replace(":", ""),
}));
}
if (route.query) {
// map to array
const query = Object.entries(route.query).map(([k, v]) => ({
name: k,
in: "query",
required: v.required,
schema: { type: v.type },
description: v.description,
}));
obj.parameters = [...(obj.parameters || []), ...query];
}
obj.tags = [...(obj.tags || []), getTag(p)].unique();
specification.paths[path] = {
...specification.paths[path],
specification.paths[path] = Object.assign(
specification.paths[path] || {},
{
[method]: obj,
};
},
);
});
}
function main() {
console.log("Generating OpenAPI Specification...");
combineSchemas(schemas);
apiRoutes();

View File

@ -57,6 +57,8 @@ const Excluded = [
"PropertiesSchema",
"AsyncSchema",
"AnySchema",
"SMTPConnection.CustomAuthenticationResponse",
"TransportMakeRequestResponse",
];
function modify(obj) {
@ -75,14 +77,14 @@ function main() {
const generator = TJS.buildGenerator(program, settings);
if (!generator || !program) return;
let schemas = generator
.getUserSymbols()
.filter(
(x) =>
(x.endsWith("Schema") || x.endsWith("Response")) &&
!Excluded.includes(x),
let schemas = generator.getUserSymbols().filter((x) => {
return (
(x.endsWith("Schema") ||
x.endsWith("Response") ||
x.startsWith("API")) &&
!Excluded.includes(x)
);
console.log(schemas);
});
var definitions = {};
@ -133,7 +135,7 @@ function main() {
definitions = { ...definitions, [name]: { ...part } };
}
modify(definitions);
//modify(definitions);
fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
}

View File

@ -1,80 +1,64 @@
const express = require("express");
const path = require("path");
const { traverseDirectory } = require("lambert-server");
const RouteUtility = require("../../dist/api/util/handlers/route.js");
const methods = ["get", "post", "put", "delete", "patch"];
const routes = new Map();
let currentFile = "";
let currentPath = "";
/*
Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
Copyright (C) 2023 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/>.
For some reason, if a route exports multiple functions, it won't be registered here!
If someone could fix that I'd really appreciate it, but for now just, don't do that :p
*/
const { traverseDirectory } = require("lambert-server");
const path = require("path");
const express = require("express");
const RouteUtility = require("../../dist/api/util/handlers/route.js");
const Router = express.Router;
const routes = new Map();
let currentPath = "";
let currentFile = "";
const methods = ["get", "post", "put", "delete", "patch"];
function registerPath(file, method, prefix, path, ...args) {
const urlPath = prefix + path;
const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts");
const opts = args.find((x) => typeof x === "object");
if (opts) {
routes.set(urlPath + "|" + method, opts);
opts.file = sourceFile;
// console.log(method, urlPath, opts);
} else {
console.log(
`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`,
const 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`,
);
}
}
function routeOptions(opts) {
console.log(prefix + path + " - " + method);
opts.file = file.replace("/dist/", "/src/").replace(".js", ".ts");
routes.set(prefix + path + "|" + method, opts());
};
express.Router = () => {
return Object.fromEntries(
methods.map((method) => [
method,
proxy.bind(null, currentFile, method, currentPath),
]),
);
};
RouteUtility.route = (opts) => {
const func = function () {
return opts;
}
RouteUtility.route = routeOptions;
express.Router = (opts) => {
const path = currentPath;
const file = currentFile;
const router = Router(opts);
for (const method of methods) {
router[method] = registerPath.bind(null, file, method, path);
}
return router;
};
func.prototype.OPTS_MARKER = true;
return func;
};
module.exports = function getRouteDescriptions() {
const root = path.join(__dirname, "..", "..", "dist", "api", "routes", "/");
traverseDirectory({ dirname: root, recursive: true }, (file) => {
currentFile = file;
let path = file.replace(root.slice(0, -1), "");
path = path.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path
path = path.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes
if (path.endsWith("/index")) path = path.slice(0, "/index".length * -1); // delete index from path
currentPath = path;
currentPath = file.replace(root.slice(0, -1), "");
currentPath = currentPath.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path
currentPath = currentPath.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes
if (currentPath.endsWith("/index"))
currentPath = currentPath.slice(0, "/index".length * -1); // delete index from path
try {
require(file);
} catch (error) {
console.error("error loading file " + file, error);
} catch (e) {
console.error(e);
}
});
return routes;
};

View File

@ -16,22 +16,34 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import {
Application,
generateToken,
User,
BotModifySchema,
handleFile,
DiscordApiErrors,
User,
generateToken,
handleFile,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { verifyToken } from "node-2fa";
const router: Router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
router.post(
"/",
route({
responses: {
204: {
body: "TokenOnlyResponse",
},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const app = await Application.findOneOrFail({
where: { id: req.params.id },
relations: ["owner"],
@ -61,9 +73,22 @@ router.post("/", route({}), async (req: Request, res: Response) => {
res.send({
token: await generateToken(user.id),
}).status(204);
});
},
);
router.post("/reset", route({}), async (req: Request, res: Response) => {
router.post(
"/reset",
route({
responses: {
200: {
body: "TokenResponse",
},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const bot = await User.findOneOrFail({ where: { id: req.params.id } });
const owner = await User.findOneOrFail({ where: { id: req.user_id } });
@ -83,11 +108,22 @@ router.post("/reset", route({}), async (req: Request, res: Response) => {
const token = await generateToken(bot.id);
res.json({ token }).status(200);
});
},
);
router.patch(
"/",
route({ body: "BotModifySchema" }),
route({
requestBody: "BotModifySchema",
responses: {
200: {
body: "Application",
},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as BotModifySchema;
if (!body.avatar?.trim()) delete body.avatar;

View File

@ -16,15 +16,25 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "ApplicationEntitlementsResponse",
},
},
}),
(req: Request, res: Response) => {
// TODO:
//const { exclude_consumed } = req.query;
res.status(200).send([]);
});
},
);
export default router;

View File

@ -16,19 +16,31 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import {
Application,
DiscordApiErrors,
ApplicationModifySchema,
DiscordApiErrors,
} from "@spacebar/util";
import { verifyToken } from "node-2fa";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { verifyToken } from "node-2fa";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "Application",
},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const app = await Application.findOneOrFail({
where: { id: req.params.id },
relations: ["owner", "bot"],
@ -37,11 +49,22 @@ router.get("/", route({}), async (req: Request, res: Response) => {
throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
return res.json(app);
});
},
);
router.patch(
"/",
route({ body: "ApplicationModifySchema" }),
route({
requestBody: "ApplicationModifySchema",
responses: {
200: {
body: "Application",
},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as ApplicationModifySchema;
@ -73,7 +96,17 @@ router.patch(
},
);
router.post("/delete", route({}), async (req: Request, res: Response) => {
router.post(
"/delete",
route({
responses: {
200: {},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const app = await Application.findOneOrFail({
where: { id: req.params.id },
relations: ["bot", "owner"],
@ -83,13 +116,15 @@ router.post("/delete", route({}), async (req: Request, res: Response) => {
if (
app.owner.totp_secret &&
(!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))
(!req.body.code ||
verifyToken(app.owner.totp_secret, req.body.code))
)
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
await Application.delete({ id: app.id });
res.send().status(200);
});
},
);
export default router;

View File

@ -16,13 +16,23 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "ApplicationSkusResponse",
},
},
}),
async (req: Request, res: Response) => {
res.json([]).status(200);
});
},
);
export default router;

View File

@ -16,14 +16,24 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "ApplicationDetectableResponse",
},
},
}),
async (req: Request, res: Response) => {
//TODO
res.send([]).status(200);
});
},
);
export default router;

View File

@ -16,28 +16,45 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import {
Application,
ApplicationCreateSchema,
trimSpecial,
User,
trimSpecial,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "APIApplicationArray",
},
},
}),
async (req: Request, res: Response) => {
const results = await Application.find({
where: { owner: { id: req.user_id } },
relations: ["owner", "bot"],
});
res.json(results).status(200);
});
},
);
router.post(
"/",
route({ body: "ApplicationCreateSchema" }),
route({
requestBody: "ApplicationCreateSchema",
responses: {
200: {
body: "Application",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as ApplicationCreateSchema;
const user = await User.findOneOrFail({ where: { id: req.user_id } });

View File

@ -30,7 +30,18 @@ const router = Router();
router.post(
"/",
route({ body: "ForgotPasswordSchema" }),
route({
requestBody: "ForgotPasswordSchema",
responses: {
204: {},
400: {
body: "APIErrorOrCaptchaResponse",
},
500: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { login, captcha_key } = req.body as ForgotPasswordSchema;

View File

@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route, random } from "@spacebar/api";
import { random, route } from "@spacebar/api";
import { Config, ValidRegistrationToken } from "@spacebar/util";
import { Request, Response, Router } from "express";
@ -25,7 +25,22 @@ export default router;
router.get(
"/",
route({ right: "OPERATOR" }),
route({
query: {
count: {
type: "number",
description:
"The number of registration tokens to generate. Defaults to 1.",
},
length: {
type: "number",
description:
"The length of each registration token. Defaults to 255.",
},
},
right: "OPERATOR",
responses: { 200: { body: "GenerateRegistrationTokensResponse" } },
}),
async (req: Request, res: Response) => {
const count = req.query.count ? parseInt(req.query.count as string) : 1;
const length = req.query.length

View File

@ -16,12 +16,20 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { getIpAdress, IPAnalysis } from "@spacebar/api";
import { IPAnalysis, getIpAdress, route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "LocationMetadataResponse",
},
},
}),
async (req: Request, res: Response) => {
//TODO
//Note: It's most likely related to legal. At the moment Discord hasn't finished this too
const country_code = (await IPAnalysis(getIpAdress(req))).country_code;
@ -30,6 +38,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
country_code: country_code,
promotional_email_opt_in: { required: true, pre_checked: false },
});
});
},
);
export default router;

View File

@ -36,7 +36,17 @@ export default router;
router.post(
"/",
route({ body: "LoginSchema" }),
route({
requestBody: "LoginSchema",
responses: {
200: {
body: "LoginResponse",
},
400: {
body: "APIErrorOrCaptchaResponse",
},
},
}),
async (req: Request, res: Response) => {
const { login, password, captcha_key, undelete } =
req.body as LoginSchema;

View File

@ -22,9 +22,19 @@ import { Request, Response, Router } from "express";
const router: Router = Router();
export default router;
router.post("/", route({}), async (req: Request, res: Response) => {
router.post(
"/",
route({
responses: {
204: {},
},
}),
async (req: Request, res: Response) => {
if (req.body.provider != null || req.body.voip_provider != null) {
console.log(`[LOGOUT]: provider or voip provider not null!`, req.body);
console.log(
`[LOGOUT]: provider or voip provider not null!`,
req.body,
);
} else {
delete req.body.provider;
delete req.body.voip_provider;
@ -32,4 +42,5 @@ router.post("/", route({}), async (req: Request, res: Response) => {
console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
}
res.status(204).send();
});
},
);

View File

@ -16,16 +16,26 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { BackupCode, generateToken, User, TotpSchema } from "@spacebar/util";
import { verifyToken } from "node-2fa";
import { BackupCode, TotpSchema, User, generateToken } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { verifyToken } from "node-2fa";
const router = Router();
router.post(
"/",
route({ body: "TotpSchema" }),
route({
requestBody: "TotpSchema",
responses: {
200: {
body: "TokenResponse",
},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// const { code, ticket, gift_code_sku_id, login_source } =
const { code, ticket } = req.body as TotpSchema;

View File

@ -41,7 +41,13 @@ function toArrayBuffer(buf: Buffer) {
router.post(
"/",
route({ body: "WebAuthnTotpSchema" }),
route({
requestBody: "WebAuthnTotpSchema",
responses: {
200: { body: "TokenResponse" },
400: { body: "APIErrorResponse" },
},
}),
async (req: Request, res: Response) => {
if (!WebAuthn.fido2) {
// TODO: I did this for typescript and I can't use !

View File

@ -16,25 +16,25 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import {
Config,
generateToken,
Invite,
FieldErrors,
User,
adjustEmail,
RegisterSchema,
ValidRegistrationToken,
} from "@spacebar/util";
import {
route,
getIpAdress,
IPAnalysis,
getIpAdress,
isProxy,
route,
verifyCaptcha,
} from "@spacebar/api";
import {
Config,
FieldErrors,
Invite,
RegisterSchema,
User,
ValidRegistrationToken,
adjustEmail,
generateToken,
} from "@spacebar/util";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { MoreThan } from "typeorm";
@ -42,7 +42,13 @@ const router: Router = Router();
router.post(
"/",
route({ body: "RegisterSchema" }),
route({
requestBody: "RegisterSchema",
responses: {
200: { body: "TokenOnlyResponse" },
400: { body: "APIErrorOrCaptchaResponse" },
},
}),
async (req: Request, res: Response) => {
const body = req.body as RegisterSchema;
const { register, security, limits } = Config.get();

View File

@ -31,9 +31,20 @@ import { Request, Response, Router } from "express";
const router = Router();
// TODO: the response interface also returns settings, but this route doesn't actually return that.
router.post(
"/",
route({ body: "PasswordResetSchema" }),
route({
requestBody: "PasswordResetSchema",
responses: {
200: {
body: "TokenOnlyResponse",
},
400: {
body: "APIErrorOrCaptchaResponse",
},
},
}),
async (req: Request, res: Response) => {
const { password, token } = req.body as PasswordResetSchema;

View File

@ -37,9 +37,20 @@ async function getToken(user: User) {
return { token };
}
// TODO: the response interface also returns settings, but this route doesn't actually return that.
router.post(
"/",
route({ body: "VerifyEmailSchema" }),
route({
requestBody: "VerifyEmailSchema",
responses: {
200: {
body: "TokenResponse",
},
400: {
body: "APIErrorOrCaptchaResponse",
},
},
}),
async (req: Request, res: Response) => {
const { captcha_key, token } = req.body;

View File

@ -24,7 +24,18 @@ const router = Router();
router.post(
"/",
route({ right: "RESEND_VERIFICATION_EMAIL" }),
route({
right: "RESEND_VERIFICATION_EMAIL",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
500: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },

View File

@ -16,15 +16,21 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { FieldErrors, User, BackupCodesChallengeSchema } from "@spacebar/util";
import { BackupCodesChallengeSchema, FieldErrors, User } from "@spacebar/util";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
const router = Router();
router.post(
"/",
route({ body: "BackupCodesChallengeSchema" }),
route({
requestBody: "BackupCodesChallengeSchema",
responses: {
200: { body: "BackupCodesChallengeResponse" },
400: { body: "APIErrorResponse" },
},
}),
async (req: Request, res: Response) => {
const { password } = req.body as BackupCodesChallengeSchema;

View File

@ -16,18 +16,18 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@spacebar/api";
import {
Channel,
ChannelDeleteEvent,
ChannelModifySchema,
ChannelType,
ChannelUpdateEvent,
emitEvent,
Recipient,
emitEvent,
handleFile,
ChannelModifySchema,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
const router: Router = Router();
// TODO: delete channel
@ -35,7 +35,15 @@ const router: Router = Router();
router.get(
"/",
route({ permission: "VIEW_CHANNEL" }),
route({
permission: "VIEW_CHANNEL",
responses: {
200: {
body: "Channel",
},
404: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id } = req.params;
@ -49,7 +57,15 @@ router.get(
router.delete(
"/",
route({ permission: "MANAGE_CHANNELS" }),
route({
permission: "MANAGE_CHANNELS",
responses: {
200: {
body: "Channel",
},
404: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id } = req.params;
@ -90,7 +106,19 @@ router.delete(
router.patch(
"/",
route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }),
route({
requestBody: "ChannelModifySchema",
permission: "MANAGE_CHANNELS",
responses: {
200: {
body: "Channel",
},
404: {},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const payload = req.body as ChannelModifySchema;
const { channel_id } = req.params;

View File

@ -16,29 +16,37 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
import { random } from "@spacebar/api";
import { random, route } from "@spacebar/api";
import {
Channel,
Guild,
Invite,
InviteCreateEvent,
emitEvent,
User,
Guild,
PublicInviteRelation,
User,
emitEvent,
isTextChannel,
} from "@spacebar/util";
import { isTextChannel } from "./messages";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router: Router = Router();
router.post(
"/",
route({
body: "InviteCreateSchema",
requestBody: "InviteCreateSchema",
permission: "CREATE_INSTANT_INVITE",
right: "CREATE_INVITES",
responses: {
201: {
body: "Invite",
},
404: {},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { user_id } = req;
@ -84,7 +92,15 @@ router.post(
router.get(
"/",
route({ permission: "MANAGE_CHANNELS" }),
route({
permission: "MANAGE_CHANNELS",
responses: {
200: {
body: "APIInviteArray",
},
404: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id } = req.params;
const channel = await Channel.findOneOrFail({

View File

@ -16,6 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@spacebar/api";
import {
emitEvent,
getPermission,
@ -23,7 +24,6 @@ import {
ReadState,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
const router = Router();
@ -33,7 +33,13 @@ const router = Router();
router.post(
"/",
route({ body: "MessageAcknowledgeSchema" }),
route({
requestBody: "MessageAcknowledgeSchema",
responses: {
200: {},
403: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params;

View File

@ -16,14 +16,21 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
router.post(
"/",
route({ permission: "MANAGE_MESSAGES" }),
route({
permission: "MANAGE_MESSAGES",
responses: {
200: {
body: "Message",
},
},
}),
(req: Request, res: Response) => {
// TODO:
res.json({

View File

@ -19,24 +19,23 @@
import {
Attachment,
Channel,
emitEvent,
SpacebarApiErrors,
getPermission,
getRights,
Message,
MessageCreateEvent,
MessageCreateSchema,
MessageDeleteEvent,
MessageEditSchema,
MessageUpdateEvent,
Snowflake,
SpacebarApiErrors,
emitEvent,
getPermission,
getRights,
uploadFile,
MessageCreateSchema,
MessageEditSchema,
} from "@spacebar/util";
import { Router, Response, Request } from "express";
import multer from "multer";
import { route } from "@spacebar/api";
import { handleMessage, postHandleMessage } from "@spacebar/api";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import multer from "multer";
import { handleMessage, postHandleMessage, route } from "../../../../../util";
const router = Router();
// TODO: message content/embed string length limit
@ -53,9 +52,19 @@ const messageUpload = multer({
router.patch(
"/",
route({
body: "MessageEditSchema",
requestBody: "MessageEditSchema",
permission: "SEND_MESSAGES",
right: "SEND_MESSAGES",
responses: {
200: {
body: "Message",
},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}),
async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params;
@ -143,9 +152,19 @@ router.put(
next();
},
route({
body: "MessageCreateSchema",
requestBody: "MessageCreateSchema",
permission: "SEND_MESSAGES",
right: "SEND_BACKDATED_EVENTS",
responses: {
200: {
body: "Message",
},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params;
@ -230,7 +249,19 @@ router.put(
router.get(
"/",
route({ permission: "VIEW_CHANNEL" }),
route({
permission: "VIEW_CHANNEL",
responses: {
200: {
body: "Message",
},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}),
async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params;
@ -252,11 +283,26 @@ router.get(
},
);
router.delete("/", route({}), async (req: Request, res: Response) => {
router.delete(
"/",
route({
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {},
},
}),
async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params;
const channel = await Channel.findOneOrFail({ where: { id: channel_id } });
const message = await Message.findOneOrFail({ where: { id: message_id } });
const channel = await Channel.findOneOrFail({
where: { id: channel_id },
});
const message = await Message.findOneOrFail({
where: { id: message_id },
});
const rights = await getRights(req.user_id);
@ -284,6 +330,7 @@ router.delete("/", route({}), async (req: Request, res: Response) => {
} as MessageDeleteEvent);
res.sendStatus(204);
});
},
);
export default router;

View File

@ -16,6 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@spacebar/api";
import {
Channel,
emitEvent,
@ -32,8 +33,7 @@ import {
PublicUserProjection,
User,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import { Router, Response, Request } from "express";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { In } from "typeorm";
@ -57,7 +57,17 @@ function getEmoji(emoji: string): PartialEmoji {
router.delete(
"/",
route({ permission: "MANAGE_MESSAGES" }),
route({
permission: "MANAGE_MESSAGES",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params;
@ -83,7 +93,17 @@ router.delete(
router.delete(
"/:emoji",
route({ permission: "MANAGE_MESSAGES" }),
route({
permission: "MANAGE_MESSAGES",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params;
const emoji = getEmoji(req.params.emoji);
@ -120,7 +140,19 @@ router.delete(
router.get(
"/:emoji",
route({ permission: "VIEW_CHANNEL" }),
route({
permission: "VIEW_CHANNEL",
responses: {
200: {
body: "PublicUser",
},
400: {
body: "APIErrorResponse",
},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => {
const { message_id, channel_id } = req.params;
const emoji = getEmoji(req.params.emoji);
@ -148,7 +180,18 @@ router.get(
router.put(
"/:emoji/:user_id",
route({ permission: "READ_MESSAGE_HISTORY", right: "SELF_ADD_REACTIONS" }),
route({
permission: "READ_MESSAGE_HISTORY",
right: "SELF_ADD_REACTIONS",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => {
const { message_id, channel_id, user_id } = req.params;
if (user_id !== "@me") throw new HTTPError("Invalid user");
@ -219,7 +262,16 @@ router.put(
router.delete(
"/:emoji/:user_id",
route({}),
route({
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => {
let { user_id } = req.params;
const { message_id, channel_id } = req.params;

View File

@ -16,18 +16,18 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import {
Channel,
Config,
emitEvent,
getPermission,
getRights,
MessageDeleteBulkEvent,
Message,
MessageDeleteBulkEvent,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
const router: Router = Router();
@ -38,7 +38,17 @@ export default router;
// https://discord.com/developers/docs/resources/channel#bulk-delete-messages
router.post(
"/",
route({ body: "BulkDeleteSchema" }),
route({
requestBody: "BulkDeleteSchema",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id } = req.params;
const channel = await Channel.findOneOrFail({

View File

@ -16,64 +16,69 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { handleMessage, postHandleMessage, route } from "@spacebar/api";
import {
Attachment,
Channel,
ChannelType,
Config,
DmChannelDTO,
emitEvent,
FieldErrors,
getPermission,
Member,
Message,
MessageCreateEvent,
Snowflake,
uploadFile,
Member,
MessageCreateSchema,
Reaction,
ReadState,
Rights,
Reaction,
Snowflake,
User,
emitEvent,
getPermission,
isTextChannel,
uploadFile,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { handleMessage, postHandleMessage, route } from "@spacebar/api";
import multer from "multer";
import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm";
import { URL } from "url";
const router: Router = Router();
export default router;
export function isTextChannel(type: ChannelType): boolean {
switch (type) {
case ChannelType.GUILD_STORE:
case ChannelType.GUILD_VOICE:
case ChannelType.GUILD_STAGE_VOICE:
case ChannelType.GUILD_CATEGORY:
case ChannelType.GUILD_FORUM:
case ChannelType.DIRECTORY:
throw new HTTPError("not a text channel", 400);
case ChannelType.DM:
case ChannelType.GROUP_DM:
case ChannelType.GUILD_NEWS:
case ChannelType.GUILD_NEWS_THREAD:
case ChannelType.GUILD_PUBLIC_THREAD:
case ChannelType.GUILD_PRIVATE_THREAD:
case ChannelType.GUILD_TEXT:
case ChannelType.ENCRYPTED:
case ChannelType.ENCRYPTED_THREAD:
return true;
default:
throw new HTTPError("unimplemented", 400);
}
}
// https://discord.com/developers/docs/resources/channel#create-message
// get messages
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
query: {
around: {
type: "string",
},
before: {
type: "string",
},
after: {
type: "string",
},
limit: {
type: "number",
description:
"max number of messages to return (1-100). defaults to 50",
},
},
responses: {
200: {
body: "APIMessageArray",
},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}),
async (req: Request, res: Response) => {
const channel_id = req.params.channel_id;
const channel = await Channel.findOneOrFail({
where: { id: channel_id },
@ -174,7 +179,8 @@ router.get("/", route({}), async (req: Request, res: Response) => {
return x;
}),
);
});
},
);
// TODO: config max upload size
const messageUpload = multer({
@ -205,9 +211,19 @@ router.post(
next();
},
route({
body: "MessageCreateSchema",
requestBody: "MessageCreateSchema",
permission: "SEND_MESSAGES",
right: "SEND_MESSAGES",
responses: {
200: {
body: "Message",
},
400: {
body: "APIErrorResponse",
},
403: {},
404: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id } = req.params;
@ -366,3 +382,5 @@ router.post(
return res.json(message);
},
);
export default router;

View File

@ -19,13 +19,13 @@
import {
Channel,
ChannelPermissionOverwrite,
ChannelPermissionOverwriteSchema,
ChannelUpdateEvent,
emitEvent,
Member,
Role,
ChannelPermissionOverwriteSchema,
} from "@spacebar/util";
import { Router, Response, Request } from "express";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
@ -36,8 +36,14 @@ const router: Router = Router();
router.put(
"/:overwrite_id",
route({
body: "ChannelPermissionOverwriteSchema",
requestBody: "ChannelPermissionOverwriteSchema",
permission: "MANAGE_ROLES",
responses: {
204: {},
404: {},
501: {},
400: { body: "APIErrorResponse" },
},
}),
async (req: Request, res: Response) => {
const { channel_id, overwrite_id } = req.params;
@ -92,7 +98,7 @@ router.put(
// TODO: check permission hierarchy
router.delete(
"/:overwrite_id",
route({ permission: "MANAGE_ROLES" }),
route({ permission: "MANAGE_ROLES", responses: { 204: {}, 404: {} } }),
async (req: Request, res: Response) => {
const { channel_id, overwrite_id } = req.params;

View File

@ -16,23 +16,33 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@spacebar/api";
import {
Channel,
ChannelPinsUpdateEvent,
Config,
DiscordApiErrors,
emitEvent,
Message,
MessageUpdateEvent,
DiscordApiErrors,
} from "@spacebar/util";
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.put(
"/:message_id",
route({ permission: "VIEW_CHANNEL" }),
route({
permission: "VIEW_CHANNEL",
responses: {
204: {},
403: {},
404: {},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params;
@ -74,7 +84,17 @@ router.put(
router.delete(
"/:message_id",
route({ permission: "VIEW_CHANNEL" }),
route({
permission: "VIEW_CHANNEL",
responses: {
204: {},
403: {},
404: {},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { channel_id, message_id } = req.params;
@ -114,7 +134,17 @@ router.delete(
router.get(
"/",
route({ permission: ["READ_MESSAGE_HISTORY"] }),
route({
permission: ["READ_MESSAGE_HISTORY"],
responses: {
200: {
body: "APIMessageArray",
},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { channel_id } = req.params;

View File

@ -16,20 +16,20 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
import { isTextChannel } from "./messages";
import { FindManyOptions, Between, Not, FindOperator } from "typeorm";
import {
Channel,
emitEvent,
getPermission,
getRights,
Message,
MessageDeleteBulkEvent,
PurgeSchema,
emitEvent,
getPermission,
getRights,
isTextChannel,
} from "@spacebar/util";
import { Router, Response, Request } from "express";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { Between, FindManyOptions, FindOperator, Not } from "typeorm";
const router: Router = Router();
@ -42,6 +42,14 @@ router.post(
"/",
route({
/*body: "PurgeSchema",*/
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id } = req.params;

View File

@ -16,7 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import {
Channel,
ChannelRecipientAddEvent,
@ -28,11 +28,19 @@ import {
Recipient,
User,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.put("/:user_id", route({}), async (req: Request, res: Response) => {
router.put(
"/:user_id",
route({
responses: {
201: {},
404: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id, user_id } = req.params;
const channel = await Channel.findOneOrFail({
where: { id: channel_id },
@ -79,9 +87,18 @@ router.put("/:user_id", route({}), async (req: Request, res: Response) => {
} as ChannelRecipientAddEvent);
return res.sendStatus(204);
}
});
},
);
router.delete("/:user_id", route({}), async (req: Request, res: Response) => {
router.delete(
"/:user_id",
route({
responses: {
204: {},
404: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id, user_id } = req.params;
const channel = await Channel.findOneOrFail({
where: { id: channel_id },
@ -102,6 +119,7 @@ router.delete("/:user_id", route({}), async (req: Request, res: Response) => {
await Channel.removeRecipientFromChannel(channel, user_id);
return res.sendStatus(204);
});
},
);
export default router;

View File

@ -16,15 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Channel, emitEvent, Member, TypingStartEvent } from "@spacebar/util";
import { route } from "@spacebar/api";
import { Router, Request, Response } from "express";
import { Channel, emitEvent, Member, TypingStartEvent } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.post(
"/",
route({ permission: "SEND_MESSAGES" }),
route({
permission: "SEND_MESSAGES",
responses: {
204: {},
404: {},
403: {},
},
}),
async (req: Request, res: Response) => {
const { channel_id } = req.params;
const user_id = req.user_id;

View File

@ -16,34 +16,56 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import {
Channel,
Config,
handleFile,
trimSpecial,
DiscordApiErrors,
User,
Webhook,
WebhookCreateSchema,
WebhookType,
handleFile,
trimSpecial,
isTextChannel,
} from "@spacebar/util";
import { HTTPError } from "lambert-server";
import { isTextChannel } from "./messages/index";
import { DiscordApiErrors } from "@spacebar/util";
import crypto from "crypto";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router: Router = Router();
//TODO: implement webhooks
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "APIWebhookArray",
},
},
}),
async (req: Request, res: Response) => {
res.json([]);
});
},
);
// TODO: use Image Data Type for avatar instead of String
router.post(
"/",
route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }),
route({
requestBody: "WebhookCreateSchema",
permission: "MANAGE_WEBHOOKS",
responses: {
200: {
body: "WebhookCreateResponse",
},
400: {
body: "APIErrorResponse",
},
403: {},
},
}),
async (req: Request, res: Response) => {
const channel_id = req.params.channel_id;
const channel = await Channel.findOneOrFail({

View File

@ -29,7 +29,7 @@ const router = Router();
router.post(
"/",
route({ body: "ConnectionCallbackSchema" }),
route({ requestBody: "ConnectionCallbackSchema" }),
async (req: Request, res: Response) => {
const { connection_name } = req.params;
const connection = ConnectionStore.connections.get(connection_name);

View File

@ -16,22 +16,33 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Guild, Config } from "@spacebar/util";
import { Config, Guild } from "@spacebar/util";
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
import { Like } from "typeorm";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "DiscoverableGuildsResponse",
},
},
}),
async (req: Request, res: Response) => {
const { offset, limit, categories } = req.query;
const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
const configLimit = Config.get().guild.discovery.limit;
let guilds;
if (categories == undefined) {
guilds = showAllGuilds
? await Guild.find({ take: Math.abs(Number(limit || configLimit)) })
? await Guild.find({
take: Math.abs(Number(limit || configLimit)),
})
: await Guild.find({
where: { features: Like(`%DISCOVERABLE%`) },
take: Math.abs(Number(limit || configLimit)),
@ -59,6 +70,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
offset: Number(offset || Config.get().guild.discovery.offset),
limit: Number(limit || configLimit),
});
});
},
);
export default router;

View File

@ -16,13 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Categories } from "@spacebar/util";
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import { Categories } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/categories", route({}), async (req: Request, res: Response) => {
router.get(
"/categories",
route({
responses: {
200: {
body: "APIDiscoveryCategoryArray",
},
},
}),
async (req: Request, res: Response) => {
// TODO:
// Get locale instead
@ -34,6 +43,7 @@ router.get("/categories", route({}), async (req: Request, res: Response) => {
: await Categories.find({ where: { is_primary: true } });
res.send(out);
});
},
);
export default router;

View File

@ -16,13 +16,23 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import { FieldErrors, Release } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
302: {},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { platform } = req.query;
if (!platform)
@ -42,6 +52,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
res.redirect(release.url);
});
},
);
export default router;

View File

@ -16,21 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@spacebar/api";
import { Config } from "@spacebar/util";
import { Router, Response, Request } from "express";
import { route, RouteOptions } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
const options: RouteOptions = {
test: {
response: {
router.get(
"/",
route({
responses: {
200: {
body: "GatewayBotResponse",
},
},
};
router.get("/", route(options), (req: Request, res: Response) => {
}),
(req: Request, res: Response) => {
const { endpointPublic } = Config.get().gateway;
res.json({
url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
@ -42,6 +43,7 @@ router.get("/", route(options), (req: Request, res: Response) => {
max_concurrency: 1,
},
});
});
},
);
export default router;

View File

@ -16,25 +16,27 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@spacebar/api";
import { Config } from "@spacebar/util";
import { Router, Response, Request } from "express";
import { route, RouteOptions } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
const options: RouteOptions = {
test: {
response: {
router.get(
"/",
route({
responses: {
200: {
body: "GatewayResponse",
},
},
};
router.get("/", route(options), (req: Request, res: Response) => {
}),
(req: Request, res: Response) => {
const { endpointPublic } = Config.get().gateway;
res.json({
url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
});
});
},
);
export default router;

View File

@ -16,15 +16,42 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import { TenorMediaTypes, getGifApiKey, parseGifResult } from "@spacebar/util";
import { Request, Response, Router } from "express";
import fetch from "node-fetch";
import ProxyAgent from "proxy-agent";
import { route } from "@spacebar/api";
import { getGifApiKey, parseGifResult } from "./trending";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
query: {
q: {
type: "string",
required: true,
description: "Search query",
},
media_format: {
type: "string",
description: "Media format",
values: Object.keys(TenorMediaTypes).filter((key) =>
isNaN(Number(key)),
),
},
locale: {
type: "string",
description: "Locale",
},
},
responses: {
200: {
body: "TenorGifsResponse",
},
},
}),
async (req: Request, res: Response) => {
// TODO: Custom providers
const { q, media_format, locale } = req.query;
@ -44,6 +71,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { results } = await response.json();
res.json(results.map(parseGifResult)).status(200);
});
},
);
export default router;

View File

@ -16,15 +16,37 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import { TenorMediaTypes, getGifApiKey, parseGifResult } from "@spacebar/util";
import { Request, Response, Router } from "express";
import fetch from "node-fetch";
import ProxyAgent from "proxy-agent";
import { route } from "@spacebar/api";
import { getGifApiKey, parseGifResult } from "./trending";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
query: {
media_format: {
type: "string",
description: "Media format",
values: Object.keys(TenorMediaTypes).filter((key) =>
isNaN(Number(key)),
),
},
locale: {
type: "string",
description: "Locale",
},
},
responses: {
200: {
body: "TenorGifsResponse",
},
},
}),
async (req: Request, res: Response) => {
// TODO: Custom providers
const { media_format, locale } = req.query;
@ -44,6 +66,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
const { results } = await response.json();
res.json(results.map(parseGifResult)).status(200);
});
},
);
export default router;

View File

@ -16,88 +16,35 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import {
TenorCategoriesResults,
TenorTrendingResults,
getGifApiKey,
parseGifResult,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import fetch from "node-fetch";
import ProxyAgent from "proxy-agent";
import { route } from "@spacebar/api";
import { Config } from "@spacebar/util";
import { HTTPError } from "lambert-server";
const router = Router();
// TODO: Move somewhere else
enum TENOR_GIF_TYPES {
gif,
mediumgif,
tinygif,
nanogif,
mp4,
loopedmp4,
tinymp4,
nanomp4,
webm,
tinywebm,
nanowebm,
}
type TENOR_MEDIA = {
preview: string;
url: string;
dims: number[];
size: number;
};
type TENOR_GIF = {
created: number;
hasaudio: boolean;
id: string;
media: { [type in keyof typeof TENOR_GIF_TYPES]: TENOR_MEDIA }[];
tags: string[];
title: string;
itemurl: string;
hascaption: boolean;
url: string;
};
type TENOR_CATEGORY = {
searchterm: string;
path: string;
image: string;
name: string;
};
type TENOR_CATEGORIES_RESULTS = {
tags: TENOR_CATEGORY[];
};
type TENOR_TRENDING_RESULTS = {
next: string;
results: TENOR_GIF[];
};
export function parseGifResult(result: TENOR_GIF) {
return {
id: result.id,
title: result.title,
url: result.itemurl,
src: result.media[0].mp4.url,
gif_src: result.media[0].gif.url,
width: result.media[0].mp4.dims[0],
height: result.media[0].mp4.dims[1],
preview: result.media[0].mp4.preview,
};
}
export function getGifApiKey() {
const { enabled, provider, apiKey } = Config.get().gif;
if (!enabled) throw new HTTPError(`Gifs are disabled`);
if (provider !== "tenor" || !apiKey)
throw new HTTPError(`${provider} gif provider not supported`);
return apiKey;
}
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
query: {
locale: {
type: "string",
description: "Locale",
},
},
responses: {
200: {
body: "TenorTrendingResponse",
},
},
}),
async (req: Request, res: Response) => {
// TODO: Custom providers
// TODO: return gifs as mp4
// const { media_format, locale } = req.query;
@ -126,8 +73,10 @@ router.get("/", route({}), async (req: Request, res: Response) => {
),
]);
const { tags } = (await responseSource.json()) as TENOR_CATEGORIES_RESULTS;
const { results } = (await trendGifSource.json()) as TENOR_TRENDING_RESULTS;
const { tags } =
(await responseSource.json()) as TenorCategoriesResults;
const { results } =
(await trendGifSource.json()) as TenorTrendingResults;
res.json({
categories: tags.map((x) => ({
@ -136,6 +85,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
})),
gifs: [parseGifResult(results[0])],
}).status(200);
});
},
);
export default router;

View File

@ -16,15 +16,24 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Guild, Config } from "@spacebar/util";
import { Config, Guild } from "@spacebar/util";
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
import { Like } from "typeorm";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildRecommendationsResponse",
},
},
}),
async (req: Request, res: Response) => {
// const { limit, personalization_disabled } = req.query;
const { limit } = req.query;
const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
@ -44,6 +53,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
recommended_guilds: guilds,
load_id: `server_recs/${genLoadId(32)}`,
}).status(200);
});
},
);
export default router;

View File

@ -16,20 +16,20 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { getIpAdress, route } from "@spacebar/api";
import {
Ban,
BanModeratorSchema,
BanRegistrySchema,
DiscordApiErrors,
emitEvent,
GuildBanAddEvent,
GuildBanRemoveEvent,
Ban,
User,
Member,
BanRegistrySchema,
BanModeratorSchema,
User,
emitEvent,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { getIpAdress, route } from "@spacebar/api";
const router: Router = Router();
@ -37,7 +37,17 @@ const router: Router = Router();
router.get(
"/",
route({ permission: "BAN_MEMBERS" }),
route({
permission: "BAN_MEMBERS",
responses: {
200: {
body: "GuildBansResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
@ -73,7 +83,20 @@ router.get(
router.get(
"/:user",
route({ permission: "BAN_MEMBERS" }),
route({
permission: "BAN_MEMBERS",
responses: {
200: {
body: "BanModeratorSchema",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const user_id = req.params.ban;
@ -97,7 +120,21 @@ router.get(
router.put(
"/:user_id",
route({ body: "BanCreateSchema", permission: "BAN_MEMBERS" }),
route({
requestBody: "BanCreateSchema",
permission: "BAN_MEMBERS",
responses: {
200: {
body: "Ban",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const banned_user_id = req.params.user_id;
@ -143,7 +180,20 @@ router.put(
router.put(
"/@me",
route({ body: "BanCreateSchema" }),
route({
requestBody: "BanCreateSchema",
responses: {
200: {
body: "Ban",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
@ -182,7 +232,18 @@ router.put(
router.delete(
"/:user_id",
route({ permission: "BAN_MEMBERS" }),
route({
permission: "BAN_MEMBERS",
responses: {
204: {},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, user_id } = req.params;

View File

@ -16,28 +16,52 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import {
Channel,
ChannelUpdateEvent,
emitEvent,
ChannelModifySchema,
ChannelReorderSchema,
ChannelUpdateEvent,
emitEvent,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
201: {
body: "APIChannelArray",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const channels = await Channel.find({ where: { guild_id } });
res.json(channels);
});
},
);
router.post(
"/",
route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }),
route({
requestBody: "ChannelModifySchema",
permission: "MANAGE_CHANNELS",
responses: {
201: {
body: "Channel",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel
const { guild_id } = req.params;
@ -54,7 +78,19 @@ router.post(
router.patch(
"/",
route({ body: "ChannelReorderSchema", permission: "MANAGE_CHANNELS" }),
route({
requestBody: "ChannelReorderSchema",
permission: "MANAGE_CHANNELS",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// changes guild channel position
const { guild_id } = req.params;

View File

@ -16,16 +16,29 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { emitEvent, GuildDeleteEvent, Guild } from "@spacebar/util";
import { Router, Request, Response } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
import { Guild, GuildDeleteEvent, emitEvent } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router = Router();
// discord prefixes this route with /delete instead of using the delete method
// docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild
router.post("/", route({}), async (req: Request, res: Response) => {
router.post(
"/",
route({
responses: {
204: {},
401: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({
@ -47,6 +60,7 @@ router.post("/", route({}), async (req: Request, res: Response) => {
]);
return res.sendStatus(204);
});
},
);
export default router;

View File

@ -16,12 +16,21 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildDiscoveryRequirementsResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
// TODO:
// Load from database
@ -50,6 +59,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
},
minimum_size: 0,
});
});
},
);
export default router;

View File

@ -16,25 +16,37 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import {
Config,
DiscordApiErrors,
emitEvent,
Emoji,
EmojiCreateSchema,
EmojiModifySchema,
GuildEmojisUpdateEvent,
handleFile,
Member,
Snowflake,
User,
EmojiCreateSchema,
EmojiModifySchema,
emitEvent,
handleFile,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "APIEmojiArray",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
@ -45,9 +57,25 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
return res.json(emojis);
});
},
);
router.get("/:emoji_id", route({}), async (req: Request, res: Response) => {
router.get(
"/:emoji_id",
route({
responses: {
200: {
body: "Emoji",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, emoji_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
@ -58,13 +86,25 @@ router.get("/:emoji_id", route({}), async (req: Request, res: Response) => {
});
return res.json(emoji);
});
},
);
router.post(
"/",
route({
body: "EmojiCreateSchema",
requestBody: "EmojiCreateSchema",
permission: "MANAGE_EMOJIS_AND_STICKERS",
responses: {
201: {
body: "Emoji",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
@ -113,8 +153,16 @@ router.post(
router.patch(
"/:emoji_id",
route({
body: "EmojiModifySchema",
requestBody: "EmojiModifySchema",
permission: "MANAGE_EMOJIS_AND_STICKERS",
responses: {
200: {
body: "Emoji",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { emoji_id, guild_id } = req.params;
@ -141,7 +189,15 @@ router.patch(
router.delete(
"/:emoji_id",
route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
route({
permission: "MANAGE_EMOJIS_AND_STICKERS",
responses: {
204: {},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { emoji_id, guild_id } = req.params;

View File

@ -16,25 +16,40 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import {
DiscordApiErrors,
Guild,
GuildUpdateEvent,
GuildUpdateSchema,
Member,
SpacebarApiErrors,
emitEvent,
getPermission,
getRights,
Guild,
GuildUpdateEvent,
handleFile,
Member,
GuildUpdateSchema,
SpacebarApiErrors,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
"200": {
body: "APIGuildWithJoinedAt",
},
401: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const [guild, member] = await Promise.all([
@ -51,11 +66,29 @@ router.get("/", route({}), async (req: Request, res: Response) => {
...guild,
joined_at: member?.joined_at,
});
});
},
);
router.patch(
"/",
route({ body: "GuildUpdateSchema", permission: "MANAGE_GUILD" }),
route({
requestBody: "GuildUpdateSchema",
permission: "MANAGE_GUILD",
responses: {
"200": {
body: "GuildUpdateSchema",
},
401: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as GuildUpdateSchema;
const { guild_id } = req.params;

View File

@ -16,15 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Invite, PublicInviteRelation } from "@spacebar/util";
import { route } from "@spacebar/api";
import { Invite, PublicInviteRelation } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.get(
"/",
route({ permission: "MANAGE_GUILD" }),
route({
permission: "MANAGE_GUILD",
responses: {
200: {
body: "APIInviteArray",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;

View File

@ -16,17 +16,27 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// TODO: member verification
res.status(404).json({
message: "Unknown Guild Member Verification Form",
code: 10068,
});
});
},
);
export default router;

View File

@ -16,25 +16,40 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import {
Member,
emitEvent,
Emoji,
getPermission,
getRights,
Role,
GuildMemberUpdateEvent,
emitEvent,
Sticker,
Emoji,
Guild,
GuildMemberUpdateEvent,
handleFile,
Member,
MemberChangeSchema,
Role,
Sticker,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "Member",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, member_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
@ -43,11 +58,28 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
return res.json(member);
});
},
);
router.patch(
"/",
route({ body: "MemberChangeSchema" }),
route({
requestBody: "MemberChangeSchema",
responses: {
200: {
body: "Member",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const member_id =
@ -119,7 +151,22 @@ router.patch(
},
);
router.put("/", route({}), async (req: Request, res: Response) => {
router.put(
"/",
route({
responses: {
200: {
body: "MemberJoinGuildResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// TODO: Lurker mode
const rights = await getRights(req.user_id);
@ -151,9 +198,20 @@ router.put("/", route({}), async (req: Request, res: Response) => {
await Member.addToGuild(member_id, guild_id);
res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
});
},
);
router.delete("/", route({}), async (req: Request, res: Response) => {
router.delete(
"/",
route({
responses: {
204: {},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, member_id } = req.params;
const permission = await getPermission(req.user_id, guild_id);
const rights = await getRights(req.user_id);
@ -167,6 +225,7 @@ router.delete("/", route({}), async (req: Request, res: Response) => {
await Member.removeFromGuild(member_id, guild_id);
res.sendStatus(204);
});
},
);
export default router;

View File

@ -16,15 +16,26 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { getPermission, Member, PermissionResolvable } from "@spacebar/util";
import { route } from "@spacebar/api";
import { getPermission, Member, PermissionResolvable } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.patch(
"/",
route({ body: "MemberNickChangeSchema" }),
route({
requestBody: "MemberNickChangeSchema",
responses: {
200: {},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
let permissionString: PermissionResolvable = "MANAGE_NICKNAMES";

View File

@ -16,15 +16,23 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Member } from "@spacebar/util";
import { route } from "@spacebar/api";
import { Member } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.delete(
"/",
route({ permission: "MANAGE_ROLES" }),
route({
permission: "MANAGE_ROLES",
responses: {
204: {},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, role_id, member_id } = req.params;
@ -35,7 +43,13 @@ router.delete(
router.put(
"/",
route({ permission: "MANAGE_ROLES" }),
route({
permission: "MANAGE_ROLES",
responses: {
204: {},
403: {},
},
}),
async (req: Request, res: Response) => {
const { guild_id, role_id, member_id } = req.params;

View File

@ -16,18 +16,40 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { Member, PublicMemberProjection } from "@spacebar/util";
import { route } from "@spacebar/api";
import { MoreThan } from "typeorm";
import { Member, PublicMemberProjection } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { MoreThan } from "typeorm";
const router = Router();
// TODO: send over websocket
// TODO: check for GUILD_MEMBERS intent
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
query: {
limit: {
type: "number",
description:
"max number of members to return (1-1000). default 1",
},
after: {
type: "string",
},
},
responses: {
200: {
body: "APIMemberArray",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const limit = Number(req.query.limit) || 1;
if (limit > 1000 || limit < 1)
@ -45,6 +67,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
return res.json(members);
});
},
);
export default router;

View File

@ -18,15 +18,30 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import { getPermission, FieldErrors, Message, Channel } from "@spacebar/util";
import { Channel, FieldErrors, Message, getPermission } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { FindManyOptions, In, Like } from "typeorm";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildMessagesSearchResponse",
},
403: {
body: "APIErrorResponse",
},
422: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const {
channel_id,
content,
@ -104,7 +119,10 @@ router.get("/", route({}), async (req: Request, res: Response) => {
req.params.guild_id,
channel.id,
);
if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY"))
if (
!perm.has("VIEW_CHANNEL") ||
!perm.has("READ_MESSAGE_HISTORY")
)
continue;
ids.push(channel.id);
}
@ -152,6 +170,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
messages: messagesDto,
total_results: messages.length,
});
});
},
);
export default router;

View File

@ -31,7 +31,20 @@ const router = Router();
router.patch(
"/:member_id",
route({ body: "MemberChangeProfileSchema" }),
route({
requestBody: "MemberChangeProfileSchema",
responses: {
200: {
body: "Member",
},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
// const member_id =

View File

@ -16,14 +16,14 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { Guild, Member, Snowflake } from "@spacebar/util";
import { LessThan, IsNull } from "typeorm";
import { route } from "@spacebar/api";
import { Guild, Member, Snowflake } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { IsNull, LessThan } from "typeorm";
const router = Router();
//Returns all inactive members, respecting role hierarchy
export const inactiveMembers = async (
const inactiveMembers = async (
guild_id: string,
user_id: string,
days: number,
@ -80,7 +80,16 @@ export const inactiveMembers = async (
return members;
};
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
"200": {
body: "GuildPruneResponse",
},
},
}),
async (req: Request, res: Response) => {
const days = parseInt(req.query.days as string);
let roles = req.query.include_roles;
@ -94,11 +103,23 @@ router.get("/", route({}), async (req: Request, res: Response) => {
);
res.send({ pruned: members.length });
});
},
);
router.post(
"/",
route({ permission: "KICK_MEMBERS", right: "KICK_BAN_MEMBERS" }),
route({
permission: "KICK_MEMBERS",
right: "KICK_BAN_MEMBERS",
responses: {
200: {
body: "GuildPurgeResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const days = parseInt(req.body.days);

View File

@ -16,13 +16,25 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { getIpAdress, getVoiceRegions, route } from "@spacebar/api";
import { Guild } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { getVoiceRegions, route, getIpAdress } from "@spacebar/api";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "APIGuildVoiceRegion",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
//TODO we should use an enum for guild's features and not hardcoded strings
@ -32,6 +44,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
guild.features.includes("VIP_REGIONS"),
),
);
});
},
);
export default router;

View File

@ -16,31 +16,63 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import {
Role,
Member,
GuildRoleUpdateEvent,
GuildRoleDeleteEvent,
emitEvent,
GuildRoleDeleteEvent,
GuildRoleUpdateEvent,
handleFile,
Member,
Role,
RoleModifySchema,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "Role",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, role_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
const role = await Role.findOneOrFail({ where: { guild_id, id: role_id } });
const role = await Role.findOneOrFail({
where: { guild_id, id: role_id },
});
return res.json(role);
});
},
);
router.delete(
"/",
route({ permission: "MANAGE_ROLES" }),
route({
permission: "MANAGE_ROLES",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, role_id } = req.params;
if (role_id === guild_id)
@ -69,7 +101,24 @@ router.delete(
router.patch(
"/",
route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }),
route({
requestBody: "RoleModifySchema",
permission: "MANAGE_ROLES",
responses: {
200: {
body: "Role",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { role_id, guild_id } = req.params;
const body = req.body as RoleModifySchema;

View File

@ -16,21 +16,20 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import {
Role,
getPermission,
Member,
GuildRoleCreateEvent,
GuildRoleUpdateEvent,
emitEvent,
Config,
DiscordApiErrors,
emitEvent,
GuildRoleCreateEvent,
GuildRoleUpdateEvent,
Member,
Role,
RoleModifySchema,
RolePositionUpdateSchema,
Snowflake,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
import { Not } from "typeorm";
const router: Router = Router();
@ -47,7 +46,21 @@ router.get("/", route({}), async (req: Request, res: Response) => {
router.post(
"/",
route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }),
route({
requestBody: "RoleModifySchema",
permission: "MANAGE_ROLES",
responses: {
200: {
body: "Role",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const guild_id = req.params.guild_id;
const body = req.body as RoleModifySchema;
@ -104,14 +117,25 @@ router.post(
router.patch(
"/",
route({ body: "RolePositionUpdateSchema" }),
route({
requestBody: "RolePositionUpdateSchema",
permission: "MANAGE_ROLES",
responses: {
200: {
body: "APIRoleArray",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const body = req.body as RolePositionUpdateSchema;
const perms = await getPermission(req.user_id, guild_id);
perms.hasThrow("MANAGE_ROLES");
await Promise.all(
body.map(async (x) =>
Role.update({ guild_id, id: x.id }, { position: x.position }),

View File

@ -16,29 +16,42 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@spacebar/api";
import {
emitEvent,
GuildStickersUpdateEvent,
Member,
ModifyGuildStickerSchema,
Snowflake,
Sticker,
StickerFormatType,
StickerType,
emitEvent,
uploadFile,
ModifyGuildStickerSchema,
} from "@spacebar/util";
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import multer from "multer";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import multer from "multer";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "APIStickerArray",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
res.json(await Sticker.find({ where: { guild_id } }));
});
},
);
const bodyParser = multer({
limits: {
@ -54,7 +67,18 @@ router.post(
bodyParser,
route({
permission: "MANAGE_EMOJIS_AND_STICKERS",
body: "ModifyGuildStickerSchema",
requestBody: "ModifyGuildStickerSchema",
responses: {
200: {
body: "Sticker",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
if (!req.file) throw new HTTPError("missing file");
@ -81,7 +105,7 @@ router.post(
},
);
export function getStickerFormat(mime_type: string) {
function getStickerFormat(mime_type: string) {
switch (mime_type) {
case "image/apng":
return StickerFormatType.APNG;
@ -98,20 +122,46 @@ export function getStickerFormat(mime_type: string) {
}
}
router.get("/:sticker_id", route({}), async (req: Request, res: Response) => {
router.get(
"/:sticker_id",
route({
responses: {
200: {
body: "Sticker",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, sticker_id } = req.params;
await Member.IsInGuildOrFail(req.user_id, guild_id);
res.json(
await Sticker.findOneOrFail({ where: { guild_id, id: sticker_id } }),
await Sticker.findOneOrFail({
where: { guild_id, id: sticker_id },
}),
);
});
},
);
router.patch(
"/:sticker_id",
route({
body: "ModifyGuildStickerSchema",
requestBody: "ModifyGuildStickerSchema",
permission: "MANAGE_EMOJIS_AND_STICKERS",
responses: {
200: {
body: "Sticker",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, sticker_id } = req.params;
@ -141,7 +191,15 @@ async function sendStickerUpdateEvent(guild_id: string) {
router.delete(
"/:sticker_id",
route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }),
route({
permission: "MANAGE_EMOJIS_AND_STICKERS",
responses: {
204: {},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id, sticker_id } = req.params;

View File

@ -16,11 +16,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { generateCode, route } from "@spacebar/api";
import { Guild, Template } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
import { generateCode } from "@spacebar/api";
const router: Router = Router();
@ -41,7 +40,16 @@ const TemplateGuildProjection: (keyof Guild)[] = [
"icon",
];
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "APITemplateArray",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const templates = await Template.find({
@ -49,11 +57,29 @@ router.get("/", route({}), async (req: Request, res: Response) => {
});
return res.json(templates);
});
},
);
router.post(
"/",
route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }),
route({
requestBody: "TemplateCreateSchema",
permission: "MANAGE_GUILD",
responses: {
200: {
body: "Template",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({
@ -81,7 +107,13 @@ router.post(
router.delete(
"/:code",
route({ permission: "MANAGE_GUILD" }),
route({
permission: "MANAGE_GUILD",
responses: {
200: { body: "Template" },
403: { body: "APIErrorResponse" },
},
}),
async (req: Request, res: Response) => {
const { code, guild_id } = req.params;
@ -96,7 +128,13 @@ router.delete(
router.put(
"/:code",
route({ permission: "MANAGE_GUILD" }),
route({
permission: "MANAGE_GUILD",
responses: {
200: { body: "Template" },
403: { body: "APIErrorResponse" },
},
}),
async (req: Request, res: Response) => {
const { code, guild_id } = req.params;
const guild = await Guild.findOneOrFail({
@ -115,7 +153,14 @@ router.put(
router.patch(
"/:code",
route({ body: "TemplateModifySchema", permission: "MANAGE_GUILD" }),
route({
requestBody: "TemplateModifySchema",
permission: "MANAGE_GUILD",
responses: {
200: { body: "Template" },
403: { body: "APIErrorResponse" },
},
}),
async (req: Request, res: Response) => {
const { code, guild_id } = req.params;
const { name, description } = req.body;

View File

@ -16,6 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@spacebar/api";
import {
Channel,
ChannelType,
@ -23,8 +24,7 @@ import {
Invite,
VanityUrlSchema,
} from "@spacebar/util";
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router = Router();
@ -33,7 +33,20 @@ const InviteRegex = /\W/g;
router.get(
"/",
route({ permission: "MANAGE_GUILD" }),
route({
permission: "MANAGE_GUILD",
responses: {
200: {
body: "GuildVanityUrlResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
@ -60,7 +73,21 @@ router.get(
router.patch(
"/",
route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }),
route({
requestBody: "VanityUrlSchema",
permission: "MANAGE_GUILD",
responses: {
200: {
body: "GuildVanityUrlCreateResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const body = req.body as VanityUrlSchema;

View File

@ -16,6 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { route } from "@spacebar/api";
import {
Channel,
ChannelType,
@ -26,7 +27,6 @@ import {
VoiceStateUpdateEvent,
VoiceStateUpdateSchema,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
@ -34,7 +34,21 @@ const router = Router();
router.patch(
"/",
route({ body: "VoiceStateUpdateSchema" }),
route({
requestBody: "VoiceStateUpdateSchema",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as VoiceStateUpdateSchema;
const { guild_id } = req.params;

View File

@ -16,27 +16,49 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { Guild, Member, GuildUpdateWelcomeScreenSchema } from "@spacebar/util";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
import { Guild, GuildUpdateWelcomeScreenSchema, Member } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildWelcomeScreen",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const guild_id = req.params.guild_id;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
await Member.IsInGuildOrFail(req.user_id, guild_id);
res.json(guild.welcome_screen);
});
},
);
router.patch(
"/",
route({
body: "GuildUpdateWelcomeScreenSchema",
requestBody: "GuildUpdateWelcomeScreenSchema",
permission: "MANAGE_GUILD",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const guild_id = req.params.guild_id;

View File

@ -16,10 +16,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { Permissions, Guild, Invite, Channel, Member } from "@spacebar/util";
import { HTTPError } from "lambert-server";
import { random, route } from "@spacebar/api";
import { Channel, Guild, Invite, Member, Permissions } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router: Router = Router();
@ -32,7 +32,19 @@ const router: Router = Router();
// https://discord.com/developers/docs/resources/guild#get-guild-widget
// TODO: Cache the response for a guild for 5 minutes regardless of response
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildWidgetJsonResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
@ -103,6 +115,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
res.set("Cache-Control", "public, max-age=300");
return res.json(data);
});
},
);
export default router;

View File

@ -18,11 +18,11 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Request, Response, Router } from "express";
import { Guild } from "@spacebar/util";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
import { Guild } from "@spacebar/util";
import { Request, Response, Router } from "express";
import fs from "fs";
import { HTTPError } from "lambert-server";
import path from "path";
const router: Router = Router();
@ -31,7 +31,20 @@ const router: Router = Router();
// https://discord.com/developers/docs/resources/guild#get-guild-widget-image
// TODO: Cache the response
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
@ -45,7 +58,9 @@ router.get("/", route({}), async (req: Request, res: Response) => {
// Fetch parameter
const style = req.query.style?.toString() || "shield";
if (
!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)
!["shield", "banner1", "banner2", "banner3", "banner4"].includes(
style,
)
) {
throw new HTTPError(
"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
@ -96,7 +111,15 @@ router.get("/", route({}), async (req: Request, res: Response) => {
break;
case "banner1":
if (icon) await drawIcon(ctx, 20, 27, 50, icon);
await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22);
await drawText(
ctx,
83,
51,
"#FFFFFF",
"12px Verdana",
name,
22,
);
await drawText(
ctx,
83,
@ -108,7 +131,15 @@ router.get("/", route({}), async (req: Request, res: Response) => {
break;
case "banner2":
if (icon) await drawIcon(ctx, 13, 19, 36, icon);
await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15);
await drawText(
ctx,
62,
34,
"#FFFFFF",
"12px Verdana",
name,
15,
);
await drawText(
ctx,
62,
@ -120,7 +151,15 @@ router.get("/", route({}), async (req: Request, res: Response) => {
break;
case "banner3":
if (icon) await drawIcon(ctx, 20, 20, 50, icon);
await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27);
await drawText(
ctx,
83,
44,
"#FFFFFF",
"12px Verdana",
name,
27,
);
await drawText(
ctx,
83,
@ -132,7 +171,15 @@ router.get("/", route({}), async (req: Request, res: Response) => {
break;
case "banner4":
if (icon) await drawIcon(ctx, 21, 136, 50, icon);
await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27);
await drawText(
ctx,
84,
156,
"#FFFFFF",
"13px Verdana",
name,
27,
);
await drawText(
ctx,
84,
@ -154,7 +201,8 @@ router.get("/", route({}), async (req: Request, res: Response) => {
res.set("Content-Type", "image/png");
res.set("Cache-Control", "public, max-age=3600");
return res.send(buffer);
});
},
);
async function drawIcon(
canvas: any,

View File

@ -16,14 +16,26 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { Guild, WidgetModifySchema } from "@spacebar/util";
import { route } from "@spacebar/api";
import { Guild, WidgetModifySchema } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router: Router = Router();
// https://discord.com/developers/docs/resources/guild#get-guild-widget-settings
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "GuildWidgetSettingsResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
@ -32,12 +44,27 @@ router.get("/", route({}), async (req: Request, res: Response) => {
enabled: guild.widget_enabled || false,
channel_id: guild.widget_channel_id || null,
});
});
},
);
// https://discord.com/developers/docs/resources/guild#modify-guild-widget
router.patch(
"/",
route({ body: "WidgetModifySchema", permission: "MANAGE_GUILD" }),
route({
requestBody: "WidgetModifySchema",
permission: "MANAGE_GUILD",
responses: {
200: {
body: "WidgetModifySchema",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as WidgetModifySchema;
const { guild_id } = req.params;

View File

@ -16,16 +16,16 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import {
Guild,
Config,
getRights,
Member,
DiscordApiErrors,
GuildCreateSchema,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import {
Config,
DiscordApiErrors,
Guild,
GuildCreateSchema,
Member,
getRights,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
const router: Router = Router();
@ -33,7 +33,21 @@ const router: Router = Router();
router.post(
"/",
route({ body: "GuildCreateSchema", right: "CREATE_GUILDS" }),
route({
requestBody: "GuildCreateSchema",
right: "CREATE_GUILDS",
responses: {
201: {
body: "GuildCreateResponse",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as GuildCreateSchema;

View File

@ -16,28 +16,44 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import {
Template,
Config,
DiscordApiErrors,
Guild,
GuildTemplateCreateSchema,
Member,
Role,
Snowflake,
Config,
Member,
GuildTemplateCreateSchema,
Template,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import { DiscordApiErrors } from "@spacebar/util";
import { Request, Response, Router } from "express";
import fetch from "node-fetch";
const router: Router = Router();
router.get("/:code", route({}), async (req: Request, res: Response) => {
router.get(
"/:code",
route({
responses: {
200: {
body: "Template",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { allowDiscordTemplates, allowRaws, enabled } =
Config.get().templates;
if (!enabled)
res.json({
code: 403,
message: "Template creation & usage is disabled on this instance.",
message:
"Template creation & usage is disabled on this instance.",
}).sendStatus(403);
const { code } = req.params;
@ -75,13 +91,16 @@ router.get("/:code", route({}), async (req: Request, res: Response) => {
return res.json(code.split("external:", 2)[1]);
}
const template = await Template.findOneOrFail({ where: { code: code } });
const template = await Template.findOneOrFail({
where: { code: code },
});
res.json(template);
});
},
);
router.post(
"/:code",
route({ body: "GuildTemplateCreateSchema" }),
route({ requestBody: "GuildTemplateCreateSchema" }),
async (req: Request, res: Response) => {
const {
enabled,

View File

@ -16,22 +16,34 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import {
emitEvent,
getPermission,
Guild,
Invite,
InviteDeleteEvent,
User,
PublicInviteRelation,
User,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router: Router = Router();
router.get("/:code", route({}), async (req: Request, res: Response) => {
router.get(
"/:code",
route({
responses: {
"200": {
body: "Invite",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { code } = req.params;
const invite = await Invite.findOneOrFail({
@ -40,11 +52,28 @@ router.get("/:code", route({}), async (req: Request, res: Response) => {
});
res.status(200).send(invite);
});
},
);
router.post(
"/:code",
route({ right: "USE_MASS_INVITES" }),
route({
right: "USE_MASS_INVITES",
responses: {
"200": {
body: "Invite",
},
401: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { code } = req.params;
const { guild_id } = await Invite.findOneOrFail({
@ -75,14 +104,36 @@ router.post(
);
// * cant use permission of route() function because path doesn't have guild_id/channel_id
router.delete("/:code", route({}), async (req: Request, res: Response) => {
router.delete(
"/:code",
route({
responses: {
"200": {
body: "Invite",
},
401: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { code } = req.params;
const invite = await Invite.findOneOrFail({ where: { code } });
const { guild_id, channel_id } = invite;
const permission = await getPermission(req.user_id, guild_id, channel_id);
const permission = await getPermission(
req.user_id,
guild_id,
channel_id,
);
if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS"))
if (
!permission.has("MANAGE_GUILD") &&
!permission.has("MANAGE_CHANNELS")
)
throw new HTTPError(
"You missing the MANAGE_GUILD or MANAGE_CHANNELS permission",
401,
@ -102,6 +153,7 @@ router.delete("/:code", route({}), async (req: Request, res: Response) => {
]);
res.json({ invite: invite });
});
},
);
export default router;

View File

@ -16,23 +16,37 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import {
ApiError,
Application,
ApplicationAuthorizeSchema,
getPermission,
DiscordApiErrors,
Member,
Permissions,
User,
getPermission,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
// TODO: scopes, other oauth types
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
// TODO: I really didn't feel like typing all of it out
200: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// const { client_id, scope, response_type, redirect_url } = req.query;
const { client_id } = req.query;
@ -56,7 +70,13 @@ router.get("/", route({}), async (req: Request, res: Response) => {
id: req.user_id,
bot: false,
},
select: ["id", "username", "avatar", "discriminator", "public_flags"],
select: [
"id",
"username",
"avatar",
"discriminator",
"public_flags",
],
});
const guilds = await Member.find({
@ -131,11 +151,33 @@ router.get("/", route({}), async (req: Request, res: Response) => {
},
authorized: false,
});
});
},
);
router.post(
"/",
route({ body: "ApplicationAuthorizeSchema" }),
route({
requestBody: "ApplicationAuthorizeSchema",
query: {
client_id: {
type: "string",
},
},
responses: {
200: {
body: "OAuthAuthorizeResponse",
},
400: {
body: "APIErrorResponse",
},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as ApplicationAuthorizeSchema;
// const { client_id, scope, response_type, redirect_url } = req.query;

View File

@ -16,13 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import { Config } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "InstancePingResponse",
},
},
}),
(req: Request, res: Response) => {
const { general } = Config.get();
res.send({
ping: "pong!",
@ -39,6 +48,7 @@ router.get("/", route({}), (req: Request, res: Response) => {
tosPage: general.tosPage,
},
});
});
},
);
export default router;

View File

@ -16,16 +16,28 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Config } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "InstanceDomainsResponse",
},
},
}),
async (req: Request, res: Response) => {
const { cdn, gateway, api } = Config.get();
const IdentityForm = {
cdn: cdn.endpointPublic || process.env.CDN || "http://localhost:3001",
cdn:
cdn.endpointPublic ||
process.env.CDN ||
"http://localhost:3001",
gateway:
gateway.endpointPublic ||
process.env.GATEWAY ||
@ -35,6 +47,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
};
res.json(IdentityForm);
});
},
);
export default router;

View File

@ -16,14 +16,24 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Config } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "APIGeneralConfiguration",
},
},
}),
async (req: Request, res: Response) => {
const { general } = Config.get();
res.json(general);
});
},
);
export default router;

View File

@ -16,14 +16,24 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Config } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "APILimitsConfiguration",
},
},
}),
async (req: Request, res: Response) => {
const { limits } = Config.get();
res.json(limits);
});
},
);
export default router;

View File

@ -28,7 +28,19 @@ import {
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "InstanceStatsResponse",
},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
if (!Config.get().security.statsWorldReadable) {
const rights = await getRights(req.user_id);
rights.hasThrow("VIEW_SERVER_STATS");
@ -42,6 +54,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
members: await Member.count(),
},
});
});
},
);
export default router;

View File

@ -16,14 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { AckBulkSchema, ReadState } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.post(
"/",
route({ body: "AckBulkSchema" }),
route({
requestBody: "AckBulkSchema",
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as AckBulkSchema;

View File

@ -16,14 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
router.post("/", route({}), (req: Request, res: Response) => {
router.post(
"/",
route({
responses: {
204: {},
},
}),
(req: Request, res: Response) => {
// TODO:
res.sendStatus(204);
});
},
);
export default router;

View File

@ -16,16 +16,28 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import { StickerPack } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
const sticker_packs = await StickerPack.find({ relations: ["stickers"] });
router.get(
"/",
route({
responses: {
200: {
body: "APIStickerPackArray",
},
},
}),
async (req: Request, res: Response) => {
const sticker_packs = await StickerPack.find({
relations: ["stickers"],
});
res.json({ sticker_packs });
});
},
);
export default router;

View File

@ -16,15 +16,25 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Sticker } from "@spacebar/util";
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Sticker } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "Sticker",
},
},
}),
async (req: Request, res: Response) => {
const { sticker_id } = req.params;
res.json(await Sticker.find({ where: { id: sticker_id } }));
});
},
);
export default router;

View File

@ -16,14 +16,22 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.post(
"/",
route({ right: "OPERATOR" }),
route({
right: "OPERATOR",
responses: {
200: {},
403: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
console.log(`/stop was called by ${req.user_id} at ${new Date()}`);
res.sendStatus(200);

View File

@ -16,13 +16,28 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import { FieldErrors, Release } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "UpdatesResponse",
},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const platform = req.query.platform;
if (!platform)
@ -47,6 +62,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
url: release.url,
notes: release.notes,
});
});
},
);
export default router;

View File

@ -30,7 +30,18 @@ const router = Router();
router.post(
"/",
route({ right: "MANAGE_USERS" }),
route({
right: "MANAGE_USERS",
responses: {
204: {},
403: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
await User.findOneOrFail({
where: { id: req.params.id },

View File

@ -16,16 +16,26 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { User } from "@spacebar/util";
import { route } from "@spacebar/api";
import { User } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "APIPublicUser",
},
},
}),
async (req: Request, res: Response) => {
const { id } = req.params;
res.json(await User.getPublicUser(id));
});
},
);
export default router;

View File

@ -16,23 +16,23 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import {
User,
Member,
UserProfileModifySchema,
handleFile,
PrivateUserProjection,
emitEvent,
UserUpdateEvent,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import {
Member,
PrivateUserProjection,
User,
UserProfileModifySchema,
UserUpdateEvent,
emitEvent,
handleFile,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.get(
"/",
route({ test: { response: { body: "UserProfileResponse" } } }),
route({ responses: { 200: { body: "UserProfileResponse" } } }),
async (req: Request, res: Response) => {
if (req.params.id === "@me") req.params.id = req.user_id;
@ -151,7 +151,7 @@ router.get(
router.patch(
"/",
route({ body: "UserProfileModifySchema" }),
route({ requestBody: "UserProfileModifySchema" }),
async (req: Request, res: Response) => {
const body = req.body as UserProfileModifySchema;

View File

@ -16,17 +16,25 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { User } from "@spacebar/util";
import { route } from "@spacebar/api";
import { User, UserRelationsResponse } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.get(
"/",
route({ test: { response: { body: "UserRelationsResponse" } } }),
route({
responses: {
200: { body: "UserRelationsResponse" },
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const mutual_relations: object[] = [];
const mutual_relations: UserRelationsResponse = [];
const requested_relations = await User.findOneOrFail({
where: { id: req.params.id },
relations: ["relationships"],

View File

@ -16,32 +16,51 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
import {
Recipient,
DmChannelDTO,
Channel,
DmChannelCreateSchema,
DmChannelDTO,
Recipient,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "APIDMChannelArray",
},
},
}),
async (req: Request, res: Response) => {
const recipients = await Recipient.find({
where: { user_id: req.user_id, closed: false },
relations: ["channel", "channel.recipients"],
});
res.json(
await Promise.all(
recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id])),
recipients.map((r) =>
DmChannelDTO.from(r.channel, [req.user_id]),
),
),
);
});
},
);
router.post(
"/",
route({ body: "DmChannelCreateSchema" }),
route({
requestBody: "DmChannelCreateSchema",
responses: {
200: {
body: "DmChannelDTO",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as DmChannelCreateSchema;
res.json(

View File

@ -29,7 +29,7 @@ const router = Router();
// TODO: connection update schema
router.patch(
"/",
route({ body: "ConnectionUpdateSchema" }),
route({ requestBody: "ConnectionUpdateSchema" }),
async (req: Request, res: Response) => {
const { connection_name, connection_id } = req.params;
const body = req.body as ConnectionUpdateSchema;

View File

@ -16,15 +16,28 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { Member, User } from "@spacebar/util";
import { route } from "@spacebar/api";
import { Member, User } from "@spacebar/util";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
const router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
router.post(
"/",
route({
responses: {
204: {},
401: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
select: ["data"],
@ -33,7 +46,10 @@ router.post("/", route({}), async (req: Request, res: Response) => {
if (user.data.hash) {
// guest accounts can delete accounts without password
correctpass = await bcrypt.compare(req.body.password, user.data.hash);
correctpass = await bcrypt.compare(
req.body.password,
user.data.hash,
);
if (!correctpass) {
throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
}
@ -51,6 +67,7 @@ router.post("/", route({}), async (req: Request, res: Response) => {
} else {
res.sendStatus(401);
}
});
},
);
export default router;

View File

@ -16,14 +16,27 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { User } from "@spacebar/util";
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import { User } from "@spacebar/util";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
const router = Router();
router.post("/", route({}), async (req: Request, res: Response) => {
router.post(
"/",
route({
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const user = await User.findOneOrFail({
where: { id: req.user_id },
select: ["data"],
@ -32,7 +45,10 @@ router.post("/", route({}), async (req: Request, res: Response) => {
if (user.data.hash) {
// guest accounts can delete accounts without password
correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/
correctpass = await bcrypt.compare(
req.body.password,
user.data.hash,
); //Not sure if user typed right password :/
}
if (correctpass) {
@ -45,6 +61,7 @@ router.post("/", route({}), async (req: Request, res: Response) => {
code: 50018,
});
}
});
},
);
export default router;

View File

@ -16,22 +16,31 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import {
Config,
Guild,
Member,
User,
GuildDeleteEvent,
GuildMemberRemoveEvent,
Member,
User,
emitEvent,
Config,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { route } from "@spacebar/api";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "APIGuildArray",
},
},
}),
async (req: Request, res: Response) => {
const members = await Member.find({
relations: ["guild"],
where: { id: req.user_id },
@ -44,10 +53,24 @@ router.get("/", route({}), async (req: Request, res: Response) => {
}
res.json(guild);
});
},
);
// user send to leave a certain guild
router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
router.delete(
"/:guild_id",
route({
responses: {
204: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const { autoJoin } = Config.get().guild;
const { guild_id } = req.params;
const guild = await Guild.findOneOrFail({
@ -63,7 +86,10 @@ router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
autoJoin.guilds.includes(guild_id) &&
!autoJoin.canLeave
) {
throw new HTTPError("You can't leave instance auto join guilds", 400);
throw new HTTPError(
"You can't leave instance auto join guilds",
400,
);
}
await Promise.all([
@ -89,6 +115,7 @@ router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
} as GuildMemberRemoveEvent);
return res.sendStatus(204);
});
},
);
export default router;

View File

@ -16,29 +16,49 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Response, Request } from "express";
import { route } from "@spacebar/api";
import {
Channel,
Member,
OrmUtils,
UserGuildSettingsSchema,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
const router = Router();
// GET doesn't exist on discord.com
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {},
404: {},
},
}),
async (req: Request, res: Response) => {
const user = await Member.findOneOrFail({
where: { id: req.user_id, guild_id: req.params.guild_id },
select: ["settings"],
});
return res.json(user.settings);
});
},
);
router.patch(
"/",
route({ body: "UserGuildSettingsSchema" }),
route({
requestBody: "UserGuildSettingsSchema",
responses: {
200: {},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as UserGuildSettingsSchema;

View File

@ -16,36 +16,59 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import {
User,
PrivateUserProjection,
emitEvent,
UserUpdateEvent,
handleFile,
FieldErrors,
adjustEmail,
Config,
UserModifySchema,
emitEvent,
FieldErrors,
generateToken,
handleFile,
PrivateUserProjection,
User,
UserModifySchema,
UserUpdateEvent,
} from "@spacebar/util";
import { route } from "@spacebar/api";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
const router: Router = Router();
router.get("/", route({}), async (req: Request, res: Response) => {
router.get(
"/",
route({
responses: {
200: {
body: "APIPrivateUser",
},
},
}),
async (req: Request, res: Response) => {
res.json(
await User.findOne({
select: PrivateUserProjection,
where: { id: req.user_id },
}),
);
});
},
);
router.patch(
"/",
route({ body: "UserModifySchema" }),
route({
requestBody: "UserModifySchema",
responses: {
200: {
body: "UserUpdateResponse",
},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
const body = req.body as UserModifySchema;

View File

@ -16,21 +16,34 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
import {
BackupCode,
generateMfaBackupCodes,
User,
CodesVerificationSchema,
DiscordApiErrors,
User,
generateMfaBackupCodes,
} from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
router.post(
"/",
route({ body: "CodesVerificationSchema" }),
route({
requestBody: "CodesVerificationSchema",
responses: {
200: {
body: "APIBackupCodeArray",
},
400: {
body: "APIErrorResponse",
},
404: {
body: "APIErrorResponse",
},
},
}),
async (req: Request, res: Response) => {
// const { key, nonce, regenerate } = req.body as CodesVerificationSchema;
const { regenerate } = req.body as CodesVerificationSchema;

Some files were not shown because too many files have changed in this diff Show More