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 502370 additions and 6557 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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