diff --git a/src/api/routes/applications/#id/bot/index.ts b/src/api/routes/applications/#id/bot/index.ts
index e3f1832c..0a6e6fd4 100644
--- a/src/api/routes/applications/#id/bot/index.ts
+++ b/src/api/routes/applications/#id/bot/index.ts
@@ -16,78 +16,114 @@
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) => {
- const app = await Application.findOneOrFail({
- where: { id: req.params.id },
- relations: ["owner"],
- });
+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"],
+ });
- if (app.owner.id != req.user_id)
- throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ if (app.owner.id != req.user_id)
+ throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- const user = await User.register({
- username: app.name,
- password: undefined,
- id: app.id,
- req,
- });
+ const user = await User.register({
+ username: app.name,
+ password: undefined,
+ id: app.id,
+ req,
+ });
- user.id = app.id;
- user.premium_since = new Date();
- user.bot = true;
+ user.id = app.id;
+ user.premium_since = new Date();
+ user.bot = true;
- await user.save();
+ await user.save();
- // flags is NaN here?
- app.assign({ bot: user, flags: app.flags || 0 });
+ // flags is NaN here?
+ app.assign({ bot: user, flags: app.flags || 0 });
- await app.save();
+ await app.save();
- res.send({
- token: await generateToken(user.id),
- }).status(204);
-});
+ res.send({
+ token: await generateToken(user.id),
+ }).status(204);
+ },
+);
-router.post("/reset", route({}), 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 } });
+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 } });
- if (owner.id != req.user_id)
- throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+ if (owner.id != req.user_id)
+ throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- if (
- owner.totp_secret &&
- (!req.body.code || verifyToken(owner.totp_secret, req.body.code))
- )
- throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ if (
+ owner.totp_secret &&
+ (!req.body.code || verifyToken(owner.totp_secret, req.body.code))
+ )
+ throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
- bot.data = { hash: undefined, valid_tokens_since: new Date() };
+ bot.data = { hash: undefined, valid_tokens_since: new Date() };
- await bot.save();
+ await bot.save();
- const token = await generateToken(bot.id);
+ const token = await generateToken(bot.id);
- res.json({ token }).status(200);
-});
+ 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;
diff --git a/src/api/routes/applications/#id/entitlements.ts b/src/api/routes/applications/#id/entitlements.ts
index e88fb7f7..6388e6b3 100644
--- a/src/api/routes/applications/#id/entitlements.ts
+++ b/src/api/routes/applications/#id/entitlements.ts
@@ -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) => {
- // TODO:
- //const { exclude_consumed } = req.query;
- res.status(200).send([]);
-});
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ApplicationEntitlementsResponse",
+ },
+ },
+ }),
+ (req: Request, res: Response) => {
+ // TODO:
+ //const { exclude_consumed } = req.query;
+ res.status(200).send([]);
+ },
+);
export default router;
diff --git a/src/api/routes/applications/#id/index.ts b/src/api/routes/applications/#id/index.ts
index 067f5dad..c372869a 100644
--- a/src/api/routes/applications/#id/index.ts
+++ b/src/api/routes/applications/#id/index.ts
@@ -16,32 +16,55 @@
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) => {
- const app = await Application.findOneOrFail({
- where: { id: req.params.id },
- relations: ["owner", "bot"],
- });
- if (app.owner.id != req.user_id)
- throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+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"],
+ });
+ if (app.owner.id != req.user_id)
+ throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- return res.json(app);
-});
+ 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,23 +96,35 @@ router.patch(
},
);
-router.post("/delete", route({}), async (req: Request, res: Response) => {
- const app = await Application.findOneOrFail({
- where: { id: req.params.id },
- relations: ["bot", "owner"],
- });
- if (app.owner.id != req.user_id)
- throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
+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"],
+ });
+ if (app.owner.id != req.user_id)
+ throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION;
- if (
- app.owner.totp_secret &&
- (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))
- )
- throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
+ if (
+ app.owner.totp_secret &&
+ (!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 });
+ await Application.delete({ id: app.id });
- res.send().status(200);
-});
+ res.send().status(200);
+ },
+);
export default router;
diff --git a/src/api/routes/applications/#id/skus.ts b/src/api/routes/applications/#id/skus.ts
index fcb75423..dc4fad23 100644
--- a/src/api/routes/applications/#id/skus.ts
+++ b/src/api/routes/applications/#id/skus.ts
@@ -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) => {
- res.json([]).status(200);
-});
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ApplicationSkusResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.json([]).status(200);
+ },
+);
export default router;
diff --git a/src/api/routes/applications/detectable.ts b/src/api/routes/applications/detectable.ts
index a8e30894..5cf9d171 100644
--- a/src/api/routes/applications/detectable.ts
+++ b/src/api/routes/applications/detectable.ts
@@ -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) => {
- //TODO
- res.send([]).status(200);
-});
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "ApplicationDetectableResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ //TODO
+ res.send([]).status(200);
+ },
+);
export default router;
diff --git a/src/api/routes/applications/index.ts b/src/api/routes/applications/index.ts
index 80a19aa8..1e536a06 100644
--- a/src/api/routes/applications/index.ts
+++ b/src/api/routes/applications/index.ts
@@ -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) => {
- const results = await Application.find({
- where: { owner: { id: req.user_id } },
- relations: ["owner", "bot"],
- });
- res.json(results).status(200);
-});
+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 } });
diff --git a/src/api/routes/auth/forgot.ts b/src/api/routes/auth/forgot.ts
index e240dff2..6fa86021 100644
--- a/src/api/routes/auth/forgot.ts
+++ b/src/api/routes/auth/forgot.ts
@@ -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;
diff --git a/src/api/routes/auth/generate-registration-tokens.ts b/src/api/routes/auth/generate-registration-tokens.ts
index 723875f8..80fdaed1 100644
--- a/src/api/routes/auth/generate-registration-tokens.ts
+++ b/src/api/routes/auth/generate-registration-tokens.ts
@@ -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
diff --git a/src/api/routes/auth/location-metadata.ts b/src/api/routes/auth/location-metadata.ts
index 52a45c67..28293e59 100644
--- a/src/api/routes/auth/location-metadata.ts
+++ b/src/api/routes/auth/location-metadata.ts
@@ -16,20 +16,29 @@
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) => {
- //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;
- res.json({
- consent_required: false,
- country_code: country_code,
- promotional_email_opt_in: { required: true, pre_checked: false },
- });
-});
+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;
+ res.json({
+ consent_required: false,
+ country_code: country_code,
+ promotional_email_opt_in: { required: true, pre_checked: false },
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/auth/login.ts b/src/api/routes/auth/login.ts
index fe0b4f99..d3fc1fb4 100644
--- a/src/api/routes/auth/login.ts
+++ b/src/api/routes/auth/login.ts
@@ -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;
diff --git a/src/api/routes/auth/logout.ts b/src/api/routes/auth/logout.ts
index 51909afa..94a3e474 100644
--- a/src/api/routes/auth/logout.ts
+++ b/src/api/routes/auth/logout.ts
@@ -22,14 +22,25 @@ import { Request, Response, Router } from "express";
const router: Router = Router();
export default router;
-router.post("/", route({}), 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);
- } else {
- delete req.body.provider;
- delete req.body.voip_provider;
- if (Object.keys(req.body).length != 0)
- console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
- }
- res.status(204).send();
-});
+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,
+ );
+ } else {
+ delete req.body.provider;
+ delete req.body.voip_provider;
+ if (Object.keys(req.body).length != 0)
+ console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body);
+ }
+ res.status(204).send();
+ },
+);
diff --git a/src/api/routes/auth/mfa/totp.ts b/src/api/routes/auth/mfa/totp.ts
index 2396443d..4df408f9 100644
--- a/src/api/routes/auth/mfa/totp.ts
+++ b/src/api/routes/auth/mfa/totp.ts
@@ -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;
diff --git a/src/api/routes/auth/mfa/webauthn.ts b/src/api/routes/auth/mfa/webauthn.ts
index 1b387411..b58d2944 100644
--- a/src/api/routes/auth/mfa/webauthn.ts
+++ b/src/api/routes/auth/mfa/webauthn.ts
@@ -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 !
diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts
index 430c9532..321b4a65 100644
--- a/src/api/routes/auth/register.ts
+++ b/src/api/routes/auth/register.ts
@@ -16,25 +16,25 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { Request, Response, Router } from "express";
+import {
+ IPAnalysis,
+ getIpAdress,
+ isProxy,
+ route,
+ verifyCaptcha,
+} from "@spacebar/api";
import {
Config,
- generateToken,
- Invite,
FieldErrors,
- User,
- adjustEmail,
+ Invite,
RegisterSchema,
+ User,
ValidRegistrationToken,
+ adjustEmail,
+ generateToken,
} from "@spacebar/util";
-import {
- route,
- getIpAdress,
- IPAnalysis,
- isProxy,
- verifyCaptcha,
-} from "@spacebar/api";
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();
diff --git a/src/api/routes/auth/reset.ts b/src/api/routes/auth/reset.ts
index 852a43c7..f97045a6 100644
--- a/src/api/routes/auth/reset.ts
+++ b/src/api/routes/auth/reset.ts
@@ -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;
diff --git a/src/api/routes/auth/verify/index.ts b/src/api/routes/auth/verify/index.ts
index c1afcde9..a98c17fa 100644
--- a/src/api/routes/auth/verify/index.ts
+++ b/src/api/routes/auth/verify/index.ts
@@ -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;
diff --git a/src/api/routes/auth/verify/resend.ts b/src/api/routes/auth/verify/resend.ts
index f2727abd..701f0ea8 100644
--- a/src/api/routes/auth/verify/resend.ts
+++ b/src/api/routes/auth/verify/resend.ts
@@ -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 },
diff --git a/src/api/routes/auth/verify/view-backup-codes-challenge.ts b/src/api/routes/auth/verify/view-backup-codes-challenge.ts
index b12719ff..5407de82 100644
--- a/src/api/routes/auth/verify/view-backup-codes-challenge.ts
+++ b/src/api/routes/auth/verify/view-backup-codes-challenge.ts
@@ -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;
diff --git a/src/api/routes/channels/#channel_id/index.ts b/src/api/routes/channels/#channel_id/index.ts
index db0d4242..567c7c92 100644
--- a/src/api/routes/channels/#channel_id/index.ts
+++ b/src/api/routes/channels/#channel_id/index.ts
@@ -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;
diff --git a/src/api/routes/channels/#channel_id/invites.ts b/src/api/routes/channels/#channel_id/invites.ts
index 9f247fe8..b02f65d3 100644
--- a/src/api/routes/channels/#channel_id/invites.ts
+++ b/src/api/routes/channels/#channel_id/invites.ts
@@ -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({
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
index f098fa8e..a6dcae6b 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
@@ -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;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
index 909a459e..5ca645c0 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
@@ -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({
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
index cd4b243e..6bc03f53 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -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,38 +283,54 @@ router.get(
},
);
-router.delete("/", route({}), async (req: Request, res: Response) => {
- const { message_id, channel_id } = req.params;
+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);
+ const rights = await getRights(req.user_id);
- if (message.author_id !== req.user_id) {
- if (!rights.has("MANAGE_MESSAGES")) {
- const permission = await getPermission(
- req.user_id,
- channel.guild_id,
- channel_id,
- );
- permission.hasThrow("MANAGE_MESSAGES");
- }
- } else rights.hasThrow("SELF_DELETE_MESSAGES");
+ if (message.author_id !== req.user_id) {
+ if (!rights.has("MANAGE_MESSAGES")) {
+ const permission = await getPermission(
+ req.user_id,
+ channel.guild_id,
+ channel_id,
+ );
+ permission.hasThrow("MANAGE_MESSAGES");
+ }
+ } else rights.hasThrow("SELF_DELETE_MESSAGES");
- await Message.delete({ id: message_id });
+ await Message.delete({ id: message_id });
- await emitEvent({
- event: "MESSAGE_DELETE",
- channel_id,
- data: {
- id: message_id,
+ await emitEvent({
+ event: "MESSAGE_DELETE",
channel_id,
- guild_id: channel.guild_id,
- },
- } as MessageDeleteEvent);
+ data: {
+ id: message_id,
+ channel_id,
+ guild_id: channel.guild_id,
+ },
+ } as MessageDeleteEvent);
- res.sendStatus(204);
-});
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
index cb66cd64..5efa0f14 100644
--- a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
+++ b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
@@ -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;
diff --git a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
index 18476d5c..9b607d59 100644
--- a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
+++ b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
@@ -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({
diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index 7f0c9fb5..f031fa75 100644
--- a/src/api/routes/channels/#channel_id/messages/index.ts
+++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -16,165 +16,171 @@
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) => {
- const channel_id = req.params.channel_id;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- });
- if (!channel) throw new HTTPError("Channel not found", 404);
-
- isTextChannel(channel.type);
- const around = req.query.around ? `${req.query.around}` : undefined;
- const before = req.query.before ? `${req.query.before}` : undefined;
- const after = req.query.after ? `${req.query.after}` : undefined;
- const limit = Number(req.query.limit) || 50;
- if (limit < 1 || limit > 100)
- throw new HTTPError("limit must be between 1 and 100", 422);
-
- const halfLimit = Math.floor(limit / 2);
-
- const permissions = await getPermission(
- req.user_id,
- channel.guild_id,
- channel_id,
- );
- permissions.hasThrow("VIEW_CHANNEL");
- if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
-
- const query: FindManyOptions<Message> & {
- where: { id?: FindOperator<string> | FindOperator<string>[] };
- } = {
- order: { timestamp: "DESC" },
- take: limit,
- where: { channel_id },
- relations: [
- "author",
- "webhook",
- "application",
- "mentions",
- "mention_roles",
- "mention_channels",
- "sticker_items",
- "attachments",
- ],
- };
-
- if (after) {
- if (BigInt(after) > BigInt(Snowflake.generate()))
- return res.status(422);
- query.where.id = MoreThan(after);
- } else if (before) {
- if (BigInt(before) < BigInt(req.params.channel_id))
- return res.status(422);
- query.where.id = LessThan(before);
- } else if (around) {
- query.where.id = [
- MoreThan((BigInt(around) - BigInt(halfLimit)).toString()),
- LessThan((BigInt(around) + BigInt(halfLimit)).toString()),
- ];
-
- return res.json([]); // TODO: fix around
- }
-
- const messages = await Message.find(query);
- const endpoint = Config.get().cdn.endpointPublic;
-
- return res.json(
- messages.map((x: Partial<Message>) => {
- (x.reactions || []).forEach((y: Partial<Reaction>) => {
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore
- if ((y.user_ids || []).includes(req.user_id)) y.me = true;
- delete y.user_ids;
- });
- if (!x.author)
- x.author = User.create({
- id: "4",
- discriminator: "0000",
- username: "Spacebar Ghost",
- public_flags: 0,
+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 },
+ });
+ if (!channel) throw new HTTPError("Channel not found", 404);
+
+ isTextChannel(channel.type);
+ const around = req.query.around ? `${req.query.around}` : undefined;
+ const before = req.query.before ? `${req.query.before}` : undefined;
+ const after = req.query.after ? `${req.query.after}` : undefined;
+ const limit = Number(req.query.limit) || 50;
+ if (limit < 1 || limit > 100)
+ throw new HTTPError("limit must be between 1 and 100", 422);
+
+ const halfLimit = Math.floor(limit / 2);
+
+ const permissions = await getPermission(
+ req.user_id,
+ channel.guild_id,
+ channel_id,
+ );
+ permissions.hasThrow("VIEW_CHANNEL");
+ if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]);
+
+ const query: FindManyOptions<Message> & {
+ where: { id?: FindOperator<string> | FindOperator<string>[] };
+ } = {
+ order: { timestamp: "DESC" },
+ take: limit,
+ where: { channel_id },
+ relations: [
+ "author",
+ "webhook",
+ "application",
+ "mentions",
+ "mention_roles",
+ "mention_channels",
+ "sticker_items",
+ "attachments",
+ ],
+ };
+
+ if (after) {
+ if (BigInt(after) > BigInt(Snowflake.generate()))
+ return res.status(422);
+ query.where.id = MoreThan(after);
+ } else if (before) {
+ if (BigInt(before) < BigInt(req.params.channel_id))
+ return res.status(422);
+ query.where.id = LessThan(before);
+ } else if (around) {
+ query.where.id = [
+ MoreThan((BigInt(around) - BigInt(halfLimit)).toString()),
+ LessThan((BigInt(around) + BigInt(halfLimit)).toString()),
+ ];
+
+ return res.json([]); // TODO: fix around
+ }
+
+ const messages = await Message.find(query);
+ const endpoint = Config.get().cdn.endpointPublic;
+
+ return res.json(
+ messages.map((x: Partial<Message>) => {
+ (x.reactions || []).forEach((y: Partial<Reaction>) => {
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ if ((y.user_ids || []).includes(req.user_id)) y.me = true;
+ delete y.user_ids;
+ });
+ if (!x.author)
+ x.author = User.create({
+ id: "4",
+ discriminator: "0000",
+ username: "Spacebar Ghost",
+ public_flags: 0,
+ });
+ x.attachments?.forEach((y: Attachment) => {
+ // dynamically set attachment proxy_url in case the endpoint changed
+ const uri = y.proxy_url.startsWith("http")
+ ? y.proxy_url
+ : `https://example.org${y.proxy_url}`;
+ y.proxy_url = `${endpoint == null ? "" : endpoint}${
+ new URL(uri).pathname
+ }`;
});
- x.attachments?.forEach((y: Attachment) => {
- // dynamically set attachment proxy_url in case the endpoint changed
- const uri = y.proxy_url.startsWith("http")
- ? y.proxy_url
- : `https://example.org${y.proxy_url}`;
- y.proxy_url = `${endpoint == null ? "" : endpoint}${
- new URL(uri).pathname
- }`;
- });
- /**
+ /**
Some clients ( discord.js ) only check if a property exists within the response,
which causes errors when, say, the `application` property is `null`.
**/
- // for (var curr in x) {
- // if (x[curr] === null)
- // delete x[curr];
- // }
+ // for (var curr in x) {
+ // if (x[curr] === null)
+ // delete x[curr];
+ // }
- return x;
- }),
- );
-});
+ 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;
diff --git a/src/api/routes/channels/#channel_id/permissions.ts b/src/api/routes/channels/#channel_id/permissions.ts
index 68dbc2f2..d3edb0fa 100644
--- a/src/api/routes/channels/#channel_id/permissions.ts
+++ b/src/api/routes/channels/#channel_id/permissions.ts
@@ -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;
diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts
index 32820916..724ebffd 100644
--- a/src/api/routes/channels/#channel_id/pins.ts
+++ b/src/api/routes/channels/#channel_id/pins.ts
@@ -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;
diff --git a/src/api/routes/channels/#channel_id/purge.ts b/src/api/routes/channels/#channel_id/purge.ts
index c8da6760..012fec1c 100644
--- a/src/api/routes/channels/#channel_id/purge.ts
+++ b/src/api/routes/channels/#channel_id/purge.ts
@@ -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;
diff --git a/src/api/routes/channels/#channel_id/recipients.ts b/src/api/routes/channels/#channel_id/recipients.ts
index f1fb48af..569bb5cd 100644
--- a/src/api/routes/channels/#channel_id/recipients.ts
+++ b/src/api/routes/channels/#channel_id/recipients.ts
@@ -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,80 +28,98 @@ 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) => {
- const { channel_id, user_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- relations: ["recipients"],
- });
+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 },
+ relations: ["recipients"],
+ });
- if (channel.type !== ChannelType.GROUP_DM) {
- const recipients = [
- ...(channel.recipients?.map((r) => r.user_id) || []),
- user_id,
- ].unique();
+ if (channel.type !== ChannelType.GROUP_DM) {
+ const recipients = [
+ ...(channel.recipients?.map((r) => r.user_id) || []),
+ user_id,
+ ].unique();
- const new_channel = await Channel.createDMChannel(
- recipients,
- req.user_id,
- );
- return res.status(201).json(new_channel);
- } else {
- if (channel.recipients?.map((r) => r.user_id).includes(user_id)) {
- throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
- }
+ const new_channel = await Channel.createDMChannel(
+ recipients,
+ req.user_id,
+ );
+ return res.status(201).json(new_channel);
+ } else {
+ if (channel.recipients?.map((r) => r.user_id).includes(user_id)) {
+ throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
+ }
- channel.recipients?.push(
- Recipient.create({ channel_id: channel_id, user_id: user_id }),
- );
- await channel.save();
+ channel.recipients?.push(
+ Recipient.create({ channel_id: channel_id, user_id: user_id }),
+ );
+ await channel.save();
- await emitEvent({
- event: "CHANNEL_CREATE",
- data: await DmChannelDTO.from(channel, [user_id]),
- user_id: user_id,
- });
+ await emitEvent({
+ event: "CHANNEL_CREATE",
+ data: await DmChannelDTO.from(channel, [user_id]),
+ user_id: user_id,
+ });
- await emitEvent({
- event: "CHANNEL_RECIPIENT_ADD",
- data: {
+ await emitEvent({
+ event: "CHANNEL_RECIPIENT_ADD",
+ data: {
+ channel_id: channel_id,
+ user: await User.findOneOrFail({
+ where: { id: user_id },
+ select: PublicUserProjection,
+ }),
+ },
channel_id: channel_id,
- user: await User.findOneOrFail({
- where: { id: user_id },
- select: PublicUserProjection,
- }),
- },
- channel_id: channel_id,
- } as ChannelRecipientAddEvent);
- return res.sendStatus(204);
- }
-});
+ } as ChannelRecipientAddEvent);
+ return res.sendStatus(204);
+ }
+ },
+);
-router.delete("/:user_id", route({}), async (req: Request, res: Response) => {
- const { channel_id, user_id } = req.params;
- const channel = await Channel.findOneOrFail({
- where: { id: channel_id },
- relations: ["recipients"],
- });
- if (
- !(
- channel.type === ChannelType.GROUP_DM &&
- (channel.owner_id === req.user_id || user_id === req.user_id)
+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 },
+ relations: ["recipients"],
+ });
+ if (
+ !(
+ channel.type === ChannelType.GROUP_DM &&
+ (channel.owner_id === req.user_id || user_id === req.user_id)
+ )
)
- )
- throw DiscordApiErrors.MISSING_PERMISSIONS;
+ throw DiscordApiErrors.MISSING_PERMISSIONS;
- if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) {
- throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
- }
+ if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) {
+ throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error?
+ }
- await Channel.removeRecipientFromChannel(channel, user_id);
+ await Channel.removeRecipientFromChannel(channel, user_id);
- return res.sendStatus(204);
-});
+ return res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/channels/#channel_id/typing.ts b/src/api/routes/channels/#channel_id/typing.ts
index 6a2fef39..b5d61d74 100644
--- a/src/api/routes/channels/#channel_id/typing.ts
+++ b/src/api/routes/channels/#channel_id/typing.ts
@@ -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;
diff --git a/src/api/routes/channels/#channel_id/webhooks.ts b/src/api/routes/channels/#channel_id/webhooks.ts
index 14791a1c..d54756a1 100644
--- a/src/api/routes/channels/#channel_id/webhooks.ts
+++ b/src/api/routes/channels/#channel_id/webhooks.ts
@@ -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) => {
- res.json([]);
-});
+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({
diff --git a/src/api/routes/connections/#connection_name/callback.ts b/src/api/routes/connections/#connection_name/callback.ts
index bc9ba455..ee0db94a 100644
--- a/src/api/routes/connections/#connection_name/callback.ts
+++ b/src/api/routes/connections/#connection_name/callback.ts
@@ -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);
diff --git a/src/api/routes/discoverable-guilds.ts b/src/api/routes/discoverable-guilds.ts
index 75eb6088..b8c6a386 100644
--- a/src/api/routes/discoverable-guilds.ts
+++ b/src/api/routes/discoverable-guilds.ts
@@ -16,49 +16,61 @@
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) => {
- 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({
- where: { features: Like(`%DISCOVERABLE%`) },
- take: Math.abs(Number(limit || configLimit)),
- });
- } else {
- guilds = showAllGuilds
- ? await Guild.find({
- where: { primary_category_id: categories.toString() },
- take: Math.abs(Number(limit || configLimit)),
- })
- : await Guild.find({
- where: {
- primary_category_id: categories.toString(),
- features: Like("%DISCOVERABLE%"),
- },
- take: Math.abs(Number(limit || configLimit)),
- });
- }
+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({
+ where: { features: Like(`%DISCOVERABLE%`) },
+ take: Math.abs(Number(limit || configLimit)),
+ });
+ } else {
+ guilds = showAllGuilds
+ ? await Guild.find({
+ where: { primary_category_id: categories.toString() },
+ take: Math.abs(Number(limit || configLimit)),
+ })
+ : await Guild.find({
+ where: {
+ primary_category_id: categories.toString(),
+ features: Like("%DISCOVERABLE%"),
+ },
+ take: Math.abs(Number(limit || configLimit)),
+ });
+ }
- const total = guilds ? guilds.length : undefined;
+ const total = guilds ? guilds.length : undefined;
- res.send({
- total: total,
- guilds: guilds,
- offset: Number(offset || Config.get().guild.discovery.offset),
- limit: Number(limit || configLimit),
- });
-});
+ res.send({
+ total: total,
+ guilds: guilds,
+ offset: Number(offset || Config.get().guild.discovery.offset),
+ limit: Number(limit || configLimit),
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/discovery.ts b/src/api/routes/discovery.ts
index 0c8089e4..a045c191 100644
--- a/src/api/routes/discovery.ts
+++ b/src/api/routes/discovery.ts
@@ -16,24 +16,34 @@
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) => {
- // TODO:
- // Get locale instead
+router.get(
+ "/categories",
+ route({
+ responses: {
+ 200: {
+ body: "APIDiscoveryCategoryArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // TODO:
+ // Get locale instead
- // const { locale, primary_only } = req.query;
- const { primary_only } = req.query;
+ // const { locale, primary_only } = req.query;
+ const { primary_only } = req.query;
- const out = primary_only
- ? await Categories.find()
- : await Categories.find({ where: { is_primary: true } });
+ const out = primary_only
+ ? await Categories.find()
+ : await Categories.find({ where: { is_primary: true } });
- res.send(out);
-});
+ res.send(out);
+ },
+);
export default router;
diff --git a/src/api/routes/download.ts b/src/api/routes/download.ts
index c4eea8e8..85fb41be 100644
--- a/src/api/routes/download.ts
+++ b/src/api/routes/download.ts
@@ -16,32 +16,43 @@
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) => {
- const { platform } = req.query;
+router.get(
+ "/",
+ route({
+ responses: {
+ 302: {},
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { platform } = req.query;
+
+ if (!platform)
+ throw FieldErrors({
+ platform: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
- if (!platform)
- throw FieldErrors({
- platform: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ const release = await Release.findOneOrFail({
+ where: {
+ enabled: true,
+ platform: platform as string,
},
+ order: { pub_date: "DESC" },
});
- const release = await Release.findOneOrFail({
- where: {
- enabled: true,
- platform: platform as string,
- },
- order: { pub_date: "DESC" },
- });
-
- res.redirect(release.url);
-});
+ res.redirect(release.url);
+ },
+);
export default router;
diff --git a/src/api/routes/gateway/bot.ts b/src/api/routes/gateway/bot.ts
index 243159ec..d9101159 100644
--- a/src/api/routes/gateway/bot.ts
+++ b/src/api/routes/gateway/bot.ts
@@ -16,32 +16,34 @@
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: {
- body: "GatewayBotResponse",
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GatewayBotResponse",
+ },
},
+ }),
+ (req: Request, res: Response) => {
+ const { endpointPublic } = Config.get().gateway;
+ res.json({
+ url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
+ shards: 1,
+ session_start_limit: {
+ total: 1000,
+ remaining: 999,
+ reset_after: 14400000,
+ max_concurrency: 1,
+ },
+ });
},
-};
-
-router.get("/", route(options), (req: Request, res: Response) => {
- const { endpointPublic } = Config.get().gateway;
- res.json({
- url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
- shards: 1,
- session_start_limit: {
- total: 1000,
- remaining: 999,
- reset_after: 14400000,
- max_concurrency: 1,
- },
- });
-});
+);
export default router;
diff --git a/src/api/routes/gateway/index.ts b/src/api/routes/gateway/index.ts
index 12e96919..9100d5ee 100644
--- a/src/api/routes/gateway/index.ts
+++ b/src/api/routes/gateway/index.ts
@@ -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: {
- body: "GatewayResponse",
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GatewayResponse",
+ },
},
+ }),
+ (req: Request, res: Response) => {
+ const { endpointPublic } = Config.get().gateway;
+ res.json({
+ url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
+ });
},
-};
-
-router.get("/", route(options), (req: Request, res: Response) => {
- const { endpointPublic } = Config.get().gateway;
- res.json({
- url: endpointPublic || process.env.GATEWAY || "ws://localhost:3001",
- });
-});
+);
export default router;
diff --git a/src/api/routes/gifs/search.ts b/src/api/routes/gifs/search.ts
index fb99374b..f125a463 100644
--- a/src/api/routes/gifs/search.ts
+++ b/src/api/routes/gifs/search.ts
@@ -16,34 +16,62 @@
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) => {
- // TODO: Custom providers
- const { q, media_format, locale } = req.query;
+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;
- const apiKey = getGifApiKey();
+ const apiKey = getGifApiKey();
- const agent = new ProxyAgent();
+ const agent = new ProxyAgent();
- const response = await fetch(
- `https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`,
- {
- agent,
- method: "get",
- headers: { "Content-Type": "application/json" },
- },
- );
+ const response = await fetch(
+ `https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`,
+ {
+ agent,
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ );
- const { results } = await response.json();
+ const { results } = await response.json();
- res.json(results.map(parseGifResult)).status(200);
-});
+ res.json(results.map(parseGifResult)).status(200);
+ },
+);
export default router;
diff --git a/src/api/routes/gifs/trending-gifs.ts b/src/api/routes/gifs/trending-gifs.ts
index 238a2abd..d6fa89ac 100644
--- a/src/api/routes/gifs/trending-gifs.ts
+++ b/src/api/routes/gifs/trending-gifs.ts
@@ -16,34 +16,57 @@
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) => {
- // TODO: Custom providers
- const { media_format, locale } = req.query;
+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;
- const apiKey = getGifApiKey();
+ const apiKey = getGifApiKey();
- const agent = new ProxyAgent();
+ const agent = new ProxyAgent();
- const response = await fetch(
- `https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`,
- {
- agent,
- method: "get",
- headers: { "Content-Type": "application/json" },
- },
- );
+ const response = await fetch(
+ `https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`,
+ {
+ agent,
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ );
- const { results } = await response.json();
+ const { results } = await response.json();
- res.json(results.map(parseGifResult)).status(200);
-});
+ res.json(results.map(parseGifResult)).status(200);
+ },
+);
export default router;
diff --git a/src/api/routes/gifs/trending.ts b/src/api/routes/gifs/trending.ts
index 5cccdb2d..e3d6e974 100644
--- a/src/api/routes/gifs/trending.ts
+++ b/src/api/routes/gifs/trending.ts
@@ -16,126 +16,76 @@
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) => {
- // TODO: Custom providers
- // TODO: return gifs as mp4
- // const { media_format, locale } = req.query;
- const { locale } = req.query;
-
- const apiKey = getGifApiKey();
-
- const agent = new ProxyAgent();
-
- const [responseSource, trendGifSource] = await Promise.all([
- fetch(
- `https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`,
- {
- agent,
- method: "get",
- headers: { "Content-Type": "application/json" },
+router.get(
+ "/",
+ route({
+ query: {
+ locale: {
+ type: "string",
+ description: "Locale",
},
- ),
- fetch(
- `https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`,
- {
- agent,
- method: "get",
- headers: { "Content-Type": "application/json" },
+ },
+ responses: {
+ 200: {
+ body: "TenorTrendingResponse",
},
- ),
- ]);
-
- const { tags } = (await responseSource.json()) as TENOR_CATEGORIES_RESULTS;
- const { results } = (await trendGifSource.json()) as TENOR_TRENDING_RESULTS;
-
- res.json({
- categories: tags.map((x) => ({
- name: x.searchterm,
- src: x.image,
- })),
- gifs: [parseGifResult(results[0])],
- }).status(200);
-});
+ },
+ }),
+ async (req: Request, res: Response) => {
+ // TODO: Custom providers
+ // TODO: return gifs as mp4
+ // const { media_format, locale } = req.query;
+ const { locale } = req.query;
+
+ const apiKey = getGifApiKey();
+
+ const agent = new ProxyAgent();
+
+ const [responseSource, trendGifSource] = await Promise.all([
+ fetch(
+ `https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`,
+ {
+ agent,
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ ),
+ fetch(
+ `https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`,
+ {
+ agent,
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ ),
+ ]);
+
+ const { tags } =
+ (await responseSource.json()) as TenorCategoriesResults;
+ const { results } =
+ (await trendGifSource.json()) as TenorTrendingResults;
+
+ res.json({
+ categories: tags.map((x) => ({
+ name: x.searchterm,
+ src: x.image,
+ })),
+ gifs: [parseGifResult(results[0])],
+ }).status(200);
+ },
+);
export default router;
diff --git a/src/api/routes/guild-recommendations.ts b/src/api/routes/guild-recommendations.ts
index 67f43c14..876780df 100644
--- a/src/api/routes/guild-recommendations.ts
+++ b/src/api/routes/guild-recommendations.ts
@@ -16,34 +16,44 @@
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) => {
- // const { limit, personalization_disabled } = req.query;
- const { limit } = req.query;
- const showAllGuilds = Config.get().guild.discovery.showAllGuilds;
+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;
- const genLoadId = (size: number) =>
- [...Array(size)]
- .map(() => Math.floor(Math.random() * 16).toString(16))
- .join("");
+ const genLoadId = (size: number) =>
+ [...Array(size)]
+ .map(() => Math.floor(Math.random() * 16).toString(16))
+ .join("");
- const guilds = showAllGuilds
- ? await Guild.find({ take: Math.abs(Number(limit || 24)) })
- : await Guild.find({
- where: { features: Like("%DISCOVERABLE%") },
- take: Math.abs(Number(limit || 24)),
- });
- res.send({
- recommended_guilds: guilds,
- load_id: `server_recs/${genLoadId(32)}`,
- }).status(200);
-});
+ const guilds = showAllGuilds
+ ? await Guild.find({ take: Math.abs(Number(limit || 24)) })
+ : await Guild.find({
+ where: { features: Like("%DISCOVERABLE%") },
+ take: Math.abs(Number(limit || 24)),
+ });
+ res.send({
+ recommended_guilds: guilds,
+ load_id: `server_recs/${genLoadId(32)}`,
+ }).status(200);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts
index 31aed6b9..0776ab62 100644
--- a/src/api/routes/guilds/#guild_id/bans.ts
+++ b/src/api/routes/guilds/#guild_id/bans.ts
@@ -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;
diff --git a/src/api/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts
index d74d9f84..1d5897a5 100644
--- a/src/api/routes/guilds/#guild_id/channels.ts
+++ b/src/api/routes/guilds/#guild_id/channels.ts
@@ -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) => {
- const { guild_id } = req.params;
- const channels = await Channel.find({ where: { guild_id } });
+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);
-});
+ 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;
diff --git a/src/api/routes/guilds/#guild_id/delete.ts b/src/api/routes/guilds/#guild_id/delete.ts
index ec72a4ae..dee52c81 100644
--- a/src/api/routes/guilds/#guild_id/delete.ts
+++ b/src/api/routes/guilds/#guild_id/delete.ts
@@ -16,37 +16,51 @@
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) => {
- const { guild_id } = req.params;
+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({
- where: { id: guild_id },
- select: ["owner_id"],
- });
- if (guild.owner_id !== req.user_id)
- throw new HTTPError("You are not the owner of this guild", 401);
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ select: ["owner_id"],
+ });
+ if (guild.owner_id !== req.user_id)
+ throw new HTTPError("You are not the owner of this guild", 401);
- await Promise.all([
- Guild.delete({ id: guild_id }), // this will also delete all guild related data
- emitEvent({
- event: "GUILD_DELETE",
- data: {
- id: guild_id,
- },
- guild_id: guild_id,
- } as GuildDeleteEvent),
- ]);
+ await Promise.all([
+ Guild.delete({ id: guild_id }), // this will also delete all guild related data
+ emitEvent({
+ event: "GUILD_DELETE",
+ data: {
+ id: guild_id,
+ },
+ guild_id: guild_id,
+ } as GuildDeleteEvent),
+ ]);
- return res.sendStatus(204);
-});
+ return res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/discovery-requirements.ts b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
index 5e15676a..741fa9b3 100644
--- a/src/api/routes/guilds/#guild_id/discovery-requirements.ts
+++ b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
@@ -16,40 +16,50 @@
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) => {
- const { guild_id } = req.params;
- // TODO:
- // Load from database
- // Admin control, but for now it allows anyone to be discoverable
-
- res.send({
- guild_id: guild_id,
- safe_environment: true,
- healthy: true,
- health_score_pending: false,
- size: true,
- nsfw_properties: {},
- protected: true,
- sufficient: true,
- sufficient_without_grace_period: true,
- valid_rules_channel: true,
- retention_healthy: true,
- engagement_healthy: true,
- age: true,
- minimum_age: 0,
- health_score: {
- avg_nonnew_participators: 0,
- avg_nonnew_communicators: 0,
- num_intentful_joiners: 0,
- perc_ret_w1_intentful: 0,
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildDiscoveryRequirementsResponse",
+ },
},
- minimum_size: 0,
- });
-});
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
+ // TODO:
+ // Load from database
+ // Admin control, but for now it allows anyone to be discoverable
+
+ res.send({
+ guild_id: guild_id,
+ safe_environment: true,
+ healthy: true,
+ health_score_pending: false,
+ size: true,
+ nsfw_properties: {},
+ protected: true,
+ sufficient: true,
+ sufficient_without_grace_period: true,
+ valid_rules_channel: true,
+ retention_healthy: true,
+ engagement_healthy: true,
+ age: true,
+ minimum_age: 0,
+ health_score: {
+ avg_nonnew_participators: 0,
+ avg_nonnew_communicators: 0,
+ num_intentful_joiners: 0,
+ perc_ret_w1_intentful: 0,
+ },
+ minimum_size: 0,
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/emojis.ts b/src/api/routes/guilds/#guild_id/emojis.ts
index c661202e..ef28f989 100644
--- a/src/api/routes/guilds/#guild_id/emojis.ts
+++ b/src/api/routes/guilds/#guild_id/emojis.ts
@@ -16,55 +16,95 @@
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) => {
- const { guild_id } = req.params;
+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);
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- const emojis = await Emoji.find({
- where: { guild_id: guild_id },
- relations: ["user"],
- });
+ const emojis = await Emoji.find({
+ where: { guild_id: guild_id },
+ relations: ["user"],
+ });
- return res.json(emojis);
-});
+ return res.json(emojis);
+ },
+);
-router.get("/:emoji_id", route({}), async (req: Request, res: Response) => {
- const { guild_id, emoji_id } = req.params;
+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);
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- const emoji = await Emoji.findOneOrFail({
- where: { guild_id: guild_id, id: emoji_id },
- relations: ["user"],
- });
+ const emoji = await Emoji.findOneOrFail({
+ where: { guild_id: guild_id, id: emoji_id },
+ relations: ["user"],
+ });
- return res.json(emoji);
-});
+ 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;
diff --git a/src/api/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts
index 672bc92e..afe60614 100644
--- a/src/api/routes/guilds/#guild_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/index.ts
@@ -16,46 +16,79 @@
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,
- emitEvent,
- getPermission,
- getRights,
Guild,
GuildUpdateEvent,
- handleFile,
- Member,
GuildUpdateSchema,
+ Member,
SpacebarApiErrors,
+ emitEvent,
+ getPermission,
+ getRights,
+ handleFile,
} 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) => {
- const { guild_id } = req.params;
-
- const [guild, member] = await Promise.all([
- Guild.findOneOrFail({ where: { id: guild_id } }),
- Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }),
- ]);
- if (!member)
- throw new HTTPError(
- "You are not a member of the guild you are trying to access",
- 401,
- );
-
- return res.send({
- ...guild,
- joined_at: member?.joined_at,
- });
-});
+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([
+ Guild.findOneOrFail({ where: { id: guild_id } }),
+ Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }),
+ ]);
+ if (!member)
+ throw new HTTPError(
+ "You are not a member of the guild you are trying to access",
+ 401,
+ );
+
+ return res.send({
+ ...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;
diff --git a/src/api/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts
index 9c446928..a0ffa3f4 100644
--- a/src/api/routes/guilds/#guild_id/invites.ts
+++ b/src/api/routes/guilds/#guild_id/invites.ts
@@ -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;
diff --git a/src/api/routes/guilds/#guild_id/member-verification.ts b/src/api/routes/guilds/#guild_id/member-verification.ts
index 242f3684..2c39093e 100644
--- a/src/api/routes/guilds/#guild_id/member-verification.ts
+++ b/src/api/routes/guilds/#guild_id/member-verification.ts
@@ -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) => {
- // TODO: member verification
+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,
- });
-});
+ res.status(404).json({
+ message: "Unknown Guild Member Verification Form",
+ code: 10068,
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
index a14691f2..5f1f6fa7 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
@@ -16,38 +16,70 @@
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,
- getPermission,
- getRights,
- Role,
- GuildMemberUpdateEvent,
emitEvent,
- Sticker,
Emoji,
+ getPermission,
+ getRights,
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) => {
- const { guild_id, member_id } = req.params;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+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);
- const member = await Member.findOneOrFail({
- where: { id: member_id, guild_id },
- });
+ const member = await Member.findOneOrFail({
+ where: { id: member_id, guild_id },
+ });
- return res.json(member);
-});
+ 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,54 +151,81 @@ router.patch(
},
);
-router.put("/", route({}), async (req: Request, res: Response) => {
- // TODO: Lurker mode
-
- const rights = await getRights(req.user_id);
-
- const { guild_id } = req.params;
- let { member_id } = req.params;
- if (member_id === "@me") {
- member_id = req.user_id;
- rights.hasThrow("JOIN_GUILDS");
- } else {
- // TODO: join others by controller
- }
-
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- });
-
- const emoji = await Emoji.find({
- where: { guild_id: guild_id },
- });
-
- const roles = await Role.find({
- where: { guild_id: guild_id },
- });
-
- const stickers = await Sticker.find({
- where: { guild_id: guild_id },
- });
-
- await Member.addToGuild(member_id, guild_id);
- res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
-});
-
-router.delete("/", route({}), 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);
- if (member_id === "@me" || member_id === req.user_id) {
- // TODO: unless force-joined
- rights.hasThrow("SELF_LEAVE_GROUPS");
- } else {
- rights.hasThrow("KICK_BAN_MEMBERS");
- permission.hasThrow("KICK_MEMBERS");
- }
-
- await Member.removeFromGuild(member_id, guild_id);
- res.sendStatus(204);
-});
+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);
+
+ const { guild_id } = req.params;
+ let { member_id } = req.params;
+ if (member_id === "@me") {
+ member_id = req.user_id;
+ rights.hasThrow("JOIN_GUILDS");
+ } else {
+ // TODO: join others by controller
+ }
+
+ const guild = await Guild.findOneOrFail({
+ where: { id: guild_id },
+ });
+
+ const emoji = await Emoji.find({
+ where: { guild_id: guild_id },
+ });
+
+ const roles = await Role.find({
+ where: { guild_id: guild_id },
+ });
+
+ const stickers = await Sticker.find({
+ where: { guild_id: guild_id },
+ });
+
+ await Member.addToGuild(member_id, guild_id);
+ res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers });
+ },
+);
+
+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);
+ if (member_id === "@me" || member_id === req.user_id) {
+ // TODO: unless force-joined
+ rights.hasThrow("SELF_LEAVE_GROUPS");
+ } else {
+ rights.hasThrow("KICK_BAN_MEMBERS");
+ permission.hasThrow("KICK_MEMBERS");
+ }
+
+ await Member.removeFromGuild(member_id, guild_id);
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
index 14e7467f..7b8e44d3 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
@@ -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";
diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
index 698df88f..46dd70bb 100644
--- a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
@@ -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;
diff --git a/src/api/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts
index f7a55cf1..9260308d 100644
--- a/src/api/routes/guilds/#guild_id/members/index.ts
+++ b/src/api/routes/guilds/#guild_id/members/index.ts
@@ -16,35 +16,58 @@
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) => {
- const { guild_id } = req.params;
- const limit = Number(req.query.limit) || 1;
- if (limit > 1000 || limit < 1)
- throw new HTTPError("Limit must be between 1 and 1000");
- const after = `${req.query.after}`;
- const query = after ? { id: MoreThan(after) } : {};
-
- await Member.IsInGuildOrFail(req.user_id, guild_id);
-
- const members = await Member.find({
- where: { guild_id, ...query },
- select: PublicMemberProjection,
- take: limit,
- order: { id: "ASC" },
- });
-
- return res.json(members);
-});
+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)
+ throw new HTTPError("Limit must be between 1 and 1000");
+ const after = `${req.query.after}`;
+ const query = after ? { id: MoreThan(after) } : {};
+
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
+
+ const members = await Member.find({
+ where: { guild_id, ...query },
+ select: PublicMemberProjection,
+ take: limit,
+ order: { id: "ASC" },
+ });
+
+ return res.json(members);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/messages/search.ts b/src/api/routes/guilds/#guild_id/messages/search.ts
index bc5f1b6e..637d1e43 100644
--- a/src/api/routes/guilds/#guild_id/messages/search.ts
+++ b/src/api/routes/guilds/#guild_id/messages/search.ts
@@ -18,140 +18,159 @@
/* 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) => {
- const {
- channel_id,
- content,
- // include_nsfw, // TODO
- offset,
- sort_order,
- // sort_by, // TODO: Handle 'relevance'
- limit,
- author_id,
- } = req.query;
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "GuildMessagesSearchResponse",
+ },
+ 403: {
+ body: "APIErrorResponse",
+ },
+ 422: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const {
+ channel_id,
+ content,
+ // include_nsfw, // TODO
+ offset,
+ sort_order,
+ // sort_by, // TODO: Handle 'relevance'
+ limit,
+ author_id,
+ } = req.query;
- const parsedLimit = Number(limit) || 50;
- if (parsedLimit < 1 || parsedLimit > 100)
- throw new HTTPError("limit must be between 1 and 100", 422);
+ const parsedLimit = Number(limit) || 50;
+ if (parsedLimit < 1 || parsedLimit > 100)
+ throw new HTTPError("limit must be between 1 and 100", 422);
- if (sort_order) {
- if (
- typeof sort_order != "string" ||
- ["desc", "asc"].indexOf(sort_order) == -1
- )
- throw FieldErrors({
- sort_order: {
- message: "Value must be one of ('desc', 'asc').",
- code: "BASE_TYPE_CHOICES",
- },
- }); // todo this is wrong
- }
+ if (sort_order) {
+ if (
+ typeof sort_order != "string" ||
+ ["desc", "asc"].indexOf(sort_order) == -1
+ )
+ throw FieldErrors({
+ sort_order: {
+ message: "Value must be one of ('desc', 'asc').",
+ code: "BASE_TYPE_CHOICES",
+ },
+ }); // todo this is wrong
+ }
- const permissions = await getPermission(
- req.user_id,
- req.params.guild_id,
- channel_id as string | undefined,
- );
- permissions.hasThrow("VIEW_CHANNEL");
- if (!permissions.has("READ_MESSAGE_HISTORY"))
- return res.json({ messages: [], total_results: 0 });
+ const permissions = await getPermission(
+ req.user_id,
+ req.params.guild_id,
+ channel_id as string | undefined,
+ );
+ permissions.hasThrow("VIEW_CHANNEL");
+ if (!permissions.has("READ_MESSAGE_HISTORY"))
+ return res.json({ messages: [], total_results: 0 });
- const query: FindManyOptions<Message> = {
- order: {
- timestamp: sort_order
- ? (sort_order.toUpperCase() as "ASC" | "DESC")
- : "DESC",
- },
- take: parsedLimit || 0,
- where: {
- guild: {
- id: req.params.guild_id,
+ const query: FindManyOptions<Message> = {
+ order: {
+ timestamp: sort_order
+ ? (sort_order.toUpperCase() as "ASC" | "DESC")
+ : "DESC",
},
- },
- relations: [
- "author",
- "webhook",
- "application",
- "mentions",
- "mention_roles",
- "mention_channels",
- "sticker_items",
- "attachments",
- ],
- skip: offset ? Number(offset) : 0,
- };
- //@ts-ignore
- if (channel_id) query.where.channel = { id: channel_id };
- else {
- // get all channel IDs that this user can access
- const channels = await Channel.find({
- where: { guild_id: req.params.guild_id },
- select: ["id"],
- });
- const ids = [];
+ take: parsedLimit || 0,
+ where: {
+ guild: {
+ id: req.params.guild_id,
+ },
+ },
+ relations: [
+ "author",
+ "webhook",
+ "application",
+ "mentions",
+ "mention_roles",
+ "mention_channels",
+ "sticker_items",
+ "attachments",
+ ],
+ skip: offset ? Number(offset) : 0,
+ };
+ //@ts-ignore
+ if (channel_id) query.where.channel = { id: channel_id };
+ else {
+ // get all channel IDs that this user can access
+ const channels = await Channel.find({
+ where: { guild_id: req.params.guild_id },
+ select: ["id"],
+ });
+ const ids = [];
- for (const channel of channels) {
- const perm = await getPermission(
- req.user_id,
- req.params.guild_id,
- channel.id,
- );
- if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY"))
- continue;
- ids.push(channel.id);
- }
+ for (const channel of channels) {
+ const perm = await getPermission(
+ req.user_id,
+ req.params.guild_id,
+ channel.id,
+ );
+ if (
+ !perm.has("VIEW_CHANNEL") ||
+ !perm.has("READ_MESSAGE_HISTORY")
+ )
+ continue;
+ ids.push(channel.id);
+ }
+ //@ts-ignore
+ query.where.channel = { id: In(ids) };
+ }
+ //@ts-ignore
+ if (author_id) query.where.author = { id: author_id };
//@ts-ignore
- query.where.channel = { id: In(ids) };
- }
- //@ts-ignore
- if (author_id) query.where.author = { id: author_id };
- //@ts-ignore
- if (content) query.where.content = Like(`%${content}%`);
+ if (content) query.where.content = Like(`%${content}%`);
- const messages: Message[] = await Message.find(query);
+ const messages: Message[] = await Message.find(query);
- const messagesDto = messages.map((x) => [
- {
- id: x.id,
- type: x.type,
- content: x.content,
- channel_id: x.channel_id,
- author: {
- id: x.author?.id,
- username: x.author?.username,
- avatar: x.author?.avatar,
- avatar_decoration: null,
- discriminator: x.author?.discriminator,
- public_flags: x.author?.public_flags,
+ const messagesDto = messages.map((x) => [
+ {
+ id: x.id,
+ type: x.type,
+ content: x.content,
+ channel_id: x.channel_id,
+ author: {
+ id: x.author?.id,
+ username: x.author?.username,
+ avatar: x.author?.avatar,
+ avatar_decoration: null,
+ discriminator: x.author?.discriminator,
+ public_flags: x.author?.public_flags,
+ },
+ attachments: x.attachments,
+ embeds: x.embeds,
+ mentions: x.mentions,
+ mention_roles: x.mention_roles,
+ pinned: x.pinned,
+ mention_everyone: x.mention_everyone,
+ tts: x.tts,
+ timestamp: x.timestamp,
+ edited_timestamp: x.edited_timestamp,
+ flags: x.flags,
+ components: x.components,
+ hit: true,
},
- attachments: x.attachments,
- embeds: x.embeds,
- mentions: x.mentions,
- mention_roles: x.mention_roles,
- pinned: x.pinned,
- mention_everyone: x.mention_everyone,
- tts: x.tts,
- timestamp: x.timestamp,
- edited_timestamp: x.edited_timestamp,
- flags: x.flags,
- components: x.components,
- hit: true,
- },
- ]);
+ ]);
- return res.json({
- messages: messagesDto,
- total_results: messages.length,
- });
-});
+ return res.json({
+ messages: messagesDto,
+ total_results: messages.length,
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/profile/index.ts b/src/api/routes/guilds/#guild_id/profile/index.ts
index 8ec22ea4..60526259 100644
--- a/src/api/routes/guilds/#guild_id/profile/index.ts
+++ b/src/api/routes/guilds/#guild_id/profile/index.ts
@@ -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 =
diff --git a/src/api/routes/guilds/#guild_id/prune.ts b/src/api/routes/guilds/#guild_id/prune.ts
index dbed546b..2c77340d 100644
--- a/src/api/routes/guilds/#guild_id/prune.ts
+++ b/src/api/routes/guilds/#guild_id/prune.ts
@@ -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,25 +80,46 @@ export const inactiveMembers = async (
return members;
};
-router.get("/", route({}), async (req: Request, res: Response) => {
- const days = parseInt(req.query.days as string);
+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;
- if (typeof roles === "string") roles = [roles]; //express will return array otherwise
+ let roles = req.query.include_roles;
+ if (typeof roles === "string") roles = [roles]; //express will return array otherwise
- const members = await inactiveMembers(
- req.params.guild_id,
- req.user_id,
- days,
- roles as string[],
- );
+ const members = await inactiveMembers(
+ req.params.guild_id,
+ req.user_id,
+ days,
+ roles as string[],
+ );
- res.send({ pruned: members.length });
-});
+ 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);
diff --git a/src/api/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts
index de1e8769..b0ae0602 100644
--- a/src/api/routes/guilds/#guild_id/regions.ts
+++ b/src/api/routes/guilds/#guild_id/regions.ts
@@ -16,22 +16,35 @@
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) => {
- 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
- return res.json(
- await getVoiceRegions(
- getIpAdress(req),
- guild.features.includes("VIP_REGIONS"),
- ),
- );
-});
+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
+ return res.json(
+ await getVoiceRegions(
+ getIpAdress(req),
+ guild.features.includes("VIP_REGIONS"),
+ ),
+ );
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
index de3fc35b..ea1a782a 100644
--- a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
@@ -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) => {
- 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 } });
- return res.json(role);
-});
+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 },
+ });
+ 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;
diff --git a/src/api/routes/guilds/#guild_id/roles/index.ts b/src/api/routes/guilds/#guild_id/roles/index.ts
index f93e9385..e2c34e7f 100644
--- a/src/api/routes/guilds/#guild_id/roles/index.ts
+++ b/src/api/routes/guilds/#guild_id/roles/index.ts
@@ -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 }),
diff --git a/src/api/routes/guilds/#guild_id/stickers.ts b/src/api/routes/guilds/#guild_id/stickers.ts
index 84a23670..88f9a40e 100644
--- a/src/api/routes/guilds/#guild_id/stickers.ts
+++ b/src/api/routes/guilds/#guild_id/stickers.ts
@@ -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) => {
- const { guild_id } = req.params;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+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 } }));
-});
+ 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) => {
- const { guild_id, sticker_id } = req.params;
- await Member.IsInGuildOrFail(req.user_id, guild_id);
+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 } }),
- );
-});
+ res.json(
+ 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;
diff --git a/src/api/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts
index 3bd28e05..85ae0ac9 100644
--- a/src/api/routes/guilds/#guild_id/templates.ts
+++ b/src/api/routes/guilds/#guild_id/templates.ts
@@ -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,19 +40,46 @@ const TemplateGuildProjection: (keyof Guild)[] = [
"icon",
];
-router.get("/", route({}), async (req: Request, res: Response) => {
- const { guild_id } = req.params;
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APITemplateArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { guild_id } = req.params;
- const templates = await Template.find({
- where: { source_guild_id: guild_id },
- });
+ const templates = await Template.find({
+ where: { source_guild_id: guild_id },
+ });
- return res.json(templates);
-});
+ 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;
diff --git a/src/api/routes/guilds/#guild_id/vanity-url.ts b/src/api/routes/guilds/#guild_id/vanity-url.ts
index c85c943f..d271c976 100644
--- a/src/api/routes/guilds/#guild_id/vanity-url.ts
+++ b/src/api/routes/guilds/#guild_id/vanity-url.ts
@@ -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;
diff --git a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
index 791ac102..60c69075 100644
--- a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
+++ b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
@@ -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;
diff --git a/src/api/routes/guilds/#guild_id/welcome-screen.ts b/src/api/routes/guilds/#guild_id/welcome-screen.ts
index 696e20db..2a739683 100644
--- a/src/api/routes/guilds/#guild_id/welcome-screen.ts
+++ b/src/api/routes/guilds/#guild_id/welcome-screen.ts
@@ -16,27 +16,49 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import { route } from "@spacebar/api";
+import { Guild, GuildUpdateWelcomeScreenSchema, Member } from "@spacebar/util";
import { Request, Response, Router } from "express";
-import { Guild, Member, GuildUpdateWelcomeScreenSchema } from "@spacebar/util";
import { HTTPError } from "lambert-server";
-import { route } from "@spacebar/api";
const router: Router = Router();
-router.get("/", route({}), async (req: Request, res: Response) => {
- const guild_id = req.params.guild_id;
+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);
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ await Member.IsInGuildOrFail(req.user_id, guild_id);
- res.json(guild.welcome_screen);
-});
+ 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;
diff --git a/src/api/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts
index 1799f0be..69b5d48c 100644
--- a/src/api/routes/guilds/#guild_id/widget.json.ts
+++ b/src/api/routes/guilds/#guild_id/widget.json.ts
@@ -16,10 +16,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import { random, route } from "@spacebar/api";
+import { Channel, Guild, Invite, Member, Permissions } from "@spacebar/util";
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";
const router: Router = Router();
@@ -32,77 +32,90 @@ 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) => {
- const { guild_id } = req.params;
+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 } });
- if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
+ if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404);
- // Fetch existing widget invite for widget channel
- let invite = await Invite.findOne({
- where: { channel_id: guild.widget_channel_id },
- });
+ // Fetch existing widget invite for widget channel
+ let invite = await Invite.findOne({
+ where: { channel_id: guild.widget_channel_id },
+ });
- if (guild.widget_channel_id && !invite) {
- // Create invite for channel if none exists
- // TODO: Refactor invite create code to a shared function
- const max_age = 86400; // 24 hours
- const expires_at = new Date(max_age * 1000 + Date.now());
+ if (guild.widget_channel_id && !invite) {
+ // Create invite for channel if none exists
+ // TODO: Refactor invite create code to a shared function
+ const max_age = 86400; // 24 hours
+ const expires_at = new Date(max_age * 1000 + Date.now());
- invite = await Invite.create({
- code: random(),
- temporary: false,
- uses: 0,
- max_uses: 0,
- max_age: max_age,
- expires_at,
- created_at: new Date(),
- guild_id,
- channel_id: guild.widget_channel_id,
- }).save();
- }
+ invite = await Invite.create({
+ code: random(),
+ temporary: false,
+ uses: 0,
+ max_uses: 0,
+ max_age: max_age,
+ expires_at,
+ created_at: new Date(),
+ guild_id,
+ channel_id: guild.widget_channel_id,
+ }).save();
+ }
- // Fetch voice channels, and the @everyone permissions object
- const channels: { id: string; name: string; position: number }[] = [];
+ // Fetch voice channels, and the @everyone permissions object
+ const channels: { id: string; name: string; position: number }[] = [];
- (
- await Channel.find({
- where: { guild_id: guild_id, type: 2 },
- order: { position: "ASC" },
- })
- ).filter((doc) => {
- // Only return channels where @everyone has the CONNECT permission
- if (
- doc.permission_overwrites === undefined ||
- Permissions.channelPermission(
- doc.permission_overwrites,
- Permissions.FLAGS.CONNECT,
- ) === Permissions.FLAGS.CONNECT
- ) {
- channels.push({
- id: doc.id,
- name: doc.name ?? "Unknown channel",
- position: doc.position ?? 0,
- });
- }
- });
+ (
+ await Channel.find({
+ where: { guild_id: guild_id, type: 2 },
+ order: { position: "ASC" },
+ })
+ ).filter((doc) => {
+ // Only return channels where @everyone has the CONNECT permission
+ if (
+ doc.permission_overwrites === undefined ||
+ Permissions.channelPermission(
+ doc.permission_overwrites,
+ Permissions.FLAGS.CONNECT,
+ ) === Permissions.FLAGS.CONNECT
+ ) {
+ channels.push({
+ id: doc.id,
+ name: doc.name ?? "Unknown channel",
+ position: doc.position ?? 0,
+ });
+ }
+ });
- // Fetch members
- // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
- const members = await Member.find({ where: { guild_id: guild_id } });
+ // Fetch members
+ // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file)
+ const members = await Member.find({ where: { guild_id: guild_id } });
- // Construct object to respond with
- const data = {
- id: guild_id,
- name: guild.name,
- instant_invite: invite?.code,
- channels: channels,
- members: members,
- presence_count: guild.presence_count,
- };
+ // Construct object to respond with
+ const data = {
+ id: guild_id,
+ name: guild.name,
+ instant_invite: invite?.code,
+ channels: channels,
+ members: members,
+ presence_count: guild.presence_count,
+ };
- res.set("Cache-Control", "public, max-age=300");
- return res.json(data);
-});
+ res.set("Cache-Control", "public, max-age=300");
+ return res.json(data);
+ },
+);
export default router;
diff --git a/src/api/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts
index 4e975603..c9ba8afc 100644
--- a/src/api/routes/guilds/#guild_id/widget.png.ts
+++ b/src/api/routes/guilds/#guild_id/widget.png.ts
@@ -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,130 +31,178 @@ 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) => {
- const { guild_id } = req.params;
-
- const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404);
-
- // Fetch guild information
- const icon = guild.icon;
- const name = guild.name;
- const presence = guild.presence_count + " ONLINE";
-
- // Fetch parameter
- const style = req.query.style?.toString() || "shield";
- if (
- !["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)
- ) {
- throw new HTTPError(
- "Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
- 400,
- );
- }
-
- // Setup canvas
- const { createCanvas } = require("canvas");
- const { loadImage } = require("canvas");
- const sizeOf = require("image-size");
-
- // TODO: Widget style templates need Spacebar branding
- const source = path.join(
- __dirname,
- "..",
- "..",
- "..",
- "..",
- "..",
- "assets",
- "widget",
- `${style}.png`,
- );
- if (!fs.existsSync(source)) {
- throw new HTTPError("Widget template does not exist.", 400);
- }
-
- // Create base template image for parameter
- const { width, height } = await sizeOf(source);
- const canvas = createCanvas(width, height);
- const ctx = canvas.getContext("2d");
- const template = await loadImage(source);
- ctx.drawImage(template, 0, 0);
-
- // Add the guild specific information to the template asset image
- switch (style) {
- case "shield":
- ctx.textAlign = "center";
- await drawText(
- ctx,
- 73,
- 13,
- "#FFFFFF",
- "thin 10px Verdana",
- presence,
- );
- 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,
- 66,
- "#C9D2F0FF",
- "thin 11px Verdana",
- presence,
- );
- 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,
- 49,
- "#C9D2F0FF",
- "thin 11px Verdana",
- presence,
- );
- 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,
- 58,
- "#C9D2F0FF",
- "thin 11px Verdana",
- presence,
- );
- 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,
- 171,
- "#C9D2F0FF",
- "thin 12px Verdana",
- presence,
- );
- break;
- default:
+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 } });
+ if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404);
+
+ // Fetch guild information
+ const icon = guild.icon;
+ const name = guild.name;
+ const presence = guild.presence_count + " ONLINE";
+
+ // Fetch parameter
+ const style = req.query.style?.toString() || "shield";
+ if (
+ !["shield", "banner1", "banner2", "banner3", "banner4"].includes(
+ style,
+ )
+ ) {
throw new HTTPError(
"Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
400,
);
- }
-
- // Return final image
- const buffer = canvas.toBuffer("image/png");
- res.set("Content-Type", "image/png");
- res.set("Cache-Control", "public, max-age=3600");
- return res.send(buffer);
-});
+ }
+
+ // Setup canvas
+ const { createCanvas } = require("canvas");
+ const { loadImage } = require("canvas");
+ const sizeOf = require("image-size");
+
+ // TODO: Widget style templates need Spacebar branding
+ const source = path.join(
+ __dirname,
+ "..",
+ "..",
+ "..",
+ "..",
+ "..",
+ "assets",
+ "widget",
+ `${style}.png`,
+ );
+ if (!fs.existsSync(source)) {
+ throw new HTTPError("Widget template does not exist.", 400);
+ }
+
+ // Create base template image for parameter
+ const { width, height } = await sizeOf(source);
+ const canvas = createCanvas(width, height);
+ const ctx = canvas.getContext("2d");
+ const template = await loadImage(source);
+ ctx.drawImage(template, 0, 0);
+
+ // Add the guild specific information to the template asset image
+ switch (style) {
+ case "shield":
+ ctx.textAlign = "center";
+ await drawText(
+ ctx,
+ 73,
+ 13,
+ "#FFFFFF",
+ "thin 10px Verdana",
+ presence,
+ );
+ 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,
+ 66,
+ "#C9D2F0FF",
+ "thin 11px Verdana",
+ presence,
+ );
+ 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,
+ 49,
+ "#C9D2F0FF",
+ "thin 11px Verdana",
+ presence,
+ );
+ 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,
+ 58,
+ "#C9D2F0FF",
+ "thin 11px Verdana",
+ presence,
+ );
+ 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,
+ 171,
+ "#C9D2F0FF",
+ "thin 12px Verdana",
+ presence,
+ );
+ break;
+ default:
+ throw new HTTPError(
+ "Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').",
+ 400,
+ );
+ }
+
+ // Return final image
+ const buffer = canvas.toBuffer("image/png");
+ res.set("Content-Type", "image/png");
+ res.set("Cache-Control", "public, max-age=3600");
+ return res.send(buffer);
+ },
+);
async function drawIcon(
canvas: any,
diff --git a/src/api/routes/guilds/#guild_id/widget.ts b/src/api/routes/guilds/#guild_id/widget.ts
index 77af25dc..cae0d6be 100644
--- a/src/api/routes/guilds/#guild_id/widget.ts
+++ b/src/api/routes/guilds/#guild_id/widget.ts
@@ -16,28 +16,55 @@
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) => {
- const { guild_id } = req.params;
+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 } });
+ const guild = await Guild.findOneOrFail({ where: { id: guild_id } });
- return res.json({
- enabled: guild.widget_enabled || false,
- channel_id: guild.widget_channel_id || null,
- });
-});
+ return res.json({
+ 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;
diff --git a/src/api/routes/guilds/index.ts b/src/api/routes/guilds/index.ts
index c793d185..26173ed5 100644
--- a/src/api/routes/guilds/index.ts
+++ b/src/api/routes/guilds/index.ts
@@ -16,16 +16,16 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { Router, Request, Response } from "express";
+import { route } from "@spacebar/api";
import {
- Guild,
Config,
- getRights,
- Member,
DiscordApiErrors,
+ Guild,
GuildCreateSchema,
+ Member,
+ getRights,
} from "@spacebar/util";
-import { route } from "@spacebar/api";
+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;
diff --git a/src/api/routes/guilds/templates/index.ts b/src/api/routes/guilds/templates/index.ts
index bfbb7d3b..8f718a21 100644
--- a/src/api/routes/guilds/templates/index.ts
+++ b/src/api/routes/guilds/templates/index.ts
@@ -16,72 +16,91 @@
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) => {
- const { allowDiscordTemplates, allowRaws, enabled } =
- Config.get().templates;
- if (!enabled)
- res.json({
- code: 403,
- message: "Template creation & usage is disabled on this instance.",
- }).sendStatus(403);
-
- const { code } = req.params;
+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.",
+ }).sendStatus(403);
- if (code.startsWith("discord:")) {
- if (!allowDiscordTemplates)
- return res
- .json({
- code: 403,
- message:
- "Discord templates cannot be used on this instance.",
- })
- .sendStatus(403);
- const discordTemplateID = code.split("discord:", 2)[1];
+ const { code } = req.params;
- const discordTemplateData = await fetch(
- `https://discord.com/api/v9/guilds/templates/${discordTemplateID}`,
- {
- method: "get",
- headers: { "Content-Type": "application/json" },
- },
- );
- return res.json(await discordTemplateData.json());
- }
+ if (code.startsWith("discord:")) {
+ if (!allowDiscordTemplates)
+ return res
+ .json({
+ code: 403,
+ message:
+ "Discord templates cannot be used on this instance.",
+ })
+ .sendStatus(403);
+ const discordTemplateID = code.split("discord:", 2)[1];
+
+ const discordTemplateData = await fetch(
+ `https://discord.com/api/v9/guilds/templates/${discordTemplateID}`,
+ {
+ method: "get",
+ headers: { "Content-Type": "application/json" },
+ },
+ );
+ return res.json(await discordTemplateData.json());
+ }
- if (code.startsWith("external:")) {
- if (!allowRaws)
- return res
- .json({
- code: 403,
- message: "Importing raws is disabled on this instance.",
- })
- .sendStatus(403);
+ if (code.startsWith("external:")) {
+ if (!allowRaws)
+ return res
+ .json({
+ code: 403,
+ message: "Importing raws is disabled on this instance.",
+ })
+ .sendStatus(403);
- return res.json(code.split("external:", 2)[1]);
- }
+ return res.json(code.split("external:", 2)[1]);
+ }
- const template = await Template.findOneOrFail({ where: { code: code } });
- res.json(template);
-});
+ 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,
diff --git a/src/api/routes/invites/index.ts b/src/api/routes/invites/index.ts
index 6680e375..5a9cd942 100644
--- a/src/api/routes/invites/index.ts
+++ b/src/api/routes/invites/index.ts
@@ -16,35 +16,64 @@
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) => {
- const { code } = req.params;
+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({
- where: { code },
- relations: PublicInviteRelation,
- });
+ const invite = await Invite.findOneOrFail({
+ where: { code },
+ relations: PublicInviteRelation,
+ });
- res.status(200).send(invite);
-});
+ 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,33 +104,56 @@ 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) => {
- 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);
+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;
- if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS"))
- throw new HTTPError(
- "You missing the MANAGE_GUILD or MANAGE_CHANNELS permission",
- 401,
+ const permission = await getPermission(
+ req.user_id,
+ guild_id,
+ channel_id,
);
- await Promise.all([
- Invite.delete({ code }),
- emitEvent({
- event: "INVITE_DELETE",
- guild_id: guild_id,
- data: {
- channel_id: channel_id,
+ if (
+ !permission.has("MANAGE_GUILD") &&
+ !permission.has("MANAGE_CHANNELS")
+ )
+ throw new HTTPError(
+ "You missing the MANAGE_GUILD or MANAGE_CHANNELS permission",
+ 401,
+ );
+
+ await Promise.all([
+ Invite.delete({ code }),
+ emitEvent({
+ event: "INVITE_DELETE",
guild_id: guild_id,
- code: code,
- },
- } as InviteDeleteEvent),
- ]);
+ data: {
+ channel_id: channel_id,
+ guild_id: guild_id,
+ code: code,
+ },
+ } as InviteDeleteEvent),
+ ]);
- res.json({ invite: invite });
-});
+ res.json({ invite: invite });
+ },
+);
export default router;
diff --git a/src/api/routes/oauth2/authorize.ts b/src/api/routes/oauth2/authorize.ts
index c041f671..7ae6fa84 100644
--- a/src/api/routes/oauth2/authorize.ts
+++ b/src/api/routes/oauth2/authorize.ts
@@ -16,126 +16,168 @@
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) => {
- // const { client_id, scope, response_type, redirect_url } = req.query;
- const { client_id } = req.query;
-
- const app = await Application.findOne({
- where: {
- id: client_id as string,
+router.get(
+ "/",
+ route({
+ responses: {
+ // TODO: I really didn't feel like typing all of it out
+ 200: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
},
- relations: ["bot"],
- });
+ }),
+ async (req: Request, res: Response) => {
+ // const { client_id, scope, response_type, redirect_url } = req.query;
+ const { client_id } = req.query;
- // TODO: use DiscordApiErrors
- // findOneOrFail throws code 404
- if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION;
- if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT;
+ const app = await Application.findOne({
+ where: {
+ id: client_id as string,
+ },
+ relations: ["bot"],
+ });
- const bot = app.bot;
- delete app.bot;
+ // TODO: use DiscordApiErrors
+ // findOneOrFail throws code 404
+ if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION;
+ if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT;
- const user = await User.findOneOrFail({
- where: {
- id: req.user_id,
- bot: false,
- },
- select: ["id", "username", "avatar", "discriminator", "public_flags"],
- });
+ const bot = app.bot;
+ delete app.bot;
- const guilds = await Member.find({
- where: {
- user: {
+ const user = await User.findOneOrFail({
+ where: {
id: req.user_id,
+ bot: false,
},
- },
- relations: ["guild", "roles"],
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
- //@ts-ignore
- // prettier-ignore
- select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id"],
- });
-
- const guildsWithPermissions = guilds.map((x) => {
- const perms =
- x.guild.owner_id === user.id
- ? new Permissions(Permissions.FLAGS.ADMINISTRATOR)
- : Permissions.finalPermission({
- user: {
- id: user.id,
- roles: x.roles?.map((x) => x.id) || [],
- },
- guild: {
- roles: x?.roles || [],
- },
- });
-
- return {
- id: x.guild.id,
- name: x.guild.name,
- icon: x.guild.icon,
- mfa_level: x.guild.mfa_level,
- permissions: perms.bitfield.toString(),
- };
- });
-
- return res.json({
- guilds: guildsWithPermissions,
- user: {
- id: user.id,
- username: user.username,
- avatar: user.avatar,
- avatar_decoration: null, // TODO
- discriminator: user.discriminator,
- public_flags: user.public_flags,
- },
- application: {
- id: app.id,
- name: app.name,
- icon: app.icon,
- description: app.description,
- summary: app.summary,
- type: app.type,
- hook: app.hook,
- guild_id: null, // TODO support guilds
- bot_public: app.bot_public,
- bot_require_code_grant: app.bot_require_code_grant,
- verify_key: app.verify_key,
- flags: app.flags,
- },
- bot: {
- id: bot.id,
- username: bot.username,
- avatar: bot.avatar,
- avatar_decoration: null, // TODO
- discriminator: bot.discriminator,
- public_flags: bot.public_flags,
- bot: true,
- approximated_guild_count: 0, // TODO
- },
- authorized: false,
- });
-});
+ select: [
+ "id",
+ "username",
+ "avatar",
+ "discriminator",
+ "public_flags",
+ ],
+ });
+
+ const guilds = await Member.find({
+ where: {
+ user: {
+ id: req.user_id,
+ },
+ },
+ relations: ["guild", "roles"],
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ //@ts-ignore
+ // prettier-ignore
+ select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id"],
+ });
+
+ const guildsWithPermissions = guilds.map((x) => {
+ const perms =
+ x.guild.owner_id === user.id
+ ? new Permissions(Permissions.FLAGS.ADMINISTRATOR)
+ : Permissions.finalPermission({
+ user: {
+ id: user.id,
+ roles: x.roles?.map((x) => x.id) || [],
+ },
+ guild: {
+ roles: x?.roles || [],
+ },
+ });
+
+ return {
+ id: x.guild.id,
+ name: x.guild.name,
+ icon: x.guild.icon,
+ mfa_level: x.guild.mfa_level,
+ permissions: perms.bitfield.toString(),
+ };
+ });
+
+ return res.json({
+ guilds: guildsWithPermissions,
+ user: {
+ id: user.id,
+ username: user.username,
+ avatar: user.avatar,
+ avatar_decoration: null, // TODO
+ discriminator: user.discriminator,
+ public_flags: user.public_flags,
+ },
+ application: {
+ id: app.id,
+ name: app.name,
+ icon: app.icon,
+ description: app.description,
+ summary: app.summary,
+ type: app.type,
+ hook: app.hook,
+ guild_id: null, // TODO support guilds
+ bot_public: app.bot_public,
+ bot_require_code_grant: app.bot_require_code_grant,
+ verify_key: app.verify_key,
+ flags: app.flags,
+ },
+ bot: {
+ id: bot.id,
+ username: bot.username,
+ avatar: bot.avatar,
+ avatar_decoration: null, // TODO
+ discriminator: bot.discriminator,
+ public_flags: bot.public_flags,
+ bot: true,
+ approximated_guild_count: 0, // TODO
+ },
+ 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;
diff --git a/src/api/routes/ping.ts b/src/api/routes/ping.ts
index 0fb6d9d0..73330239 100644
--- a/src/api/routes/ping.ts
+++ b/src/api/routes/ping.ts
@@ -16,29 +16,39 @@
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) => {
- const { general } = Config.get();
- res.send({
- ping: "pong!",
- instance: {
- id: general.instanceId,
- name: general.instanceName,
- description: general.instanceDescription,
- image: general.image,
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "InstancePingResponse",
+ },
+ },
+ }),
+ (req: Request, res: Response) => {
+ const { general } = Config.get();
+ res.send({
+ ping: "pong!",
+ instance: {
+ id: general.instanceId,
+ name: general.instanceName,
+ description: general.instanceDescription,
+ image: general.image,
- correspondenceEmail: general.correspondenceEmail,
- correspondenceUserID: general.correspondenceUserID,
+ correspondenceEmail: general.correspondenceEmail,
+ correspondenceUserID: general.correspondenceUserID,
- frontPage: general.frontPage,
- tosPage: general.tosPage,
- },
- });
-});
+ frontPage: general.frontPage,
+ tosPage: general.tosPage,
+ },
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/policies/instance/domains.ts b/src/api/routes/policies/instance/domains.ts
index 696a8510..afeb0e85 100644
--- a/src/api/routes/policies/instance/domains.ts
+++ b/src/api/routes/policies/instance/domains.ts
@@ -16,25 +16,38 @@
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) => {
- const { cdn, gateway, api } = Config.get();
+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",
- gateway:
- gateway.endpointPublic ||
- process.env.GATEWAY ||
- "ws://localhost:3001",
- defaultApiVersion: api.defaultVersion ?? 9,
- apiEndpoint: api.endpointPublic ?? "http://localhost:3001/api/",
- };
+ const IdentityForm = {
+ cdn:
+ cdn.endpointPublic ||
+ process.env.CDN ||
+ "http://localhost:3001",
+ gateway:
+ gateway.endpointPublic ||
+ process.env.GATEWAY ||
+ "ws://localhost:3001",
+ defaultApiVersion: api.defaultVersion ?? 9,
+ apiEndpoint: api.endpointPublic ?? "http://localhost:3001/api/",
+ };
- res.json(IdentityForm);
-});
+ res.json(IdentityForm);
+ },
+);
export default router;
diff --git a/src/api/routes/policies/instance/index.ts b/src/api/routes/policies/instance/index.ts
index 68ce3b42..6e269a5c 100644
--- a/src/api/routes/policies/instance/index.ts
+++ b/src/api/routes/policies/instance/index.ts
@@ -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) => {
- const { general } = Config.get();
- res.json(general);
-});
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIGeneralConfiguration",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { general } = Config.get();
+ res.json(general);
+ },
+);
export default router;
diff --git a/src/api/routes/policies/instance/limits.ts b/src/api/routes/policies/instance/limits.ts
index a6f13170..9852459d 100644
--- a/src/api/routes/policies/instance/limits.ts
+++ b/src/api/routes/policies/instance/limits.ts
@@ -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) => {
- const { limits } = Config.get();
- res.json(limits);
-});
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APILimitsConfiguration",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { limits } = Config.get();
+ res.json(limits);
+ },
+);
export default router;
diff --git a/src/api/routes/policies/stats.ts b/src/api/routes/policies/stats.ts
index 3939e1e8..b2cd3d5a 100644
--- a/src/api/routes/policies/stats.ts
+++ b/src/api/routes/policies/stats.ts
@@ -28,20 +28,33 @@ import {
import { Request, Response, Router } from "express";
const router = Router();
-router.get("/", route({}), async (req: Request, res: Response) => {
- if (!Config.get().security.statsWorldReadable) {
- const rights = await getRights(req.user_id);
- rights.hasThrow("VIEW_SERVER_STATS");
- }
-
- res.json({
- counts: {
- user: await User.count(),
- guild: await Guild.count(),
- message: await Message.count(),
- members: await Member.count(),
+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");
+ }
+
+ res.json({
+ counts: {
+ user: await User.count(),
+ guild: await Guild.count(),
+ message: await Message.count(),
+ members: await Member.count(),
+ },
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/read-states/ack-bulk.ts b/src/api/routes/read-states/ack-bulk.ts
index 2c51893b..3ee25d1a 100644
--- a/src/api/routes/read-states/ack-bulk.ts
+++ b/src/api/routes/read-states/ack-bulk.ts
@@ -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;
diff --git a/src/api/routes/science.ts b/src/api/routes/science.ts
index 099da18b..d5cdc173 100644
--- a/src/api/routes/science.ts
+++ b/src/api/routes/science.ts
@@ -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) => {
- // TODO:
- res.sendStatus(204);
-});
+router.post(
+ "/",
+ route({
+ responses: {
+ 204: {},
+ },
+ }),
+ (req: Request, res: Response) => {
+ // TODO:
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/sticker-packs/index.ts b/src/api/routes/sticker-packs/index.ts
index 234e03c6..569d1104 100644
--- a/src/api/routes/sticker-packs/index.ts
+++ b/src/api/routes/sticker-packs/index.ts
@@ -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 });
-});
+ res.json({ sticker_packs });
+ },
+);
export default router;
diff --git a/src/api/routes/stickers/#sticker_id/index.ts b/src/api/routes/stickers/#sticker_id/index.ts
index 360149b5..2ea81bf9 100644
--- a/src/api/routes/stickers/#sticker_id/index.ts
+++ b/src/api/routes/stickers/#sticker_id/index.ts
@@ -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) => {
- const { sticker_id } = req.params;
+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 } }));
-});
+ res.json(await Sticker.find({ where: { id: sticker_id } }));
+ },
+);
export default router;
diff --git a/src/api/routes/stop.ts b/src/api/routes/stop.ts
index 6a6e6277..79e132d7 100644
--- a/src/api/routes/stop.ts
+++ b/src/api/routes/stop.ts
@@ -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);
diff --git a/src/api/routes/updates.ts b/src/api/routes/updates.ts
index f7403899..101bd3bc 100644
--- a/src/api/routes/updates.ts
+++ b/src/api/routes/updates.ts
@@ -16,37 +16,53 @@
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) => {
- const platform = req.query.platform;
+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)
- throw FieldErrors({
- platform: {
- code: "BASE_TYPE_REQUIRED",
- message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ if (!platform)
+ throw FieldErrors({
+ platform: {
+ code: "BASE_TYPE_REQUIRED",
+ message: req.t("common:field.BASE_TYPE_REQUIRED"),
+ },
+ });
+
+ const release = await Release.findOneOrFail({
+ where: {
+ enabled: true,
+ platform: platform as string,
},
+ order: { pub_date: "DESC" },
});
- const release = await Release.findOneOrFail({
- where: {
- enabled: true,
- platform: platform as string,
- },
- order: { pub_date: "DESC" },
- });
-
- res.json({
- name: release.name,
- pub_date: release.pub_date,
- url: release.url,
- notes: release.notes,
- });
-});
+ res.json({
+ name: release.name,
+ pub_date: release.pub_date,
+ url: release.url,
+ notes: release.notes,
+ });
+ },
+);
export default router;
diff --git a/src/api/routes/users/#id/delete.ts b/src/api/routes/users/#id/delete.ts
index e36a35e6..5b1a682c 100644
--- a/src/api/routes/users/#id/delete.ts
+++ b/src/api/routes/users/#id/delete.ts
@@ -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 },
diff --git a/src/api/routes/users/#id/index.ts b/src/api/routes/users/#id/index.ts
index 0c7cfe37..1bd413d3 100644
--- a/src/api/routes/users/#id/index.ts
+++ b/src/api/routes/users/#id/index.ts
@@ -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) => {
- const { id } = req.params;
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIPublicUser",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { id } = req.params;
- res.json(await User.getPublicUser(id));
-});
+ res.json(await User.getPublicUser(id));
+ },
+);
export default router;
diff --git a/src/api/routes/users/#id/profile.ts b/src/api/routes/users/#id/profile.ts
index 2836c563..a94eb546 100644
--- a/src/api/routes/users/#id/profile.ts
+++ b/src/api/routes/users/#id/profile.ts
@@ -16,23 +16,23 @@
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,
Member,
- UserProfileModifySchema,
- handleFile,
PrivateUserProjection,
- emitEvent,
+ User,
+ UserProfileModifySchema,
UserUpdateEvent,
+ emitEvent,
+ handleFile,
} from "@spacebar/util";
-import { route } from "@spacebar/api";
+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;
diff --git a/src/api/routes/users/#id/relationships.ts b/src/api/routes/users/#id/relationships.ts
index dfe52a5e..3737ca00 100644
--- a/src/api/routes/users/#id/relationships.ts
+++ b/src/api/routes/users/#id/relationships.ts
@@ -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"],
diff --git a/src/api/routes/users/@me/channels.ts b/src/api/routes/users/@me/channels.ts
index 04db4fe9..8a8fadd9 100644
--- a/src/api/routes/users/@me/channels.ts
+++ b/src/api/routes/users/@me/channels.ts
@@ -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) => {
- 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])),
- ),
- );
-});
+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]),
+ ),
+ ),
+ );
+ },
+);
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(
diff --git a/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts b/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts
index 3a4e5e0a..351ec99a 100644
--- a/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts
+++ b/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts
@@ -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;
diff --git a/src/api/routes/users/@me/delete.ts b/src/api/routes/users/@me/delete.ts
index dce737fc..e36a1e92 100644
--- a/src/api/routes/users/@me/delete.ts
+++ b/src/api/routes/users/@me/delete.ts
@@ -16,41 +16,58 @@
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) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- }); //User object
- let correctpass = true;
-
- if (user.data.hash) {
- // guest accounts can delete accounts without password
- correctpass = await bcrypt.compare(req.body.password, user.data.hash);
- if (!correctpass) {
- throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
+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"],
+ }); //User object
+ let correctpass = true;
+
+ if (user.data.hash) {
+ // guest accounts can delete accounts without password
+ correctpass = await bcrypt.compare(
+ req.body.password,
+ user.data.hash,
+ );
+ if (!correctpass) {
+ throw new HTTPError(req.t("auth:login.INVALID_PASSWORD"));
+ }
}
- }
- // TODO: decrement guild member count
+ // TODO: decrement guild member count
- if (correctpass) {
- await Promise.all([
- User.delete({ id: req.user_id }),
- Member.delete({ id: req.user_id }),
- ]);
+ if (correctpass) {
+ await Promise.all([
+ User.delete({ id: req.user_id }),
+ Member.delete({ id: req.user_id }),
+ ]);
- res.sendStatus(204);
- } else {
- res.sendStatus(401);
- }
-});
+ res.sendStatus(204);
+ } else {
+ res.sendStatus(401);
+ }
+ },
+);
export default router;
diff --git a/src/api/routes/users/@me/disable.ts b/src/api/routes/users/@me/disable.ts
index d123a6a1..b4d03e62 100644
--- a/src/api/routes/users/@me/disable.ts
+++ b/src/api/routes/users/@me/disable.ts
@@ -16,35 +16,52 @@
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) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: ["data"],
- }); //User object
- let correctpass = true;
+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"],
+ }); //User object
+ let correctpass = true;
- 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 :/
- }
+ 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 :/
+ }
- if (correctpass) {
- await User.update({ id: req.user_id }, { disabled: true });
+ if (correctpass) {
+ await User.update({ id: req.user_id }, { disabled: true });
- res.sendStatus(204);
- } else {
- res.status(400).json({
- message: "Password does not match",
- code: 50018,
- });
- }
-});
+ res.sendStatus(204);
+ } else {
+ res.status(400).json({
+ message: "Password does not match",
+ code: 50018,
+ });
+ }
+ },
+);
export default router;
diff --git a/src/api/routes/users/@me/guilds.ts b/src/api/routes/users/@me/guilds.ts
index b16b909d..0bce432b 100644
--- a/src/api/routes/users/@me/guilds.ts
+++ b/src/api/routes/users/@me/guilds.ts
@@ -16,79 +16,106 @@
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) => {
- const members = await Member.find({
- relations: ["guild"],
- where: { id: req.user_id },
- });
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIGuildArray",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const members = await Member.find({
+ relations: ["guild"],
+ where: { id: req.user_id },
+ });
- let guild = members.map((x) => x.guild);
+ let guild = members.map((x) => x.guild);
- if ("with_counts" in req.query && req.query.with_counts == "true") {
- guild = []; // TODO: Load guilds with user role permissions number
- }
+ if ("with_counts" in req.query && req.query.with_counts == "true") {
+ guild = []; // TODO: Load guilds with user role permissions number
+ }
- res.json(guild);
-});
+ res.json(guild);
+ },
+);
// user send to leave a certain guild
-router.delete("/:guild_id", route({}), async (req: Request, res: Response) => {
- const { autoJoin } = Config.get().guild;
- const { guild_id } = req.params;
- const guild = await Guild.findOneOrFail({
- where: { id: guild_id },
- select: ["owner_id"],
- });
+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({
+ where: { id: guild_id },
+ select: ["owner_id"],
+ });
- if (!guild) throw new HTTPError("Guild doesn't exist", 404);
- if (guild.owner_id === req.user_id)
- throw new HTTPError("You can't leave your own guild", 400);
- if (
- autoJoin.enabled &&
- autoJoin.guilds.includes(guild_id) &&
- !autoJoin.canLeave
- ) {
- throw new HTTPError("You can't leave instance auto join guilds", 400);
- }
+ if (!guild) throw new HTTPError("Guild doesn't exist", 404);
+ if (guild.owner_id === req.user_id)
+ throw new HTTPError("You can't leave your own guild", 400);
+ if (
+ autoJoin.enabled &&
+ autoJoin.guilds.includes(guild_id) &&
+ !autoJoin.canLeave
+ ) {
+ throw new HTTPError(
+ "You can't leave instance auto join guilds",
+ 400,
+ );
+ }
- await Promise.all([
- Member.delete({ id: req.user_id, guild_id: guild_id }),
- emitEvent({
- event: "GUILD_DELETE",
- data: {
- id: guild_id,
- },
- user_id: req.user_id,
- } as GuildDeleteEvent),
- ]);
+ await Promise.all([
+ Member.delete({ id: req.user_id, guild_id: guild_id }),
+ emitEvent({
+ event: "GUILD_DELETE",
+ data: {
+ id: guild_id,
+ },
+ user_id: req.user_id,
+ } as GuildDeleteEvent),
+ ]);
- const user = await User.getPublicUser(req.user_id);
+ const user = await User.getPublicUser(req.user_id);
- await emitEvent({
- event: "GUILD_MEMBER_REMOVE",
- data: {
+ await emitEvent({
+ event: "GUILD_MEMBER_REMOVE",
+ data: {
+ guild_id: guild_id,
+ user: user,
+ },
guild_id: guild_id,
- user: user,
- },
- guild_id: guild_id,
- } as GuildMemberRemoveEvent);
+ } as GuildMemberRemoveEvent);
- return res.sendStatus(204);
-});
+ return res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/users/@me/guilds/#guild_id/settings.ts b/src/api/routes/users/@me/guilds/#guild_id/settings.ts
index 7e9f2a08..ac6586ce 100644
--- a/src/api/routes/users/@me/guilds/#guild_id/settings.ts
+++ b/src/api/routes/users/@me/guilds/#guild_id/settings.ts
@@ -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) => {
- const user = await Member.findOneOrFail({
- where: { id: req.user_id, guild_id: req.params.guild_id },
- select: ["settings"],
- });
- return res.json(user.settings);
-});
+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;
diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts
index b3eeb964..8fe86265 100644
--- a/src/api/routes/users/@me/index.ts
+++ b/src/api/routes/users/@me/index.ts
@@ -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) => {
- res.json(
- await User.findOne({
- select: PrivateUserProjection,
- where: { id: req.user_id },
- }),
- );
-});
+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;
diff --git a/src/api/routes/users/@me/mfa/codes-verification.ts b/src/api/routes/users/@me/mfa/codes-verification.ts
index 69d45e91..f71704a9 100644
--- a/src/api/routes/users/@me/mfa/codes-verification.ts
+++ b/src/api/routes/users/@me/mfa/codes-verification.ts
@@ -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;
diff --git a/src/api/routes/users/@me/mfa/codes.ts b/src/api/routes/users/@me/mfa/codes.ts
index 4ddbf78e..f9cfc4c4 100644
--- a/src/api/routes/users/@me/mfa/codes.ts
+++ b/src/api/routes/users/@me/mfa/codes.ts
@@ -16,16 +16,16 @@
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,
FieldErrors,
generateMfaBackupCodes,
- User,
MfaCodesSchema,
+ User,
} from "@spacebar/util";
import bcrypt from "bcrypt";
+import { Request, Response, Router } from "express";
const router = Router();
@@ -33,7 +33,23 @@ const router = Router();
router.post(
"/",
- route({ body: "MfaCodesSchema" }),
+ route({
+ requestBody: "MfaCodesSchema",
+ deprecated: true,
+ description:
+ "This route is replaced with users/@me/mfa/codes-verification in newer clients",
+ responses: {
+ 200: {
+ body: "APIBackupCodeArray",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
async (req: Request, res: Response) => {
const { password, regenerate } = req.body as MfaCodesSchema;
diff --git a/src/api/routes/users/@me/mfa/totp/disable.ts b/src/api/routes/users/@me/mfa/totp/disable.ts
index 9f406423..362152d7 100644
--- a/src/api/routes/users/@me/mfa/totp/disable.ts
+++ b/src/api/routes/users/@me/mfa/totp/disable.ts
@@ -16,22 +16,32 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { Router, Request, Response } from "express";
import { route } from "@spacebar/api";
-import { verifyToken } from "node-2fa";
-import { HTTPError } from "lambert-server";
import {
- User,
- generateToken,
BackupCode,
TotpDisableSchema,
+ 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: "TotpDisableSchema" }),
+ route({
+ requestBody: "TotpDisableSchema",
+ responses: {
+ 200: {
+ body: "TokenOnlyResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
async (req: Request, res: Response) => {
const body = req.body as TotpDisableSchema;
diff --git a/src/api/routes/users/@me/mfa/totp/enable.ts b/src/api/routes/users/@me/mfa/totp/enable.ts
index 4d6b2763..19836e4d 100644
--- a/src/api/routes/users/@me/mfa/totp/enable.ts
+++ b/src/api/routes/users/@me/mfa/totp/enable.ts
@@ -16,15 +16,15 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { Router, Request, Response } from "express";
+import { route } from "@spacebar/api";
import {
+ TotpEnableSchema,
User,
- generateToken,
generateMfaBackupCodes,
- TotpEnableSchema,
+ generateToken,
} from "@spacebar/util";
-import { route } from "@spacebar/api";
import bcrypt from "bcrypt";
+import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { verifyToken } from "node-2fa";
@@ -32,7 +32,20 @@ const router = Router();
router.post(
"/",
- route({ body: "TotpEnableSchema" }),
+ route({
+ requestBody: "TotpEnableSchema",
+ responses: {
+ 200: {
+ body: "TokenWithBackupCodesResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
async (req: Request, res: Response) => {
const body = req.body as TotpEnableSchema;
diff --git a/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts b/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts
index 04aca7e4..9cf42def 100644
--- a/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts
+++ b/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts
@@ -21,21 +21,31 @@ import { SecurityKey, User } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router();
-router.delete("/", route({}), async (req: Request, res: Response) => {
- const { key_id } = req.params;
+router.delete(
+ "/",
+ route({
+ responses: {
+ 204: {},
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { key_id } = req.params;
- await SecurityKey.delete({
- id: key_id,
- user_id: req.user_id,
- });
+ await SecurityKey.delete({
+ id: key_id,
+ user_id: req.user_id,
+ });
- const keys = await SecurityKey.count({ where: { user_id: req.user_id } });
+ const keys = await SecurityKey.count({
+ where: { user_id: req.user_id },
+ });
- // disable webauthn if there are no keys left
- if (keys === 0)
- await User.update({ id: req.user_id }, { webauthn_enabled: false });
+ // disable webauthn if there are no keys left
+ if (keys === 0)
+ await User.update({ id: req.user_id }, { webauthn_enabled: false });
- res.sendStatus(204);
-});
+ res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts b/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts
index 29dbb7cf..f383ffb7 100644
--- a/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts
+++ b/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts
@@ -73,7 +73,17 @@ router.get("/", route({}), async (req: Request, res: Response) => {
router.post(
"/",
- route({ body: "WebAuthnPostSchema" }),
+ route({
+ requestBody: "WebAuthnPostSchema",
+ responses: {
+ 200: {
+ body: "WebAuthnCreateResponse",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
async (req: Request, res: Response) => {
if (!WebAuthn.fido2) {
// TODO: I did this for typescript and I can't use !
diff --git a/src/api/routes/users/@me/notes.ts b/src/api/routes/users/@me/notes.ts
index d05c799c..248e61f9 100644
--- a/src/api/routes/users/@me/notes.ts
+++ b/src/api/routes/users/@me/notes.ts
@@ -16,71 +16,99 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { Request, Response, Router } from "express";
import { route } from "@spacebar/api";
-import { User, Note, emitEvent, Snowflake } from "@spacebar/util";
+import { Note, Snowflake, User, emitEvent } from "@spacebar/util";
+import { Request, Response, Router } from "express";
const router: Router = Router();
-router.get("/:id", route({}), async (req: Request, res: Response) => {
- const { id } = req.params;
-
- const note = await Note.findOneOrFail({
- where: {
- owner: { id: req.user_id },
- target: { id: id },
+router.get(
+ "/:id",
+ route({
+ responses: {
+ 200: {
+ body: "UserNoteResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
},
- });
+ }),
+ async (req: Request, res: Response) => {
+ const { id } = req.params;
+
+ const note = await Note.findOneOrFail({
+ where: {
+ owner: { id: req.user_id },
+ target: { id: id },
+ },
+ });
- return res.json({
- note: note?.content,
- note_user_id: id,
- user_id: req.user_id,
- });
-});
+ return res.json({
+ note: note?.content,
+ note_user_id: id,
+ user_id: req.user_id,
+ });
+ },
+);
-router.put("/:id", route({}), async (req: Request, res: Response) => {
- const { id } = req.params;
- const owner = await User.findOneOrFail({ where: { id: req.user_id } });
- const target = await User.findOneOrFail({ where: { id: id } }); //if noted user does not exist throw
- const { note } = req.body;
+router.put(
+ "/:id",
+ route({
+ requestBody: "UserNoteUpdateSchema",
+ responses: {
+ 204: {},
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { id } = req.params;
+ const owner = await User.findOneOrFail({ where: { id: req.user_id } });
+ const target = await User.findOneOrFail({ where: { id: id } }); //if noted user does not exist throw
+ const { note } = req.body;
- if (note && note.length) {
- // upsert a note
- if (
- await Note.findOne({
- where: { owner: { id: owner.id }, target: { id: target.id } },
- })
- ) {
- Note.update(
- { owner: { id: owner.id }, target: { id: target.id } },
- { owner, target, content: note },
- );
+ if (note && note.length) {
+ // upsert a note
+ if (
+ await Note.findOne({
+ where: {
+ owner: { id: owner.id },
+ target: { id: target.id },
+ },
+ })
+ ) {
+ Note.update(
+ { owner: { id: owner.id }, target: { id: target.id } },
+ { owner, target, content: note },
+ );
+ } else {
+ Note.insert({
+ id: Snowflake.generate(),
+ owner,
+ target,
+ content: note,
+ });
+ }
} else {
- Note.insert({
- id: Snowflake.generate(),
- owner,
- target,
- content: note,
+ await Note.delete({
+ owner: { id: owner.id },
+ target: { id: target.id },
});
}
- } else {
- await Note.delete({
- owner: { id: owner.id },
- target: { id: target.id },
- });
- }
- await emitEvent({
- event: "USER_NOTE_UPDATE",
- data: {
- note: note,
- id: target.id,
- },
- user_id: owner.id,
- });
+ await emitEvent({
+ event: "USER_NOTE_UPDATE",
+ data: {
+ note: note,
+ id: target.id,
+ },
+ user_id: owner.id,
+ });
- return res.status(204);
-});
+ return res.status(204);
+ },
+);
export default router;
diff --git a/src/api/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts
index e9ea47e6..bce0a654 100644
--- a/src/api/routes/users/@me/relationships.ts
+++ b/src/api/routes/users/@me/relationships.ts
@@ -16,20 +16,20 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+import { route } from "@spacebar/api";
import {
- RelationshipAddEvent,
- User,
+ Config,
+ DiscordApiErrors,
PublicUserProjection,
- RelationshipType,
+ Relationship,
+ RelationshipAddEvent,
RelationshipRemoveEvent,
+ RelationshipType,
+ User,
emitEvent,
- Relationship,
- Config,
} from "@spacebar/util";
-import { Router, Response, Request } from "express";
+import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
-import { DiscordApiErrors } from "@spacebar/util";
-import { route } from "@spacebar/api";
const router = Router();
@@ -38,29 +38,53 @@ const userProjection: (keyof User)[] = [
...PublicUserProjection,
];
-router.get("/", route({}), async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- relations: ["relationships", "relationships.to"],
- select: ["id", "relationships"],
- });
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "UserRelationshipsResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["relationships", "relationships.to"],
+ select: ["id", "relationships"],
+ });
- //TODO DTO
- const related_users = user.relationships.map((r) => {
- return {
- id: r.to.id,
- type: r.type,
- nickname: null,
- user: r.to.toPublicUser(),
- };
- });
+ //TODO DTO
+ const related_users = user.relationships.map((r) => {
+ return {
+ id: r.to.id,
+ type: r.type,
+ nickname: null,
+ user: r.to.toPublicUser(),
+ };
+ });
- return res.json(related_users);
-});
+ return res.json(related_users);
+ },
+);
router.put(
"/:id",
- route({ body: "RelationshipPutSchema" }),
+ route({
+ requestBody: "RelationshipPutSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
async (req: Request, res: Response) => {
return await updateRelationship(
req,
@@ -77,7 +101,18 @@ router.put(
router.post(
"/",
- route({ body: "RelationshipPostSchema" }),
+ route({
+ requestBody: "RelationshipPostSchema",
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
async (req: Request, res: Response) => {
return await updateRelationship(
req,
@@ -98,64 +133,78 @@ router.post(
},
);
-router.delete("/:id", route({}), async (req: Request, res: Response) => {
- const { id } = req.params;
- if (id === req.user_id)
- throw new HTTPError("You can't remove yourself as a friend");
+router.delete(
+ "/:id",
+ route({
+ responses: {
+ 204: {},
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const { id } = req.params;
+ if (id === req.user_id)
+ throw new HTTPError("You can't remove yourself as a friend");
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- select: userProjection,
- relations: ["relationships"],
- });
- const friend = await User.findOneOrFail({
- where: { id: id },
- select: userProjection,
- relations: ["relationships"],
- });
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ select: userProjection,
+ relations: ["relationships"],
+ });
+ const friend = await User.findOneOrFail({
+ where: { id: id },
+ select: userProjection,
+ relations: ["relationships"],
+ });
- const relationship = user.relationships.find((x) => x.to_id === id);
- const friendRequest = friend.relationships.find(
- (x) => x.to_id === req.user_id,
- );
+ const relationship = user.relationships.find((x) => x.to_id === id);
+ const friendRequest = friend.relationships.find(
+ (x) => x.to_id === req.user_id,
+ );
- if (!relationship)
- throw new HTTPError("You are not friends with the user", 404);
- if (relationship?.type === RelationshipType.blocked) {
- // unblock user
+ if (!relationship)
+ throw new HTTPError("You are not friends with the user", 404);
+ if (relationship?.type === RelationshipType.blocked) {
+ // unblock user
+
+ await Promise.all([
+ Relationship.delete({ id: relationship.id }),
+ emitEvent({
+ event: "RELATIONSHIP_REMOVE",
+ user_id: req.user_id,
+ data: relationship.toPublicRelationship(),
+ } as RelationshipRemoveEvent),
+ ]);
+ return res.sendStatus(204);
+ }
+ if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
+ await Promise.all([
+ Relationship.delete({ id: friendRequest.id }),
+ await emitEvent({
+ event: "RELATIONSHIP_REMOVE",
+ data: friendRequest.toPublicRelationship(),
+ user_id: id,
+ } as RelationshipRemoveEvent),
+ ]);
+ }
await Promise.all([
Relationship.delete({ id: relationship.id }),
emitEvent({
event: "RELATIONSHIP_REMOVE",
- user_id: req.user_id,
data: relationship.toPublicRelationship(),
+ user_id: req.user_id,
} as RelationshipRemoveEvent),
]);
- return res.sendStatus(204);
- }
- if (friendRequest && friendRequest.type !== RelationshipType.blocked) {
- await Promise.all([
- Relationship.delete({ id: friendRequest.id }),
- await emitEvent({
- event: "RELATIONSHIP_REMOVE",
- data: friendRequest.toPublicRelationship(),
- user_id: id,
- } as RelationshipRemoveEvent),
- ]);
- }
- await Promise.all([
- Relationship.delete({ id: relationship.id }),
- emitEvent({
- event: "RELATIONSHIP_REMOVE",
- data: relationship.toPublicRelationship(),
- user_id: req.user_id,
- } as RelationshipRemoveEvent),
- ]);
-
- return res.sendStatus(204);
-});
+ return res.sendStatus(204);
+ },
+);
export default router;
diff --git a/src/api/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts
index 62cfe904..d22d6de1 100644
--- a/src/api/routes/users/@me/settings.ts
+++ b/src/api/routes/users/@me/settings.ts
@@ -16,23 +16,49 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { Router, Response, Request } from "express";
-import { User, UserSettingsSchema } from "@spacebar/util";
import { route } from "@spacebar/api";
+import { User, UserSettingsSchema } from "@spacebar/util";
+import { Request, Response, Router } from "express";
const router = Router();
-router.get("/", route({}), async (req: Request, res: Response) => {
- const user = await User.findOneOrFail({
- where: { id: req.user_id },
- relations: ["settings"],
- });
- return res.json(user.settings);
-});
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "UserSettings",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ const user = await User.findOneOrFail({
+ where: { id: req.user_id },
+ relations: ["settings"],
+ });
+ return res.json(user.settings);
+ },
+);
router.patch(
"/",
- route({ body: "UserSettingsSchema" }),
+ route({
+ requestBody: "UserSettingsSchema",
+ responses: {
+ 200: {
+ body: "UserSettings",
+ },
+ 400: {
+ body: "APIErrorResponse",
+ },
+ 404: {
+ body: "APIErrorResponse",
+ },
+ },
+ }),
async (req: Request, res: Response) => {
const body = req.body as UserSettingsSchema;
if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unkown locale
diff --git a/src/api/routes/voice/regions.ts b/src/api/routes/voice/regions.ts
index 59bac07f..10a8b21d 100644
--- a/src/api/routes/voice/regions.ts
+++ b/src/api/routes/voice/regions.ts
@@ -16,14 +16,23 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { Router, Request, Response } from "express";
-import { getIpAdress, route } from "@spacebar/api";
-import { getVoiceRegions } from "@spacebar/api";
+import { getIpAdress, getVoiceRegions, route } from "@spacebar/api";
+import { Request, Response, Router } from "express";
const router: Router = Router();
-router.get("/", route({}), async (req: Request, res: Response) => {
- res.json(await getVoiceRegions(getIpAdress(req), true)); //vip true?
-});
+router.get(
+ "/",
+ route({
+ responses: {
+ 200: {
+ body: "APIGuildVoiceRegion",
+ },
+ },
+ }),
+ async (req: Request, res: Response) => {
+ res.json(await getVoiceRegions(getIpAdress(req), true)); //vip true?
+ },
+);
export default router;
diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts
index 604df4e9..5a0b48e6 100644
--- a/src/api/util/handlers/route.ts
+++ b/src/api/util/handlers/route.ts
@@ -17,21 +17,21 @@
*/
import {
- ajv,
DiscordApiErrors,
EVENT,
FieldErrors,
- SpacebarApiErrors,
- getPermission,
- getRights,
- normalizeBody,
PermissionResolvable,
Permissions,
RightResolvable,
Rights,
+ SpacebarApiErrors,
+ ajv,
+ getPermission,
+ getRights,
+ normalizeBody,
} from "@spacebar/util";
-import { NextFunction, Request, Response } from "express";
import { AnyValidateFunction } from "ajv/dist/core";
+import { NextFunction, Request, Response } from "express";
declare global {
// TODO: fix this
@@ -52,21 +52,40 @@ export type RouteResponse = {
export interface RouteOptions {
permission?: PermissionResolvable;
right?: RightResolvable;
- body?: `${string}Schema`; // typescript interface name
- test?: {
- response?: RouteResponse;
- body?: unknown;
- path?: string;
- event?: EVENT | EVENT[];
- headers?: Record<string, string>;
+ requestBody?: `${string}Schema`; // typescript interface name
+ responses?: {
+ [status: number]: {
+ // body?: `${string}Response`;
+ body?: string;
+ };
+ };
+ event?: EVENT | EVENT[];
+ summary?: string;
+ description?: string;
+ query?: {
+ [key: string]: {
+ type: string;
+ required?: boolean;
+ description?: string;
+ values?: string[];
+ };
};
+ deprecated?: boolean;
+ // test?: {
+ // response?: RouteResponse;
+ // body?: unknown;
+ // path?: string;
+ // event?: EVENT | EVENT[];
+ // headers?: Record<string, string>;
+ // };
}
export function route(opts: RouteOptions) {
let validate: AnyValidateFunction | undefined;
- if (opts.body) {
- validate = ajv.getSchema(opts.body);
- if (!validate) throw new Error(`Body schema ${opts.body} not found`);
+ if (opts.requestBody) {
+ validate = ajv.getSchema(opts.requestBody);
+ if (!validate)
+ throw new Error(`Body schema ${opts.requestBody} not found`);
}
return async (req: Request, res: Response, next: NextFunction) => {
|