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) => {
diff --git a/src/gateway/opcodes/LazyRequest.ts b/src/gateway/opcodes/LazyRequest.ts
index 64e50d92..cde91a75 100644
--- a/src/gateway/opcodes/LazyRequest.ts
+++ b/src/gateway/opcodes/LazyRequest.ts
@@ -267,7 +267,9 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) {
if (!Array.isArray(ranges)) throw new Error("Not a valid Array");
const member_count = await Member.count({ where: { guild_id } });
- const ops = await Promise.all(ranges.map((x) => getMembers(guild_id, x)));
+ const ops = await Promise.all(
+ ranges.map((x) => getMembers(guild_id, x as [number, number])),
+ );
// TODO: unsubscribe member_events that are not in op.members
diff --git a/src/util/config/types/subconfigurations/limits/RateLimits.ts b/src/util/config/types/subconfigurations/limits/RateLimits.ts
index caba740b..0ce0827c 100644
--- a/src/util/config/types/subconfigurations/limits/RateLimits.ts
+++ b/src/util/config/types/subconfigurations/limits/RateLimits.ts
@@ -16,11 +16,11 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { RouteRateLimit, RateLimitOptions } from ".";
+import { RateLimitOptions, RouteRateLimit } from ".";
export class RateLimits {
enabled: boolean = false;
- ip: Omit<RateLimitOptions, "bot_count"> = {
+ ip: RateLimitOptions = {
count: 500,
window: 5,
};
diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts
index 9ce04848..e23d93db 100644
--- a/src/util/entities/Channel.ts
+++ b/src/util/entities/Channel.ts
@@ -482,3 +482,27 @@ export enum ChannelPermissionOverwriteType {
member = 1,
group = 2,
}
+
+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);
+ }
+}
diff --git a/src/util/entities/Guild.ts b/src/util/entities/Guild.ts
index e8454986..e2b3e1bd 100644
--- a/src/util/entities/Guild.ts
+++ b/src/util/entities/Guild.ts
@@ -24,7 +24,7 @@ import {
OneToMany,
RelationId,
} from "typeorm";
-import { Config, handleFile, Snowflake } from "..";
+import { Config, GuildWelcomeScreen, handleFile, Snowflake } from "..";
import { Ban } from "./Ban";
import { BaseClass } from "./BaseClass";
import { Channel } from "./Channel";
@@ -77,7 +77,7 @@ export class Guild extends BaseClass {
afk_channel?: Channel;
@Column({ nullable: true })
- afk_timeout?: number = Config.get().defaults.guild.afkTimeout;
+ afk_timeout?: number;
// * commented out -> use owner instead
// application id of the guild creator if it is bot-created
@@ -95,8 +95,7 @@ export class Guild extends BaseClass {
banner?: string;
@Column({ nullable: true })
- default_message_notifications?: number =
- Config.get().defaults.guild.defaultMessageNotifications;
+ default_message_notifications?: number;
@Column({ nullable: true })
description?: string;
@@ -105,11 +104,10 @@ export class Guild extends BaseClass {
discovery_splash?: string;
@Column({ nullable: true })
- explicit_content_filter?: number =
- Config.get().defaults.guild.explicitContentFilter;
+ explicit_content_filter?: number;
@Column({ type: "simple-array" })
- features: string[] = Config.get().guild.defaultFeatures || []; //TODO use enum
+ features: string[] = []; //TODO use enum
//TODO: https://discord.com/developers/docs/resources/guild#guild-object-guild-features
@Column({ nullable: true })
@@ -122,14 +120,13 @@ export class Guild extends BaseClass {
large?: boolean = false;
@Column({ nullable: true })
- max_members?: number = Config.get().limits.guild.maxMembers;
+ max_members?: number;
@Column({ nullable: true })
- max_presences?: number = Config.get().defaults.guild.maxPresences;
+ max_presences?: number;
@Column({ nullable: true })
- max_video_channel_users?: number =
- Config.get().defaults.guild.maxVideoChannelUsers;
+ max_video_channel_users?: number;
@Column({ nullable: true })
member_count?: number;
@@ -247,7 +244,7 @@ export class Guild extends BaseClass {
rules_channel?: string;
@Column({ nullable: true })
- region?: string = Config.get().regions.default;
+ region?: string;
@Column({ nullable: true })
splash?: string;
@@ -270,16 +267,7 @@ export class Guild extends BaseClass {
verification_level?: number;
@Column({ type: "simple-json" })
- welcome_screen: {
- enabled: boolean;
- description: string;
- welcome_channels: {
- description: string;
- emoji_id?: string;
- emoji_name?: string;
- channel_id: string;
- }[];
- };
+ welcome_screen: GuildWelcomeScreen;
@Column({ nullable: true })
@RelationId((guild: Guild) => guild.widget_channel)
@@ -336,6 +324,18 @@ export class Guild extends BaseClass {
description: "Fill in your description",
welcome_channels: [],
},
+
+ afk_timeout: Config.get().defaults.guild.afkTimeout,
+ default_message_notifications:
+ Config.get().defaults.guild.defaultMessageNotifications,
+ explicit_content_filter:
+ Config.get().defaults.guild.explicitContentFilter,
+ features: Config.get().guild.defaultFeatures,
+ max_members: Config.get().limits.guild.maxMembers,
+ max_presences: Config.get().defaults.guild.maxPresences,
+ max_video_channel_users:
+ Config.get().defaults.guild.maxVideoChannelUsers,
+ region: Config.get().regions.default,
}).save();
// we have to create the role _after_ the guild because else we would get a "SQLITE_CONSTRAINT: FOREIGN KEY constraint failed" error
diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts
index df9af328..3e72c3c9 100644
--- a/src/util/entities/User.ts
+++ b/src/util/entities/User.ts
@@ -26,11 +26,11 @@ import {
OneToOne,
} from "typeorm";
import {
- adjustEmail,
Config,
Email,
FieldErrors,
Snowflake,
+ adjustEmail,
trimSpecial,
} from "..";
import { BitField } from "../util/BitField";
@@ -86,8 +86,7 @@ export const PrivateUserProjection = [
// Private user data that should never get sent to the client
export type PublicUser = Pick<User, PublicUserKeys>;
-
-export type UserPublic = Pick<User, PublicUserKeys>;
+export type PrivateUser = Pick<User, PrivateUserKeys>;
export interface UserPrivate extends Pick<User, PrivateUserKeys> {
locale: string;
@@ -110,8 +109,10 @@ export class User extends BaseClass {
@Column({ nullable: true })
banner?: string; // hash of the user banner
+ // TODO: Separate `User` and `UserProfile` models
+ // puyo: changed from [number, number] because it breaks openapi
@Column({ nullable: true, type: "simple-array" })
- theme_colors?: [number, number]; // TODO: Separate `User` and `UserProfile` models
+ theme_colors?: number[];
@Column({ nullable: true })
pronouns?: string;
@@ -126,10 +127,10 @@ export class User extends BaseClass {
mobile: boolean = false; // if the user has mobile app installed
@Column()
- premium: boolean = Config.get().defaults.user.premium ?? false; // if user bought individual premium
+ premium: boolean; // if user bought individual premium
@Column()
- premium_type: number = Config.get().defaults.user.premiumType ?? 0; // individual premium level
+ premium_type: number; // individual premium level
@Column()
bot: boolean = false; // if user is bot
@@ -156,13 +157,13 @@ export class User extends BaseClass {
totp_last_ticket?: string = "";
@Column()
- created_at: Date = new Date(); // registration date
+ created_at: Date; // registration date
@Column({ nullable: true })
premium_since: Date; // premium date
@Column({ select: false })
- verified: boolean = Config.get().defaults.user.verified ?? true; // email is verified
+ verified: boolean; // email is verified
@Column()
disabled: boolean = false; // if the account is disabled
@@ -381,11 +382,16 @@ export class User extends BaseClass {
valid_tokens_since: new Date(),
},
extended_settings: "{}",
+ settings: settings,
+
premium_since: Config.get().defaults.user.premium
? new Date()
: undefined,
- settings: settings,
rights: Config.get().register.defaultRights,
+ premium: Config.get().defaults.user.premium ?? false,
+ premium_type: Config.get().defaults.user.premiumType ?? 0,
+ verified: Config.get().defaults.user.verified ?? true,
+ created_at: new Date(),
});
user.validate();
diff --git a/src/util/interfaces/Activity.ts b/src/util/interfaces/Activity.ts
index 7654ba90..0227f242 100644
--- a/src/util/interfaces/Activity.ts
+++ b/src/util/interfaces/Activity.ts
@@ -36,7 +36,7 @@ export interface Activity {
};
party?: {
id?: string;
- size?: [number]; // used to show the party's current and maximum size // TODO: array length 2
+ size?: number[]; // used to show the party's current and maximum size // TODO: array length 2
};
assets?: {
large_image?: string; // the id for a large asset of the activity, usually a snowflake
diff --git a/src/util/interfaces/GuildWelcomeScreen.ts b/src/util/interfaces/GuildWelcomeScreen.ts
new file mode 100644
index 00000000..38b6061b
--- /dev/null
+++ b/src/util/interfaces/GuildWelcomeScreen.ts
@@ -0,0 +1,10 @@
+export interface GuildWelcomeScreen {
+ enabled: boolean;
+ description: string;
+ welcome_channels: {
+ description: string;
+ emoji_id?: string;
+ emoji_name?: string;
+ channel_id: string;
+ }[];
+}
diff --git a/src/util/interfaces/index.ts b/src/util/interfaces/index.ts
index c6a00458..6620ba32 100644
--- a/src/util/interfaces/index.ts
+++ b/src/util/interfaces/index.ts
@@ -19,6 +19,7 @@
export * from "./Activity";
export * from "./ConnectedAccount";
export * from "./Event";
+export * from "./GuildWelcomeScreen";
export * from "./Interaction";
export * from "./Presence";
export * from "./Status";
diff --git a/src/util/schemas/AckBulkSchema.ts b/src/util/schemas/AckBulkSchema.ts
index cf6dc597..5604c2fc 100644
--- a/src/util/schemas/AckBulkSchema.ts
+++ b/src/util/schemas/AckBulkSchema.ts
@@ -17,11 +17,9 @@
*/
export interface AckBulkSchema {
- read_states: [
- {
- channel_id: string;
- message_id: string;
- read_state_type: number; // WHat is this?
- },
- ];
+ read_states: {
+ channel_id: string;
+ message_id: string;
+ read_state_type: number; // WHat is this?
+ }[];
}
diff --git a/src/util/schemas/IdentifySchema.ts b/src/util/schemas/IdentifySchema.ts
index fb48c2a4..cb967aed 100644
--- a/src/util/schemas/IdentifySchema.ts
+++ b/src/util/schemas/IdentifySchema.ts
@@ -109,7 +109,11 @@ export interface IdentifySchema {
compress?: boolean;
large_threshold?: number;
largeThreshold?: number;
- shard?: [bigint, bigint];
+ /**
+ * @minItems 2
+ * @maxItems 2
+ */
+ shard?: bigint[]; // puyo: changed from [bigint, bigint] because it breaks openapi
guild_subscriptions?: boolean;
capabilities?: number;
client_state?: {
diff --git a/src/util/schemas/LazyRequestSchema.ts b/src/util/schemas/LazyRequestSchema.ts
index 63e67416..ee52d66c 100644
--- a/src/util/schemas/LazyRequestSchema.ts
+++ b/src/util/schemas/LazyRequestSchema.ts
@@ -18,7 +18,14 @@
export interface LazyRequestSchema {
guild_id: string;
- channels?: Record<string, [number, number][]>;
+ channels?: {
+ /**
+ * @items.type integer
+ * @minItems 2
+ * @maxItems 2
+ */
+ [key: string]: number[][]; // puyo: changed from [number, number] because it breaks openapi
+ };
activities?: boolean;
threads?: boolean;
typing?: true;
diff --git a/src/util/schemas/LoginResponse.ts b/src/util/schemas/LoginResponse.ts
new file mode 100644
index 00000000..faf3f769
--- /dev/null
+++ b/src/util/schemas/LoginResponse.ts
@@ -0,0 +1,14 @@
+import { TokenResponse } from "./responses";
+
+export interface MFAResponse {
+ ticket: string;
+ mfa: true;
+ sms: false; // TODO
+ token: null;
+}
+
+export interface WebAuthnResponse extends MFAResponse {
+ webauthn: string;
+}
+
+export type LoginResponse = TokenResponse | MFAResponse | WebAuthnResponse;
diff --git a/src/util/schemas/MemberChangeProfileSchema.ts b/src/util/schemas/MemberChangeProfileSchema.ts
index e955a0f1..d2d1481d 100644
--- a/src/util/schemas/MemberChangeProfileSchema.ts
+++ b/src/util/schemas/MemberChangeProfileSchema.ts
@@ -21,8 +21,7 @@ export interface MemberChangeProfileSchema {
nick?: string;
bio?: string;
pronouns?: string;
-
- /*
+ /**
* @items.type integer
*/
theme_colors?: [number, number];
diff --git a/src/util/schemas/UserGuildSettingsSchema.ts b/src/util/schemas/UserGuildSettingsSchema.ts
index c295f767..82edae9c 100644
--- a/src/util/schemas/UserGuildSettingsSchema.ts
+++ b/src/util/schemas/UserGuildSettingsSchema.ts
@@ -16,12 +16,12 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { UserGuildSettings, ChannelOverride } from "@spacebar/util";
+import { ChannelOverride, UserGuildSettings } from "@spacebar/util";
// This sucks. I would use a DeepPartial, my own or typeorms, but they both generate inncorect schema
export interface UserGuildSettingsSchema
extends Partial<Omit<UserGuildSettings, "channel_overrides">> {
channel_overrides?: {
- [channel_id: string]: Partial<ChannelOverride>;
+ [channel_id: string]: ChannelOverride;
};
}
diff --git a/src/util/schemas/UserNoteUpdateSchema.ts b/src/util/schemas/UserNoteUpdateSchema.ts
new file mode 100644
index 00000000..0a731279
--- /dev/null
+++ b/src/util/schemas/UserNoteUpdateSchema.ts
@@ -0,0 +1,3 @@
+export interface UserNoteUpdateSchema {
+ note: string;
+}
diff --git a/src/util/schemas/UserProfileModifySchema.ts b/src/util/schemas/UserProfileModifySchema.ts
index d49fe326..3dea257a 100644
--- a/src/util/schemas/UserProfileModifySchema.ts
+++ b/src/util/schemas/UserProfileModifySchema.ts
@@ -21,8 +21,7 @@ export interface UserProfileModifySchema {
accent_color?: number | null;
banner?: string | null;
pronouns?: string;
-
- /*
+ /**
* @items.type integer
*/
theme_colors?: [number, number];
diff --git a/src/util/schemas/UserProfileResponse.ts b/src/util/schemas/UserProfileResponse.ts
index 699d6a29..10bbcdbf 100644
--- a/src/util/schemas/UserProfileResponse.ts
+++ b/src/util/schemas/UserProfileResponse.ts
@@ -16,10 +16,10 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import { PublicConnectedAccount, UserPublic } from "..";
+import { PublicConnectedAccount, PublicUser } from "..";
export interface UserProfileResponse {
- user: UserPublic;
+ user: PublicUser;
connected_accounts: PublicConnectedAccount;
premium_guild_since?: Date;
premium_since?: Date;
diff --git a/src/util/schemas/UserRelationsResponse.ts b/src/util/schemas/UserRelationsResponse.ts
deleted file mode 100644
index 38507420..00000000
--- a/src/util/schemas/UserRelationsResponse.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- Spacebar: A FOSS re-implementation and extension of the Discord.com backend.
- Copyright (C) 2023 Spacebar and Spacebar Contributors
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU Affero General Public License as published
- by the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU Affero General Public License for more details.
-
- You should have received a copy of the GNU Affero General Public License
- along with this program. If not, see <https://www.gnu.org/licenses/>.
-*/
-
-export interface UserRelationsResponse {
- object: {
- id?: string;
- username?: string;
- avatar?: string;
- discriminator?: string;
- public_flags?: number;
- };
-}
diff --git a/src/util/schemas/WebAuthnSchema.ts b/src/util/schemas/WebAuthnSchema.ts
index 652cda34..3f5e0da7 100644
--- a/src/util/schemas/WebAuthnSchema.ts
+++ b/src/util/schemas/WebAuthnSchema.ts
@@ -28,9 +28,9 @@ export interface CreateWebAuthnCredentialSchema {
ticket: string;
}
-export type WebAuthnPostSchema = Partial<
- GenerateWebAuthnCredentialsSchema | CreateWebAuthnCredentialSchema
->;
+export type WebAuthnPostSchema =
+ | GenerateWebAuthnCredentialsSchema
+ | CreateWebAuthnCredentialSchema;
export interface WebAuthnTotpSchema {
code: string;
diff --git a/src/util/schemas/index.ts b/src/util/schemas/index.ts
index 2d254752..44a504cd 100644
--- a/src/util/schemas/index.ts
+++ b/src/util/schemas/index.ts
@@ -69,6 +69,7 @@ export * from "./TotpSchema";
export * from "./UserDeleteSchema";
export * from "./UserGuildSettingsSchema";
export * from "./UserModifySchema";
+export * from "./UserNoteUpdateSchema";
export * from "./UserProfileModifySchema";
export * from "./UserSettingsSchema";
export * from "./Validator";
@@ -79,7 +80,4 @@ export * from "./VoiceVideoSchema";
export * from "./WebAuthnSchema";
export * from "./WebhookCreateSchema";
export * from "./WidgetModifySchema";
-export * from "./UserRelationsResponse";
-export * from "./GatewayResponse";
-export * from "./GatewayBotResponse";
-export * from "./UserProfileResponse";
+export * from "./responses";
diff --git a/src/util/schemas/responses/APIErrorOrCaptchaResponse.ts b/src/util/schemas/responses/APIErrorOrCaptchaResponse.ts
new file mode 100644
index 00000000..c9a0e5be
--- /dev/null
+++ b/src/util/schemas/responses/APIErrorOrCaptchaResponse.ts
@@ -0,0 +1,6 @@
+import { APIErrorResponse } from "./APIErrorResponse";
+import { CaptchaRequiredResponse } from "./CaptchaRequiredResponse";
+
+export type APIErrorOrCaptchaResponse =
+ | CaptchaRequiredResponse
+ | APIErrorResponse;
diff --git a/src/util/schemas/responses/APIErrorResponse.ts b/src/util/schemas/responses/APIErrorResponse.ts
new file mode 100644
index 00000000..25bb9504
--- /dev/null
+++ b/src/util/schemas/responses/APIErrorResponse.ts
@@ -0,0 +1,12 @@
+export interface APIErrorResponse {
+ code: number;
+ message: string;
+ errors: {
+ [key: string]: {
+ _errors: {
+ message: string;
+ code: string;
+ }[];
+ };
+ };
+}
diff --git a/src/util/schemas/responses/BackupCodesChallengeResponse.ts b/src/util/schemas/responses/BackupCodesChallengeResponse.ts
new file mode 100644
index 00000000..5473ad1f
--- /dev/null
+++ b/src/util/schemas/responses/BackupCodesChallengeResponse.ts
@@ -0,0 +1,4 @@
+export interface BackupCodesChallengeResponse {
+ nonce: string;
+ regenerate_nonce: string;
+}
diff --git a/src/util/schemas/responses/CaptchaRequiredResponse.ts b/src/util/schemas/responses/CaptchaRequiredResponse.ts
new file mode 100644
index 00000000..9f7f02ff
--- /dev/null
+++ b/src/util/schemas/responses/CaptchaRequiredResponse.ts
@@ -0,0 +1,5 @@
+export interface CaptchaRequiredResponse {
+ captcha_key: string;
+ captcha_sitekey: string;
+ captcha_service: string;
+}
diff --git a/src/util/schemas/responses/DiscoverableGuildsResponse.ts b/src/util/schemas/responses/DiscoverableGuildsResponse.ts
new file mode 100644
index 00000000..2a9fb1bd
--- /dev/null
+++ b/src/util/schemas/responses/DiscoverableGuildsResponse.ts
@@ -0,0 +1,8 @@
+import { Guild } from "../../entities";
+
+export interface DiscoverableGuildsResponse {
+ total: number;
+ guilds: Guild[];
+ offset: number;
+ limit: number;
+}
diff --git a/src/util/schemas/responses/GatewayBotResponse.ts b/src/util/schemas/responses/GatewayBotResponse.ts
new file mode 100644
index 00000000..30f1f57f
--- /dev/null
+++ b/src/util/schemas/responses/GatewayBotResponse.ts
@@ -0,0 +1,10 @@
+export interface GatewayBotResponse {
+ url: string;
+ shards: number;
+ session_start_limit: {
+ total: number;
+ remaining: number;
+ reset_after: number;
+ max_concurrency: number;
+ };
+}
diff --git a/src/util/schemas/responses/GatewayResponse.ts b/src/util/schemas/responses/GatewayResponse.ts
new file mode 100644
index 00000000..e909f7bd
--- /dev/null
+++ b/src/util/schemas/responses/GatewayResponse.ts
@@ -0,0 +1,3 @@
+export interface GatewayResponse {
+ url: string;
+}
diff --git a/src/util/schemas/responses/GenerateRegistrationTokensResponse.ts b/src/util/schemas/responses/GenerateRegistrationTokensResponse.ts
new file mode 100644
index 00000000..8816eabf
--- /dev/null
+++ b/src/util/schemas/responses/GenerateRegistrationTokensResponse.ts
@@ -0,0 +1,3 @@
+export interface GenerateRegistrationTokensResponse {
+ tokens: string[];
+}
diff --git a/src/util/schemas/responses/GuildBansResponse.ts b/src/util/schemas/responses/GuildBansResponse.ts
new file mode 100644
index 00000000..876a4bc4
--- /dev/null
+++ b/src/util/schemas/responses/GuildBansResponse.ts
@@ -0,0 +1,10 @@
+export interface GuildBansResponse {
+ reason: string;
+ user: {
+ username: string;
+ discriminator: string;
+ id: string;
+ avatar: string | null;
+ public_flags: number;
+ };
+}
diff --git a/src/util/schemas/responses/GuildCreateResponse.ts b/src/util/schemas/responses/GuildCreateResponse.ts
new file mode 100644
index 00000000..8185cb86
--- /dev/null
+++ b/src/util/schemas/responses/GuildCreateResponse.ts
@@ -0,0 +1,3 @@
+export interface GuildCreateResponse {
+ id: string;
+}
diff --git a/src/util/schemas/responses/GuildDiscoveryRequirements.ts b/src/util/schemas/responses/GuildDiscoveryRequirements.ts
new file mode 100644
index 00000000..731976f7
--- /dev/null
+++ b/src/util/schemas/responses/GuildDiscoveryRequirements.ts
@@ -0,0 +1,23 @@
+export interface GuildDiscoveryRequirementsResponse {
+ uild_id: string;
+ safe_environment: boolean;
+ healthy: boolean;
+ health_score_pending: boolean;
+ size: boolean;
+ nsfw_properties: unknown;
+ protected: boolean;
+ sufficient: boolean;
+ sufficient_without_grace_period: boolean;
+ valid_rules_channel: boolean;
+ retention_healthy: boolean;
+ engagement_healthy: boolean;
+ age: boolean;
+ minimum_age: number;
+ health_score: {
+ avg_nonnew_participators: number;
+ avg_nonnew_communicators: number;
+ num_intentful_joiners: number;
+ perc_ret_w1_intentful: number;
+ };
+ minimum_size: number;
+}
diff --git a/src/util/schemas/responses/GuildMessagesSearchResponse.ts b/src/util/schemas/responses/GuildMessagesSearchResponse.ts
new file mode 100644
index 00000000..0b6248b7
--- /dev/null
+++ b/src/util/schemas/responses/GuildMessagesSearchResponse.ts
@@ -0,0 +1,32 @@
+import {
+ Attachment,
+ Embed,
+ MessageType,
+ PublicUser,
+ Role,
+} from "../../entities";
+
+export interface GuildMessagesSearchMessage {
+ id: string;
+ type: MessageType;
+ content?: string;
+ channel_id: string;
+ author: PublicUser;
+ attachments: Attachment[];
+ embeds: Embed[];
+ mentions: PublicUser[];
+ mention_roles: Role[];
+ pinned: boolean;
+ mention_everyone?: boolean;
+ tts: boolean;
+ timestamp: string;
+ edited_timestamp: string | null;
+ flags: number;
+ components: unknown[];
+ hit: true;
+}
+
+export interface GuildMessagesSearchResponse {
+ messages: GuildMessagesSearchMessage[];
+ total_results: number;
+}
diff --git a/src/util/schemas/responses/GuildPruneResponse.ts b/src/util/schemas/responses/GuildPruneResponse.ts
new file mode 100644
index 00000000..fb1abb89
--- /dev/null
+++ b/src/util/schemas/responses/GuildPruneResponse.ts
@@ -0,0 +1,7 @@
+export interface GuildPruneResponse {
+ pruned: number;
+}
+
+export interface GuildPurgeResponse {
+ purged: number;
+}
diff --git a/src/util/schemas/responses/GuildRecommendationsResponse.ts b/src/util/schemas/responses/GuildRecommendationsResponse.ts
new file mode 100644
index 00000000..211670a6
--- /dev/null
+++ b/src/util/schemas/responses/GuildRecommendationsResponse.ts
@@ -0,0 +1,6 @@
+import { Guild } from "../../entities";
+
+export interface GuildRecommendationsResponse {
+ recommended_guilds: Guild[];
+ load_id: string;
+}
diff --git a/src/util/schemas/responses/GuildVanityUrl.ts b/src/util/schemas/responses/GuildVanityUrl.ts
new file mode 100644
index 00000000..ff37bf4e
--- /dev/null
+++ b/src/util/schemas/responses/GuildVanityUrl.ts
@@ -0,0 +1,17 @@
+export interface GuildVanityUrl {
+ code: string;
+ uses: number;
+}
+
+export interface GuildVanityUrlNoInvite {
+ code: null;
+}
+
+export type GuildVanityUrlResponse =
+ | GuildVanityUrl
+ | GuildVanityUrl[]
+ | GuildVanityUrlNoInvite;
+
+export interface GuildVanityUrlCreateResponse {
+ code: string;
+}
diff --git a/src/util/schemas/responses/GuildVoiceRegionsResponse.ts b/src/util/schemas/responses/GuildVoiceRegionsResponse.ts
new file mode 100644
index 00000000..8190d5fd
--- /dev/null
+++ b/src/util/schemas/responses/GuildVoiceRegionsResponse.ts
@@ -0,0 +1,7 @@
+export interface GuildVoiceRegion {
+ id: string;
+ name: string;
+ custom: boolean;
+ deprecated: boolean;
+ optimal: boolean;
+}
diff --git a/src/util/schemas/responses/GuildWidgetJsonResponse.ts b/src/util/schemas/responses/GuildWidgetJsonResponse.ts
new file mode 100644
index 00000000..ef85dd08
--- /dev/null
+++ b/src/util/schemas/responses/GuildWidgetJsonResponse.ts
@@ -0,0 +1,21 @@
+import { ClientStatus } from "../../interfaces";
+
+export interface GuildWidgetJsonResponse {
+ id: string;
+ name: string;
+ instant_invite: string;
+ channels: {
+ id: string;
+ name: string;
+ position: number;
+ }[];
+ members: {
+ id: string;
+ username: string;
+ discriminator: string;
+ avatar: string | null;
+ status: ClientStatus;
+ avatar_url: string;
+ }[];
+ presence_count: number;
+}
diff --git a/src/util/schemas/responses/GuildWidgetSettingsResponse.ts b/src/util/schemas/responses/GuildWidgetSettingsResponse.ts
new file mode 100644
index 00000000..3c6b45ce
--- /dev/null
+++ b/src/util/schemas/responses/GuildWidgetSettingsResponse.ts
@@ -0,0 +1,6 @@
+import { Snowflake } from "../../util";
+
+export interface GuildWidgetSettingsResponse {
+ enabled: boolean;
+ channel_id: Snowflake | null;
+}
diff --git a/src/util/schemas/responses/InstanceDomainsResponse.ts b/src/util/schemas/responses/InstanceDomainsResponse.ts
new file mode 100644
index 00000000..60367492
--- /dev/null
+++ b/src/util/schemas/responses/InstanceDomainsResponse.ts
@@ -0,0 +1,6 @@
+export interface InstanceDomainsResponse {
+ cdn: string;
+ gateway: string;
+ defaultApiVersion: string;
+ apiEndpoint: string;
+}
diff --git a/src/util/schemas/responses/InstancePingResponse.ts b/src/util/schemas/responses/InstancePingResponse.ts
new file mode 100644
index 00000000..5f1a9488
--- /dev/null
+++ b/src/util/schemas/responses/InstancePingResponse.ts
@@ -0,0 +1,13 @@
+export interface InstancePingResponse {
+ ping: "pong!";
+ instance: {
+ id: string;
+ name: string;
+ description: string | null;
+ image: string | null;
+ correspondenceEmail: string | null;
+ correspondenceUserID: string | null;
+ frontPage: string | null;
+ tosPage: string | null;
+ };
+}
diff --git a/src/util/schemas/responses/InstanceStatsResponse.ts b/src/util/schemas/responses/InstanceStatsResponse.ts
new file mode 100644
index 00000000..d24fd434
--- /dev/null
+++ b/src/util/schemas/responses/InstanceStatsResponse.ts
@@ -0,0 +1,8 @@
+export interface InstanceStatsResponse {
+ counts: {
+ user: number;
+ guild: number;
+ message: number;
+ members: number;
+ };
+}
diff --git a/src/util/schemas/responses/LocationMetadataResponse.ts b/src/util/schemas/responses/LocationMetadataResponse.ts
new file mode 100644
index 00000000..55337557
--- /dev/null
+++ b/src/util/schemas/responses/LocationMetadataResponse.ts
@@ -0,0 +1,5 @@
+export interface LocationMetadataResponse {
+ consent_required: boolean;
+ country_code: string;
+ promotional_email_opt_in: { required: true; pre_checked: false };
+}
diff --git a/src/util/schemas/responses/MemberJoinGuildResponse.ts b/src/util/schemas/responses/MemberJoinGuildResponse.ts
new file mode 100644
index 00000000..d7b39d10
--- /dev/null
+++ b/src/util/schemas/responses/MemberJoinGuildResponse.ts
@@ -0,0 +1,8 @@
+import { Emoji, Guild, Role, Sticker } from "../../entities";
+
+export interface MemberJoinGuildResponse {
+ guild: Guild;
+ emojis: Emoji[];
+ roles: Role[];
+ stickers: Sticker[];
+}
diff --git a/src/util/schemas/responses/OAuthAuthorizeResponse.ts b/src/util/schemas/responses/OAuthAuthorizeResponse.ts
new file mode 100644
index 00000000..60d6d2e2
--- /dev/null
+++ b/src/util/schemas/responses/OAuthAuthorizeResponse.ts
@@ -0,0 +1,3 @@
+export interface OAuthAuthorizeResponse {
+ location: string;
+}
diff --git a/src/util/schemas/responses/Tenor.ts b/src/util/schemas/responses/Tenor.ts
new file mode 100644
index 00000000..9dddf9d0
--- /dev/null
+++ b/src/util/schemas/responses/Tenor.ts
@@ -0,0 +1,72 @@
+export enum TenorMediaTypes {
+ gif,
+ mediumgif,
+ tinygif,
+ nanogif,
+ mp4,
+ loopedmp4,
+ tinymp4,
+ nanomp4,
+ webm,
+ tinywebm,
+ nanowebm,
+}
+
+export type TenorMedia = {
+ preview: string;
+ url: string;
+ dims: number[];
+ size: number;
+};
+
+export type TenorGif = {
+ created: number;
+ hasaudio: boolean;
+ id: string;
+ media: { [type in keyof typeof TenorMediaTypes]: TenorMedia }[];
+ tags: string[];
+ title: string;
+ itemurl: string;
+ hascaption: boolean;
+ url: string;
+};
+
+export type TenorCategory = {
+ searchterm: string;
+ path: string;
+ image: string;
+ name: string;
+};
+
+export type TenorCategoriesResults = {
+ tags: TenorCategory[];
+};
+
+export type TenorTrendingResults = {
+ next: string;
+ results: TenorGif[];
+ locale: string;
+};
+
+export type TenorSearchResults = {
+ next: string;
+ results: TenorGif[];
+};
+
+export interface TenorGifResponse {
+ id: string;
+ title: string;
+ url: string;
+ src: string;
+ gif_src: string;
+ width: number;
+ height: number;
+ preview: string;
+}
+
+export interface TenorTrendingResponse {
+ categories: TenorCategoriesResults;
+ gifs: TenorGifResponse[];
+}
+
+export type TenorGifsResponse = TenorGifResponse[];
diff --git a/src/util/schemas/responses/TokenResponse.ts b/src/util/schemas/responses/TokenResponse.ts
new file mode 100644
index 00000000..7e93055a
--- /dev/null
+++ b/src/util/schemas/responses/TokenResponse.ts
@@ -0,0 +1,15 @@
+import { BackupCode, UserSettings } from "../../entities";
+
+export interface TokenResponse {
+ token: string;
+ settings: UserSettings;
+}
+
+export interface TokenOnlyResponse {
+ token: string;
+}
+
+export interface TokenWithBackupCodesResponse {
+ token: string;
+ backup_codes: BackupCode[];
+}
diff --git a/src/util/schemas/responses/TypedResponses.ts b/src/util/schemas/responses/TypedResponses.ts
new file mode 100644
index 00000000..099efba3
--- /dev/null
+++ b/src/util/schemas/responses/TypedResponses.ts
@@ -0,0 +1,86 @@
+import { GeneralConfiguration, LimitsConfiguration } from "../../config";
+import { DmChannelDTO } from "../../dtos";
+import {
+ Application,
+ BackupCode,
+ Categories,
+ Channel,
+ Emoji,
+ Guild,
+ Invite,
+ Member,
+ Message,
+ PrivateUser,
+ PublicUser,
+ Role,
+ Sticker,
+ StickerPack,
+ Template,
+ Webhook,
+} from "../../entities";
+import { GuildVoiceRegion } from "./GuildVoiceRegionsResponse";
+
+// removes internal properties from the guild class
+export type APIGuild = Omit<
+ Guild,
+ | "afk_channel"
+ | "template"
+ | "owner"
+ | "public_updates_channel"
+ | "rules_channel"
+ | "system_channel"
+ | "widget_channel"
+>;
+
+export type APIPublicUser = PublicUser;
+export type APIPrivateUser = PrivateUser;
+
+export type APIGuildArray = APIGuild[];
+
+export type APIDMChannelArray = DmChannelDTO[];
+
+export type APIBackupCodeArray = BackupCode[];
+
+export interface UserUpdateResponse extends APIPrivateUser {
+ newToken?: string;
+}
+
+export type ApplicationDetectableResponse = unknown[];
+
+export type ApplicationEntitlementsResponse = unknown[];
+
+export type ApplicationSkusResponse = unknown[];
+
+export type APIApplicationArray = Application[];
+
+export type APIInviteArray = Invite[];
+
+export type APIMessageArray = Message[];
+
+export type APIWebhookArray = Webhook[];
+
+export type APIDiscoveryCategoryArray = Categories[];
+
+export type APIGeneralConfiguration = GeneralConfiguration;
+
+export type APIChannelArray = Channel[];
+
+export type APIEmojiArray = Emoji[];
+
+export type APIMemberArray = Member[];
+
+export interface APIGuildWithJoinedAt extends Guild {
+ joined_at: string;
+}
+
+export type APIRoleArray = Role[];
+
+export type APIStickerArray = Sticker[];
+
+export type APITemplateArray = Template[];
+
+export type APIGuildVoiceRegion = GuildVoiceRegion[];
+
+export type APILimitsConfiguration = LimitsConfiguration;
+
+export type APIStickerPackArray = StickerPack[];
diff --git a/src/util/schemas/responses/UpdatesResponse.ts b/src/util/schemas/responses/UpdatesResponse.ts
new file mode 100644
index 00000000..6b8566f4
--- /dev/null
+++ b/src/util/schemas/responses/UpdatesResponse.ts
@@ -0,0 +1,6 @@
+export interface UpdatesResponse {
+ name: string;
+ pub_date: string;
+ url: string;
+ notes: string | null;
+}
diff --git a/src/util/schemas/responses/UserNoteResponse.ts b/src/util/schemas/responses/UserNoteResponse.ts
new file mode 100644
index 00000000..b142811e
--- /dev/null
+++ b/src/util/schemas/responses/UserNoteResponse.ts
@@ -0,0 +1,5 @@
+export interface UserNoteResponse {
+ note: string;
+ note_user_id: string;
+ user_id: string;
+}
diff --git a/src/util/schemas/responses/UserProfileResponse.ts b/src/util/schemas/responses/UserProfileResponse.ts
new file mode 100644
index 00000000..bd1f46dd
--- /dev/null
+++ b/src/util/schemas/responses/UserProfileResponse.ts
@@ -0,0 +1,8 @@
+import { PublicConnectedAccount, PublicUser } from "../../entities";
+
+export interface UserProfileResponse {
+ user: PublicUser;
+ connected_accounts: PublicConnectedAccount;
+ premium_guild_since?: Date;
+ premium_since?: Date;
+}
diff --git a/src/util/schemas/responses/UserRelationsResponse.ts b/src/util/schemas/responses/UserRelationsResponse.ts
new file mode 100644
index 00000000..e784cafb
--- /dev/null
+++ b/src/util/schemas/responses/UserRelationsResponse.ts
@@ -0,0 +1,7 @@
+import { User } from "@spacebar/util";
+
+export type UserRelationsResponse = (Pick<User, "id"> &
+ Pick<User, "username"> &
+ Pick<User, "discriminator"> &
+ Pick<User, "avatar"> &
+ Pick<User, "public_flags">)[];
diff --git a/src/util/schemas/responses/UserRelationshipsResponse.ts b/src/util/schemas/responses/UserRelationshipsResponse.ts
new file mode 100644
index 00000000..dff2f118
--- /dev/null
+++ b/src/util/schemas/responses/UserRelationshipsResponse.ts
@@ -0,0 +1,8 @@
+import { PublicUser, RelationshipType } from "../../entities";
+
+export interface UserRelationshipsResponse {
+ id: string;
+ type: RelationshipType;
+ nickname: null;
+ user: PublicUser;
+}
diff --git a/src/util/schemas/responses/WebAuthnCreateResponse.ts b/src/util/schemas/responses/WebAuthnCreateResponse.ts
new file mode 100644
index 00000000..9aa9e206
--- /dev/null
+++ b/src/util/schemas/responses/WebAuthnCreateResponse.ts
@@ -0,0 +1,4 @@
+export interface WebAuthnCreateResponse {
+ name: string;
+ id: string;
+}
diff --git a/src/util/schemas/responses/WebhookCreateResponse.ts b/src/util/schemas/responses/WebhookCreateResponse.ts
new file mode 100644
index 00000000..ae142632
--- /dev/null
+++ b/src/util/schemas/responses/WebhookCreateResponse.ts
@@ -0,0 +1,6 @@
+import { User, Webhook } from "../../entities";
+
+export interface WebhookCreateResponse {
+ user: User;
+ hook: Webhook;
+}
diff --git a/src/util/schemas/responses/index.ts b/src/util/schemas/responses/index.ts
new file mode 100644
index 00000000..d8b7fd57
--- /dev/null
+++ b/src/util/schemas/responses/index.ts
@@ -0,0 +1,34 @@
+export * from "./APIErrorOrCaptchaResponse";
+export * from "./APIErrorResponse";
+export * from "./BackupCodesChallengeResponse";
+export * from "./CaptchaRequiredResponse";
+export * from "./DiscoverableGuildsResponse";
+export * from "./GatewayBotResponse";
+export * from "./GatewayResponse";
+export * from "./GenerateRegistrationTokensResponse";
+export * from "./GuildBansResponse";
+export * from "./GuildCreateResponse";
+export * from "./GuildDiscoveryRequirements";
+export * from "./GuildMessagesSearchResponse";
+export * from "./GuildPruneResponse";
+export * from "./GuildRecommendationsResponse";
+export * from "./GuildVanityUrl";
+export * from "./GuildVoiceRegionsResponse";
+export * from "./GuildWidgetJsonResponse";
+export * from "./GuildWidgetSettingsResponse";
+export * from "./InstanceDomainsResponse";
+export * from "./InstancePingResponse";
+export * from "./InstanceStatsResponse";
+export * from "./LocationMetadataResponse";
+export * from "./MemberJoinGuildResponse";
+export * from "./OAuthAuthorizeResponse";
+export * from "./Tenor";
+export * from "./TokenResponse";
+export * from "./TypedResponses";
+export * from "./UpdatesResponse";
+export * from "./UserNoteResponse";
+export * from "./UserProfileResponse";
+export * from "./UserRelationshipsResponse";
+export * from "./UserRelationsResponse";
+export * from "./WebAuthnCreateResponse";
+export * from "./WebhookCreateResponse";
diff --git a/src/util/util/Gifs.ts b/src/util/util/Gifs.ts
new file mode 100644
index 00000000..a5a5e64c
--- /dev/null
+++ b/src/util/util/Gifs.ts
@@ -0,0 +1,25 @@
+import { HTTPError } from "lambert-server";
+import { Config } from "./Config";
+import { TenorGif } from "..";
+
+export function parseGifResult(result: TenorGif) {
+ 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;
+}
diff --git a/src/util/util/index.ts b/src/util/util/index.ts
index 838239b7..3a98be15 100644
--- a/src/util/util/index.ts
+++ b/src/util/util/index.ts
@@ -41,3 +41,4 @@ export * from "./String";
export * from "./Token";
export * from "./TraverseDirectory";
export * from "./WebAuthn";
+export * from "./Gifs";
|