From 9fda915b376a2f3853a26aa4c55e41b7a42d6a3a Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sat, 21 Aug 2021 16:47:22 +0200 Subject: :construction: typeorm --- api/src/routes/users/@me/settings.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'api/src') diff --git a/api/src/routes/users/@me/settings.ts b/api/src/routes/users/@me/settings.ts index 5664fc2f..f045a010 100644 --- a/api/src/routes/users/@me/settings.ts +++ b/api/src/routes/users/@me/settings.ts @@ -8,7 +8,8 @@ const router = Router(); router.patch("/", check(UserSettingsSchema), async (req: Request, res: Response) => { const body = req.body as UserSettings; - await UserModel.updateOne({ id: req.user_id }, body).exec(); + // only users can update user settings + await UserModel.updateOne({ id: req.user_id, bot: false }, body).exec(); res.sendStatus(204); }); -- cgit 1.5.1 From ef4d4a318176c3e572adc17427a8b8c728a618ab Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 24 Aug 2021 16:35:04 +0200 Subject: :construction: api --- api/src/middlewares/RateLimit.ts | 6 +-- api/src/routes/auth/login.ts | 26 +++++----- api/src/routes/auth/register.ts | 18 +++---- api/src/routes/channels/#channel_id/index.ts | 16 +++--- api/src/routes/channels/#channel_id/invites.ts | 10 ++-- .../#channel_id/messages/#message_id/ack.ts | 7 +-- .../#channel_id/messages/#message_id/index.ts | 16 +++--- .../#channel_id/messages/#message_id/reactions.ts | 40 +++++++-------- .../channels/#channel_id/messages/bulk-delete.ts | 6 +-- .../routes/channels/#channel_id/messages/index.ts | 42 +++++++--------- api/src/routes/channels/#channel_id/permissions.ts | 25 +++------- api/src/routes/channels/#channel_id/pins.ts | 29 ++++------- api/src/routes/channels/#channel_id/typing.ts | 8 +-- api/src/routes/channels/#channel_id/webhooks.ts | 4 +- api/src/routes/guilds/#guild_id/bans.ts | 18 +++---- api/src/routes/guilds/#guild_id/channels.ts | 17 +++---- api/src/routes/guilds/#guild_id/delete.ts | 31 ++++-------- api/src/routes/guilds/#guild_id/index.ts | 29 +++++------ api/src/routes/guilds/#guild_id/invites.ts | 4 +- .../guilds/#guild_id/members/#member_id/index.ts | 20 ++++---- api/src/routes/guilds/#guild_id/members/index.ts | 11 ++-- api/src/routes/guilds/#guild_id/roles.ts | 36 +++++++------- api/src/routes/guilds/#guild_id/templates.ts | 34 ++++++------- api/src/routes/guilds/#guild_id/vanity-url.ts | 24 ++++----- api/src/routes/guilds/#guild_id/welcome_screen.ts | 12 ++--- api/src/routes/guilds/#guild_id/widget.json.ts | 12 ++--- api/src/routes/guilds/#guild_id/widget.png.ts | 4 +- api/src/routes/guilds/#guild_id/widget.ts | 6 +-- api/src/routes/guilds/index.ts | 6 +-- api/src/routes/guilds/templates/index.ts | 12 ++--- api/src/routes/invites/index.ts | 24 ++++----- api/src/routes/users/#id/profile.ts | 34 ++++++------- api/src/routes/users/@me/channels.ts | 16 +++--- api/src/routes/users/@me/delete.ts | 10 ++-- api/src/routes/users/@me/disable.ts | 8 +-- api/src/routes/users/@me/guilds.ts | 17 +++---- api/src/routes/users/@me/index.ts | 6 +-- api/src/routes/users/@me/profile.ts | 34 ++++++------- api/src/routes/users/@me/relationships.ts | 40 +++++++-------- api/src/routes/users/@me/settings.ts | 4 +- api/src/test/mongo_test.ts | 10 ++-- api/src/util/Channel.ts | 10 ++-- api/src/util/Member.ts | 58 +++++++++++----------- api/src/util/Message.ts | 27 +++++----- api/src/util/User.ts | 8 +-- 45 files changed, 381 insertions(+), 454 deletions(-) (limited to 'api/src') diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index acf92606..9601dad3 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -72,7 +72,7 @@ export default function RateLimit(opts: { offender.expires_at = new Date(Date.now() + opts.window * 1000); offender.blocked = false; // mongodb ttl didn't update yet -> manually update/delete - db.collection("ratelimits").updateOne({ id: bucket_id, user_id }, { $set: offender }); + db.collection("ratelimits").update({ id: bucket_id, user_id }, { $set: offender }); Cache.delete(user_id + bucket_id); } } @@ -132,7 +132,7 @@ export async function initRateLimits(app: Router) { async function hitRoute(opts: { user_id: string; bucket_id: string; max_hits: number; window: number }) { const filter = { id: opts.bucket_id, user_id: opts.user_id }; - const { value } = await db.collection("ratelimits").findOneAndUpdate( + const { value } = await db.collection("ratelimits").findOneOrFailAndUpdate( filter, { $setOnInsert: { @@ -158,7 +158,7 @@ async function hitRoute(opts: { user_id: string; bucket_id: string; max_hits: nu event: EventRateLimit, data: value }); - await db.collection("ratelimits").updateOne(filter, { $set: { blocked: true } }); + await db.collection("ratelimits").update(filter, { $set: { blocked: true } }); } else { Cache.delete(opts.user_id); } diff --git a/api/src/routes/auth/login.ts b/api/src/routes/auth/login.ts index dc970e4c..579a097e 100644 --- a/api/src/routes/auth/login.ts +++ b/api/src/routes/auth/login.ts @@ -2,7 +2,7 @@ import { Request, Response, Router } from "express"; import { check, FieldErrors, Length } from "../../util/instanceOf"; import bcrypt from "bcrypt"; import jwt from "jsonwebtoken"; -import { Config, UserModel } from "@fosscord/util"; +import { Config, User } from "@fosscord/util"; import { adjustEmail } from "./register"; const router: Router = Router(); @@ -41,27 +41,25 @@ router.post( // TODO: check captcha } - const user = await UserModel.findOne( + const user = await User.findOneOrFail( { $or: query }, - { "user_data.hash": true, id: true, disabled: true, deleted: true, "user_settings.locale": true, "user_settings.theme": true } - ) - .exec() - .catch((e) => { - console.log(e, query); - throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } }); - }); + { "data.hash": true, id: true, disabled: true, deleted: true, "settings.locale": true, "settings.theme": true } + ).catch((e) => { + console.log(e, query); + throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } }); + }); if (undelete) { // undelete refers to un'disable' here - if (user.disabled) await UserModel.updateOne({ id: user.id }, { disabled: false }).exec(); - if (user.deleted) await UserModel.updateOne({ id: user.id }, { deleted: false }).exec(); + if (user.disabled) await User.update({ id: user.id }, { disabled: false }); + if (user.deleted) await User.update({ id: user.id }, { deleted: false }); } else { if (user.deleted) return res.status(400).json({ message: "This account is scheduled for deletion.", code: 20011 }); if (user.disabled) return res.status(400).json({ message: req.t("auth:login.ACCOUNT_DISABLED"), code: 20013 }); } // the salt is saved in the password refer to bcrypt docs - const same_password = await bcrypt.compare(password, user.user_data.hash || ""); + const same_password = await bcrypt.compare(password, user.data.hash || ""); if (!same_password) { throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); } @@ -72,7 +70,7 @@ router.post( // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png - res.json({ token, user_settings: user.user_settings }); + res.json({ token, settings: user.settings }); } ); @@ -106,6 +104,6 @@ export async function generateToken(id: string) { * @returns {"captcha_key": ["captcha-required"], "captcha_sitekey": null, "captcha_service": "recaptcha"} * Sucess: - * @returns {"token": "USERTOKEN", "user_settings": {"locale": "en", "theme": "dark"}} + * @returns {"token": "USERTOKEN", "settings": {"locale": "en", "theme": "dark"}} */ diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index fecde874..1405e219 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { trimSpecial, User, Snowflake, UserModel, Config } from "@fosscord/util"; +import { trimSpecial, User, Snowflake, User, Config } from "@fosscord/util"; import bcrypt from "bcrypt"; import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "../../util/instanceOf"; import "missing-native-js-functions"; @@ -92,9 +92,7 @@ router.post( if (!adjusted_email) throw FieldErrors({ email: { code: "INVALID_EMAIL", message: req.t("auth:register.INVALID_EMAIL") } }); // check if there is already an account with this email - const exists = await UserModel.findOne({ email: adjusted_email }) - .exec() - .catch((e) => {}); + const exists = await User.findOneOrFail({ email: adjusted_email }).catch((e) => {}); if (exists) { throw FieldErrors({ @@ -131,9 +129,7 @@ router.post( if (!register.allowMultipleAccounts) { // TODO: check if fingerprint was eligible generated - const exists = await UserModel.findOne({ fingerprints: fingerprint }) - .exec() - .catch((e) => {}); + const exists = await User.findOneOrFail({ fingerprints: fingerprint }).catch((e) => {}); if (exists) { throw FieldErrors({ @@ -169,7 +165,7 @@ router.post( for (let tries = 0; tries < 5; tries++) { discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); try { - exists = await UserModel.findOne({ discriminator, username: adjusted_username }, "id").exec(); + exists = await User.findOneOrFail({ discriminator, username: adjusted_username }, "id"); } catch (error) { // doesn't exist -> break break; @@ -223,14 +219,14 @@ router.post( public_flags: 0n, flags: 0n, // TODO: generate default flags guilds: [], - user_data: { + data: { hash: adjusted_password, valid_tokens_since: new Date(), relationships: [], connected_accounts: [], fingerprints: [] }, - user_settings: { + settings: { afk_timeout: 300, allow_accessibility_detection: true, animate_emoji: true, @@ -272,7 +268,7 @@ router.post( }; // insert user into database - await new UserModel(user).save(); + await new User(user).save(); return res.json({ token: await generateToken(user.id) }); } diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index fb6bcb1a..91534602 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,4 +1,4 @@ -import { ChannelDeleteEvent, ChannelModel, ChannelUpdateEvent, emitEvent, getPermission, GuildUpdateEvent, toObject } from "@fosscord/util"; +import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, getPermission, GuildUpdateEvent, toObject } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; import { ChannelModifySchema } from "../../../schema/Channel"; @@ -10,28 +10,28 @@ const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { const { channel_id } = req.params; - const channel = await ChannelModel.findOne({ id: channel_id }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }); const permission = await getPermission(req.user_id, channel.guild_id, channel_id); permission.hasThrow("VIEW_CHANNEL"); - return res.send(toObject(channel)); + return res.send(channel); }); router.delete("/", async (req: Request, res: Response) => { const { channel_id } = req.params; - const channel = await ChannelModel.findOne({ id: channel_id }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }); const permission = await getPermission(req.user_id, channel?.guild_id, channel_id, { channel }); permission.hasThrow("MANAGE_CHANNELS"); // TODO: Dm channel "close" not delete - const data = toObject(channel); + const data = channel; await emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent); - await ChannelModel.deleteOne({ id: channel_id }); + await Channel.deleteOne({ id: channel_id }); res.send(data); }); @@ -43,9 +43,9 @@ router.patch("/", check(ChannelModifySchema), async (req: Request, res: Response const permission = await getPermission(req.user_id, undefined, channel_id); permission.hasThrow("MANAGE_CHANNELS"); - const channel = await ChannelModel.findOneAndUpdate({ id: channel_id }, payload, { new: true }).exec(); + const channel = await Channel.findOneOrFailAndUpdate({ id: channel_id }, payload, { new: true }); - const data = toObject(channel); + const data = channel; await emitEvent({ event: "CHANNEL_UPDATE", diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index 438f8c51..cc77df26 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -6,14 +6,14 @@ import { random } from "../../../util/RandomInviteID"; import { InviteCreateSchema } from "../../../schema/Invite"; -import { getPermission, ChannelModel, InviteModel, InviteCreateEvent, toObject, emitEvent } from "@fosscord/util"; +import { getPermission, Channel, InviteModel, InviteCreateEvent, toObject, emitEvent } from "@fosscord/util"; const router: Router = Router(); router.post("/", check(InviteCreateSchema), async (req: Request, res: Response) => { const { user_id } = req; const { channel_id } = req.params; - const channel = await ChannelModel.findOne({ id: channel_id }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) { throw new HTTPError("This channel doesn't exist", 404); @@ -47,7 +47,7 @@ router.post("/", check(InviteCreateSchema), async (req: Request, res: Response) router.get("/", async (req: Request, res: Response) => { const { user_id } = req; const { channel_id } = req.params; - const channel = await ChannelModel.findOne({ id: channel_id }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) { throw new HTTPError("This channel doesn't exist", 404); @@ -56,9 +56,9 @@ router.get("/", async (req: Request, res: Response) => { const permission = await getPermission(user_id, guild_id); permission.hasThrow("MANAGE_CHANNELS"); - const invites = await InviteModel.find({ guild_id }).exec(); + const invites = await Invite.find({ guild_id }); - res.status(200).send(toObject(invites)); + res.status(200).send(invites); }); export default router; diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts index bbc779dd..ff576d90 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts @@ -1,4 +1,4 @@ -import { emitEvent, getPermission, MessageAckEvent, ReadStateModel } from "@fosscord/util"; +import { emitEvent, getPermission, MessageAckEvent, ReadState } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { check } from "../../../../../util/instanceOf"; @@ -14,10 +14,7 @@ router.post("/", check({ $manual: Boolean, $mention_count: Number }), async (req const permission = await getPermission(req.user_id, undefined, channel_id); permission.hasThrow("VIEW_CHANNEL"); - await ReadStateModel.updateOne( - { user_id: req.user_id, channel_id, message_id }, - { user_id: req.user_id, channel_id, message_id } - ).exec(); + await ReadState.update({ user_id: req.user_id, channel_id, message_id }, { user_id: req.user_id, channel_id, message_id }); await emitEvent({ event: "MESSAGE_ACK", diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index 35952d26..058e2390 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -1,4 +1,4 @@ -import { ChannelModel, emitEvent, getPermission, MessageDeleteEvent, MessageModel, MessageUpdateEvent, toObject } from "@fosscord/util"; +import { Channel, emitEvent, getPermission, MessageDeleteEvent, Message, MessageUpdateEvent, toObject } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; import { MessageCreateSchema } from "../../../../../schema/Message"; @@ -12,7 +12,7 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response const { message_id, channel_id } = req.params; var body = req.body as MessageCreateSchema; - var message = await MessageModel.findOne({ id: message_id, channel_id }, { author_id: true, message_reference: true }).lean().exec(); + var message = await Message.findOneOrFail({ id: message_id, channel_id }, { author_id: true, message_reference: true }); const permissions = await getPermission(req.user_id, undefined, channel_id); @@ -31,17 +31,17 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response }); // @ts-ignore - message = await MessageModel.findOneAndUpdate({ id: message_id }, opts, { new: true }).populate("author").exec(); + message = await Message.findOneOrFailAndUpdate({ id: message_id }, opts, { new: true }).populate("author"); await emitEvent({ event: "MESSAGE_UPDATE", channel_id, - data: { ...toObject(message), nonce: undefined } + data: { ...message, nonce: undefined } } as MessageUpdateEvent); postHandleMessage(message); - return res.json(toObject(message)); + return res.json(message); }); // TODO: delete attachments in message @@ -49,13 +49,13 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response router.delete("/", async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; - const channel = await ChannelModel.findOne({ id: channel_id }, { guild_id: true }); - const message = await MessageModel.findOne({ id: message_id }, { author_id: true }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); + const message = await Message.findOneOrFail({ id: message_id }, { author_id: true }); const permission = await getPermission(req.user_id, channel.guild_id, channel_id); if (message.author_id !== req.user_id) permission.hasThrow("MANAGE_MESSAGES"); - await MessageModel.deleteOne({ id: message_id }).exec(); + await Message.deleteOne({ id: message_id }); await emitEvent({ event: "MESSAGE_DELETE", diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts index 7da63644..9a6e4436 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts @@ -1,10 +1,10 @@ import { - ChannelModel, + Channel, emitEvent, EmojiModel, getPermission, - MemberModel, - MessageModel, + Member, + Message, MessageReactionAddEvent, MessageReactionRemoveAllEvent, MessageReactionRemoveEmojiEvent, @@ -12,7 +12,7 @@ import { PartialEmoji, PublicUserProjection, toObject, - UserModel + User } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; @@ -38,12 +38,12 @@ function getEmoji(emoji: string): PartialEmoji { router.delete("/", async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; - const channel = await ChannelModel.findOne({ id: channel_id }, { guild_id: true }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); const permissions = await getPermission(req.user_id, undefined, channel_id); permissions.hasThrow("MANAGE_MESSAGES"); - await MessageModel.findOneAndUpdate({ id: message_id, channel_id }, { reactions: [] }, { new: true }).exec(); + await Message.findOneOrFailAndUpdate({ id: message_id, channel_id }, { reactions: [] }, { new: true }); await emitEvent({ event: "MESSAGE_REACTION_REMOVE_ALL", @@ -62,18 +62,18 @@ router.delete("/:emoji", async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; const emoji = getEmoji(req.params.emoji); - const channel = await ChannelModel.findOne({ id: channel_id }, { guild_id: true }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); const permissions = await getPermission(req.user_id, undefined, channel_id); permissions.hasThrow("MANAGE_MESSAGES"); - const message = await MessageModel.findOne({ id: message_id, channel_id }).exec(); + const message = await Message.findOneOrFail({ id: message_id, channel_id }); const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); if (!already_added) throw new HTTPError("Reaction not found", 404); message.reactions.remove(already_added); - await MessageModel.updateOne({ id: message_id, channel_id }, message).exec(); + await Message.update({ id: message_id, channel_id }, message); await emitEvent({ event: "MESSAGE_REACTION_REMOVE_EMOJI", @@ -93,7 +93,7 @@ router.get("/:emoji", async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; const emoji = getEmoji(req.params.emoji); - const message = await MessageModel.findOne({ id: message_id, channel_id }).exec(); + const message = await Message.findOneOrFail({ id: message_id, channel_id }); if (!message) throw new HTTPError("Message not found", 404); const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); if (!reaction) throw new HTTPError("Reaction not found", 404); @@ -101,9 +101,9 @@ router.get("/:emoji", async (req: Request, res: Response) => { const permissions = await getPermission(req.user_id, undefined, channel_id); permissions.hasThrow("VIEW_CHANNEL"); - const users = await UserModel.find({ id: { $in: reaction.user_ids } }, PublicUserProjection).exec(); + const users = await User.find({ id: { $in: reaction.user_ids } }, PublicUserProjection); - res.json(toObject(users)); + res.json(users); }); router.put("/:emoji/:user_id", async (req: Request, res: Response) => { @@ -111,8 +111,8 @@ router.put("/:emoji/:user_id", async (req: Request, res: Response) => { if (user_id !== "@me") throw new HTTPError("Invalid user"); const emoji = getEmoji(req.params.emoji); - const channel = await ChannelModel.findOne({ id: channel_id }, { guild_id: true }).exec(); - const message = await MessageModel.findOne({ id: message_id, channel_id }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); + const message = await Message.findOneOrFail({ id: message_id, channel_id }); const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); const permissions = await getPermission(req.user_id, undefined, channel_id); @@ -120,7 +120,7 @@ router.put("/:emoji/:user_id", async (req: Request, res: Response) => { if (!already_added) permissions.hasThrow("ADD_REACTIONS"); if (emoji.id) { - const external_emoji = await EmojiModel.findOne({ id: emoji.id }).exec(); + const external_emoji = await Emoji.findOneOrFail({ id: emoji.id }); if (!already_added) permissions.hasThrow("USE_EXTERNAL_EMOJIS"); emoji.animated = external_emoji.animated; emoji.name = external_emoji.name; @@ -131,9 +131,9 @@ router.put("/:emoji/:user_id", async (req: Request, res: Response) => { already_added.count++; } else message.reactions.push({ count: 1, emoji, user_ids: [req.user_id] }); - await MessageModel.updateOne({ id: message_id, channel_id }, message).exec(); + await Message.update({ id: message_id, channel_id }, message); - const member = channel.guild_id && (await MemberModel.findOne({ id: req.user_id }).exec()); + const member = channel.guild_id && (await Member.findOneOrFail({ id: req.user_id })); await emitEvent({ event: "MESSAGE_REACTION_ADD", @@ -156,8 +156,8 @@ router.delete("/:emoji/:user_id", async (req: Request, res: Response) => { const emoji = getEmoji(req.params.emoji); - const channel = await ChannelModel.findOne({ id: channel_id }, { guild_id: true }).exec(); - const message = await MessageModel.findOne({ id: message_id, channel_id }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); + const message = await Message.findOneOrFail({ id: message_id, channel_id }); const permissions = await getPermission(req.user_id, undefined, channel_id); @@ -171,7 +171,7 @@ router.delete("/:emoji/:user_id", async (req: Request, res: Response) => { if (already_added.count <= 0) message.reactions.remove(already_added); - await MessageModel.updateOne({ id: message_id, channel_id }, message).exec(); + await Message.update({ id: message_id, channel_id }, message); await emitEvent({ event: "MESSAGE_REACTION_REMOVE", diff --git a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts index 8132462f..028a5d73 100644 --- a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { ChannelModel, Config, emitEvent, getPermission, MessageDeleteBulkEvent, MessageModel } from "@fosscord/util"; +import { Channel, Config, emitEvent, getPermission, MessageDeleteBulkEvent, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { check } from "../../../../util/instanceOf"; @@ -13,7 +13,7 @@ export default router; // https://discord.com/developers/docs/resources/channel#bulk-delete-messages router.post("/", check({ messages: [String] }), async (req: Request, res: Response) => { const { channel_id } = req.params; - const channel = await ChannelModel.findOne({ id: channel_id }, { permission_overwrites: true, guild_id: true }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }, { permission_overwrites: true, guild_id: true }); if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400); const permission = await getPermission(req.user_id, channel?.guild_id, channel_id, { channel }); @@ -25,7 +25,7 @@ router.post("/", check({ messages: [String] }), async (req: Request, res: Respon if (messages.length < 2) throw new HTTPError("You must at least specify 2 messages to bulk delete"); if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`); - await MessageModel.deleteMany({ id: { $in: messages } }).exec(); + await Message.deleteMany({ id: { $in: messages } }); await emitEvent({ event: "MESSAGE_DELETE_BULK", diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index 6ae6491f..b5c650bb 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { Attachment, ChannelModel, ChannelType, getPermission, MessageDocument, MessageModel, toObject } from "@fosscord/util"; +import { Attachment, Channel, ChannelType, getPermission, MessageDocument, Message, toObject } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { MessageCreateSchema } from "../../../../schema/Message"; import { check, instanceOf, Length } from "../../../../util/instanceOf"; @@ -30,12 +30,10 @@ export function isTextChannel(type: ChannelType): boolean { // get messages router.get("/", async (req: Request, res: Response) => { const channel_id = req.params.channel_id; - const channel = await ChannelModel.findOne( + const channel = await Channel.findOneOrFail( { id: channel_id }, - { guild_id: true, type: true, permission_overwrites: true, recipient_ids: true, owner_id: true } - ) - .lean() // lean is needed, because we don't want to populate .recipients that also auto deletes .recipient_ids - .exec(); + { select: ["guild_id", "type", "permission_overwrites", "recipient_ids", "owner_id"] } + ); // lean is needed, because we don't want to populate .recipients that also auto deletes .recipient_ids if (!channel) throw new HTTPError("Channel not found", 404); isTextChannel(channel.type); @@ -58,35 +56,33 @@ router.get("/", async (req: Request, res: Response) => { if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); var query: Query; - if (after) query = MessageModel.find({ channel_id, id: { $gt: after } }); - else if (before) query = MessageModel.find({ channel_id, id: { $lt: before } }); + if (after) query = Message.find({ channel_id, id: { $gt: after } }); + else if (before) query = Message.find({ channel_id, id: { $lt: before } }); else if (around) - query = MessageModel.find({ + query = Message.find({ channel_id, id: { $gt: (BigInt(around) - BigInt(halfLimit)).toString(), $lt: (BigInt(around) + BigInt(halfLimit)).toString() } }); else { - query = MessageModel.find({ channel_id }); + query = Message.find({ channel_id }); } query = query.sort({ id: -1 }); - const messages = await query.limit(limit).exec(); + const messages = await query.limit(limit); - return res.json( - toObject(messages).map((x) => { - (x.reactions || []).forEach((x) => { - // @ts-ignore - if ((x.user_ids || []).includes(req.user_id)) x.me = true; - // @ts-ignore - delete x.user_ids; - }); + return res.json(messages).map((x) => { + (x.reactions || []).forEach((x) => { // @ts-ignore - if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: 0n, avatar: null }; + if ((x.user_ids || []).includes(req.user_id)) x.me = true; + // @ts-ignore + delete x.user_ids; + }); + // @ts-ignore + if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: 0n, avatar: null }; - return x; - }) - ); + return x; + }); }); // TODO: config max upload size diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts index f93075b1..2990e35b 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/api/src/routes/channels/#channel_id/permissions.ts @@ -1,13 +1,4 @@ -import { - ChannelModel, - ChannelPermissionOverwrite, - ChannelUpdateEvent, - emitEvent, - getPermission, - MemberModel, - RoleModel, - toObject -} from "@fosscord/util"; +import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, getPermission, Member, Role, toObject } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; @@ -20,16 +11,16 @@ router.put("/:overwrite_id", check({ allow: String, deny: String, type: Number, const { channel_id, overwrite_id } = req.params; const body = req.body as { allow: bigint; deny: bigint; type: number; id: string }; - var channel = await ChannelModel.findOne({ id: channel_id }, { guild_id: true, permission_overwrites: true }).exec(); + var channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true, permission_overwrites: true }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); permissions.hasThrow("MANAGE_ROLES"); if (body.type === 0) { - if (!(await RoleModel.exists({ id: overwrite_id }))) throw new HTTPError("role not found", 404); + if (!(await Role.exists({ id: overwrite_id }))) throw new HTTPError("role not found", 404); } else if (body.type === 1) { - if (!(await MemberModel.exists({ id: overwrite_id }))) throw new HTTPError("user not found", 404); + if (!(await Member.exists({ id: overwrite_id }))) throw new HTTPError("user not found", 404); } else throw new HTTPError("type not supported", 501); // @ts-ignore @@ -48,12 +39,12 @@ router.put("/:overwrite_id", check({ allow: String, deny: String, type: Number, overwrite.deny = body.deny; // @ts-ignore - channel = await ChannelModel.findOneAndUpdate({ id: channel_id }, channel, { new: true }).exec(); + channel = await Channel.findOneOrFailAndUpdate({ id: channel_id }, channel, { new: true }); await emitEvent({ event: "CHANNEL_UPDATE", channel_id, - data: toObject(channel) + data: channel } as ChannelUpdateEvent); return res.sendStatus(204); @@ -66,7 +57,7 @@ router.delete("/:overwrite_id", async (req: Request, res: Response) => { const permissions = await getPermission(req.user_id, undefined, channel_id); permissions.hasThrow("MANAGE_ROLES"); - const channel = await ChannelModel.findOneAndUpdate( + const channel = await Channel.findOneOrFailAndUpdate( { id: channel_id }, { $pull: { permission_overwrites: { id: overwrite_id } } }, { new: true } @@ -76,7 +67,7 @@ router.delete("/:overwrite_id", async (req: Request, res: Response) => { await emitEvent({ event: "CHANNEL_UPDATE", channel_id, - data: toObject(channel) + data: channel } as ChannelUpdateEvent); return res.sendStatus(204); diff --git a/api/src/routes/channels/#channel_id/pins.ts b/api/src/routes/channels/#channel_id/pins.ts index 0dd81bd3..3ed42ab4 100644 --- a/api/src/routes/channels/#channel_id/pins.ts +++ b/api/src/routes/channels/#channel_id/pins.ts @@ -1,13 +1,4 @@ -import { - ChannelModel, - ChannelPinsUpdateEvent, - Config, - emitEvent, - getPermission, - MessageModel, - MessageUpdateEvent, - toObject -} from "@fosscord/util"; +import { Channel, ChannelPinsUpdateEvent, Config, emitEvent, getPermission, Message, MessageUpdateEvent, toObject } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; @@ -15,19 +6,19 @@ const router: Router = Router(); router.put("/:message_id", async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; - const channel = await ChannelModel.findOne({ id: channel_id }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }); const permission = await getPermission(req.user_id, channel.guild_id, channel_id); permission.hasThrow("VIEW_CHANNEL"); // * in dm channels anyone can pin messages -> only check for guilds if (channel.guild_id) permission.hasThrow("MANAGE_MESSAGES"); - const pinned_count = await MessageModel.count({ channel_id, pinned: true }).exec(); + const pinned_count = await Messagecount({ channel_id, pinned: true }); const { maxPins } = Config.get().limits.channel; if (pinned_count >= maxPins) throw new HTTPError("Max pin count reached: " + maxPins); - await MessageModel.updateOne({ id: message_id }, { pinned: true }).exec(); - const message = toObject(await MessageModel.findOne({ id: message_id }).exec()); + await Message.update({ id: message_id }, { pinned: true }); + const message = await Message.findOneOrFail({ id: message_id }); await emitEvent({ event: "MESSAGE_UPDATE", @@ -51,13 +42,13 @@ router.put("/:message_id", async (req: Request, res: Response) => { router.delete("/:message_id", async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; - const channel = await ChannelModel.findOne({ id: channel_id }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }); const permission = await getPermission(req.user_id, channel.guild_id, channel_id); permission.hasThrow("VIEW_CHANNEL"); if (channel.guild_id) permission.hasThrow("MANAGE_MESSAGES"); - const message = toObject(await MessageModel.findOneAndUpdate({ id: message_id }, { pinned: false }, { new: true }).exec()); + const message = await Message.findOneOrFailAndUpdate({ id: message_id }, { pinned: false }, { new: true }); await emitEvent({ event: "MESSAGE_UPDATE", @@ -81,13 +72,13 @@ router.delete("/:message_id", async (req: Request, res: Response) => { router.get("/", async (req: Request, res: Response) => { const { channel_id } = req.params; - const channel = await ChannelModel.findOne({ id: channel_id }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }); const permission = await getPermission(req.user_id, channel.guild_id, channel_id); permission.hasThrow("VIEW_CHANNEL"); - let pins = await MessageModel.find({ channel_id: channel_id, pinned: true }).exec(); + let pins = await Message.find({ channel_id: channel_id, pinned: true }); - res.send(toObject(pins)); + res.send(pins); }); export default router; diff --git a/api/src/routes/channels/#channel_id/typing.ts b/api/src/routes/channels/#channel_id/typing.ts index 21d453d8..298d2f50 100644 --- a/api/src/routes/channels/#channel_id/typing.ts +++ b/api/src/routes/channels/#channel_id/typing.ts @@ -1,4 +1,4 @@ -import { ChannelModel, emitEvent, MemberModel, toObject, TypingStartEvent } from "@fosscord/util"; +import { Channel, emitEvent, Member, toObject, TypingStartEvent } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; @@ -9,15 +9,15 @@ router.post("/", async (req: Request, res: Response) => { const { channel_id } = req.params; const user_id = req.user_id; const timestamp = Date.now(); - const channel = await ChannelModel.findOne({ id: channel_id }); - const member = await MemberModel.findOne({ id: user_id }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }); + const member = await Member.findOneOrFail({ id: user_id }); await emitEvent({ event: "TYPING_START", channel_id: channel_id, data: { // this is the paylod - member: toObject(member), + member: member, channel_id, timestamp, user_id, diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index 7852f8f3..38a8226b 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -1,6 +1,6 @@ import { Router, Response, Request } from "express"; import { check, Length } from "../../../util/instanceOf"; -import { ChannelModel, getPermission, trimSpecial } from "@fosscord/util"; +import { Channel, getPermission, trimSpecial } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { isTextChannel } from "./messages/index"; @@ -10,7 +10,7 @@ const router: Router = Router(); // TODO: use Image Data Type for avatar instead of String router.post("/", check({ name: new Length(String, 1, 80), $avatar: String }), async (req: Request, res: Response) => { const channel_id = req.params.channel_id; - const channel = await ChannelModel.findOne({ id: channel_id }, { guild_id: true, type: true }).exec(); + const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true, type: true }); isTextChannel(channel.type); if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400); diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index bb3eac03..cbc0b0fa 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { BanModel, emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, GuildModel, toObject } from "@fosscord/util"; +import { BanModel, emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, toObject } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { getIpAdress } from "../../../util/ipAddress"; import { BanCreateSchema } from "../../../schema/Ban"; @@ -13,18 +13,18 @@ const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await GuildModel.exists({ id: guild_id }); + const guild = await Guild.exists({ id: guild_id }); if (!guild) throw new HTTPError("Guild not found", 404); - var bans = await BanModel.find({ guild_id: guild_id }, { user_id: true, reason: true }).exec(); - return res.json(toObject(bans)); + var bans = await Ban.find({ guild_id: guild_id }, { user_id: true, reason: true }); + return res.json(bans); }); router.get("/:user", async (req: Request, res: Response) => { const { guild_id } = req.params; const user_id = req.params.ban; - var ban = await BanModel.findOne({ guild_id: guild_id, user_id: user_id }).exec(); + var ban = await Ban.findOneOrFail({ guild_id: guild_id, user_id: user_id }); return res.json(ban); }); @@ -56,7 +56,7 @@ router.put("/:user_id", check(BanCreateSchema), async (req: Request, res: Respon guild_id: guild_id } as GuildBanAddEvent); - return res.json(toObject(ban)); + return res.json(ban); }); router.delete("/:user_id", async (req: Request, res: Response) => { @@ -64,16 +64,16 @@ router.delete("/:user_id", async (req: Request, res: Response) => { var banned_user_id = req.params.user_id; const banned_user = await getPublicUser(banned_user_id); - const guild = await GuildModel.exists({ id: guild_id }); + const guild = await Guild.exists({ id: guild_id }); if (!guild) throw new HTTPError("Guild not found", 404); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("BAN_MEMBERS"); - await BanModel.deleteOne({ + await Ban.deleteOne({ user_id: banned_user_id, guild_id - }).exec(); + }); await emitEvent({ event: "GUILD_BAN_REMOVE", diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index 1c55ef24..b53c9a5a 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { ChannelModel, toObject, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; +import { Channel, toObject, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { ChannelModifySchema } from "../../../schema/Channel"; @@ -9,9 +9,9 @@ const router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; - const channels = await ChannelModel.find({ guild_id }).exec(); + const channels = await Channel.find({ guild_id }); - res.json(toObject(channels)); + res.json(channels); }); // TODO: check if channel type is permitted @@ -24,7 +24,7 @@ router.post("/", check(ChannelModifySchema), async (req: Request, res: Response) const channel = await createChannel({ ...body, guild_id }, req.user_id); - res.status(201).json(toObject(channel)); + res.status(201).json(channel); }); // TODO: check if parent_id exists @@ -48,18 +48,15 @@ router.patch( if (x.parent_id) { opts.parent_id = x.parent_id; - const parent_channel = await ChannelModel.findOne( - { id: x.parent_id, guild_id }, - { permission_overwrites: true } - ).exec(); + const parent_channel = await Channel.findOneOrFail({ id: x.parent_id, guild_id }, { permission_overwrites: true }); if (x.lock_permissions) { opts.permission_overwrites = parent_channel.permission_overwrites; } } - const channel = await ChannelModel.findOneAndUpdate({ id: x.id, guild_id }, opts, { new: true }).exec(); + const channel = await Channel.findOneOrFailAndUpdate({ id: x.id, guild_id }, opts, { new: true }); - await emitEvent({ event: "CHANNEL_UPDATE", data: toObject(channel), channel_id: x.id, guild_id } as ChannelUpdateEvent); + await emitEvent({ event: "CHANNEL_UPDATE", data: channel), channel_id: x.id, guild_id } as ChannelUpdateEvent; }) ]); diff --git a/api/src/routes/guilds/#guild_id/delete.ts b/api/src/routes/guilds/#guild_id/delete.ts index ba1c2fde..a53271ce 100644 --- a/api/src/routes/guilds/#guild_id/delete.ts +++ b/api/src/routes/guilds/#guild_id/delete.ts @@ -1,15 +1,4 @@ -import { - ChannelModel, - emitEvent, - EmojiModel, - GuildDeleteEvent, - GuildModel, - InviteModel, - MemberModel, - MessageModel, - RoleModel, - UserModel -} from "@fosscord/util"; +import { Channel, emitEvent, EmojiModel, GuildDeleteEvent, Guild, InviteModel, Member, Message, Role, User } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; @@ -20,7 +9,7 @@ const router = Router(); router.post("/", async (req: Request, res: Response) => { var { guild_id } = req.params; - const guild = await GuildModel.findOne({ id: guild_id }, "owner_id").exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }, "owner_id"); if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401); await emitEvent({ @@ -32,14 +21,14 @@ router.post("/", async (req: Request, res: Response) => { } as GuildDeleteEvent); await Promise.all([ - GuildModel.deleteOne({ id: guild_id }).exec(), - UserModel.updateMany({ guilds: guild_id }, { $pull: { guilds: guild_id } }).exec(), - RoleModel.deleteMany({ guild_id }).exec(), - ChannelModel.deleteMany({ guild_id }).exec(), - EmojiModel.deleteMany({ guild_id }).exec(), - InviteModel.deleteMany({ guild_id }).exec(), - MessageModel.deleteMany({ guild_id }).exec(), - MemberModel.deleteMany({ guild_id }).exec() + Guild.deleteOne({ id: guild_id }), + User.updateMany({ guilds: guild_id }, { $pull: { guilds: guild_id } }), + Role.deleteMany({ guild_id }), + Channel.deleteMany({ guild_id }), + Emoji.deleteMany({ guild_id }), + Invite.deleteMany({ guild_id }), + Message.deleteMany({ guild_id }), + Member.deleteMany({ guild_id }) ]); return res.sendStatus(204); diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index 87103caa..af9ea9d6 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -1,18 +1,18 @@ import { Request, Response, Router } from "express"; import { - ChannelModel, + Channel, emitEvent, EmojiModel, getPermission, GuildDeleteEvent, - GuildModel, + Guild, GuildUpdateEvent, InviteModel, - MemberModel, - MessageModel, - RoleModel, + Member, + Message, + Role, toObject, - UserModel + User } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { GuildUpdateSchema } from "../../../schema/Guild"; @@ -26,11 +26,8 @@ const router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await GuildModel.findOne({ id: guild_id }) - .populate({ path: "joined_at", match: { id: req.user_id } }) - .exec(); - - const member = await MemberModel.exists({ guild_id: guild_id, id: req.user_id }); + const guild = await Guild.findOneOrFail({ id: guild_id }).populate({ path: "joined_at", match: { id: req.user_id } }); + const member = await Member.exists({ 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.json(guild); @@ -48,11 +45,11 @@ router.patch("/", check(GuildUpdateSchema), async (req: Request, res: Response) if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner); if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash); - const guild = await GuildModel.findOneAndUpdate({ id: guild_id }, body, { new: true }) - .populate({ path: "joined_at", match: { id: req.user_id } }) - .exec(); - - const data = toObject(guild); + const guild = await Guild.findOneOrFailAndUpdate({ id: guild_id }, body, { new: true }).populate({ + path: "joined_at", + match: { id: req.user_id } + }); + const data = guild; emitEvent({ event: "GUILD_UPDATE", data: data, guild_id } as GuildUpdateEvent); diff --git a/api/src/routes/guilds/#guild_id/invites.ts b/api/src/routes/guilds/#guild_id/invites.ts index 08048d61..ca72cce8 100644 --- a/api/src/routes/guilds/#guild_id/invites.ts +++ b/api/src/routes/guilds/#guild_id/invites.ts @@ -9,9 +9,9 @@ router.get("/", async (req: Request, res: Response) => { const permissions = await getPermission(req.user_id, guild_id); permissions.hasThrow("MANAGE_GUILD"); - const invites = await InviteModel.find({ guild_id }).exec(); + const invites = await Invite.find({ guild_id }); - return res.json(toObject(invites)); + return res.json(invites); }); export default router; diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts index 515434d6..1dacbdad 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts @@ -1,13 +1,13 @@ import { Request, Response, Router } from "express"; import { - GuildModel, - MemberModel, - UserModel, + Guild, + Member, + User, toObject, GuildMemberAddEvent, getPermission, PermissionResolvable, - RoleModel, + Role, GuildMemberUpdateEvent, emitEvent } from "@fosscord/util"; @@ -22,29 +22,29 @@ router.get("/", async (req: Request, res: Response) => { const { guild_id, member_id } = req.params; await isMember(req.user_id, guild_id); - const member = await MemberModel.findOne({ id: member_id, guild_id }).exec(); + const member = await Member.findOneOrFail({ id: member_id, guild_id }); - return res.json(toObject(member)); + return res.json(member); }); router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) => { const { guild_id, member_id } = req.params; const body = req.body as MemberChangeSchema; if (body.roles) { - const roles = await RoleModel.find({ id: { $in: body.roles } }).exec(); + const roles = await Role.find({ id: { $in: body.roles } }); if (body.roles.length !== roles.length) throw new HTTPError("Roles not found", 404); // TODO: check if user has permission to add role } - const member = await MemberModel.findOneAndUpdate({ id: member_id, guild_id }, body, { new: true }).exec(); + const member = await Member.findOneOrFailAndUpdate({ id: member_id, guild_id }, body, { new: true }); await emitEvent({ event: "GUILD_MEMBER_UPDATE", guild_id, - data: toObject(member) + data: member } as GuildMemberUpdateEvent); - res.json(toObject(member)); + res.json(member); }); router.put("/", async (req: Request, res: Response) => { diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/api/src/routes/guilds/#guild_id/members/index.ts index 70303436..656d3acd 100644 --- a/api/src/routes/guilds/#guild_id/members/index.ts +++ b/api/src/routes/guilds/#guild_id/members/index.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { GuildModel, MemberModel, toObject } from "@fosscord/util"; +import { Guild, Member, toObject } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { instanceOf, Length } from "../../../../util/instanceOf"; import { PublicMemberProjection, isMember } from "../../../../util/Member"; @@ -10,7 +10,7 @@ const router = Router(); // TODO: send over websocket router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await GuildModel.findOne({ id: guild_id }).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }); await isMember(req.user_id, guild_id); try { @@ -28,11 +28,8 @@ router.get("/", async (req: Request, res: Response) => { const { limit, after } = (req.query) as { limit: number; after: string }; const query = after ? { id: { $gt: after } } : {}; - var members = await MemberModel.find({ guild_id, ...query }, PublicMemberProjection) - .limit(limit) - .exec(); - - return res.json(toObject(members)); + var members = await Member.find({ guild_id, ...query }, PublicMemberProjection).limit(limit); + return res.json(members); }); export default router; diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index f095c885..5ebc0580 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -1,12 +1,12 @@ import { Request, Response, Router } from "express"; import { - RoleModel, - GuildModel, + Role, + Guild, getPermission, toObject, - UserModel, + User, Snowflake, - MemberModel, + Member, GuildRoleCreateEvent, GuildRoleUpdateEvent, GuildRoleDeleteEvent, @@ -26,23 +26,23 @@ router.get("/", async (req: Request, res: Response) => { await isMember(req.user_id, guild_id); - const roles = await RoleModel.find({ guild_id: guild_id }).exec(); + const roles = await Role.find({ guild_id: guild_id }); - return res.json(toObject(roles)); + return res.json(roles); }); router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const body = req.body as RoleModifySchema; - const guild = await GuildModel.findOne({ id: guild_id }, { id: true }).exec(); - const user = await UserModel.findOne({ id: req.user_id }).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }, { id: true }); + const user = await User.findOneOrFail({ id: req.user_id }); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_ROLES"); if (!body.name) throw new HTTPError("You need to specify a name"); - const role = await new RoleModel({ + const role = await new Role({ ...body, id: Snowflake.generate(), guild_id: guild_id, @@ -57,11 +57,11 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => guild_id, data: { guild_id, - role: toObject(role) + role: role } } as GuildRoleCreateEvent); - res.json(toObject(role)); + res.json(role); }); router.delete("/:role_id", async (req: Request, res: Response) => { @@ -72,10 +72,10 @@ router.delete("/:role_id", async (req: Request, res: Response) => { const permissions = await getPermission(req.user_id, guild_id); permissions.hasThrow("MANAGE_ROLES"); - await RoleModel.deleteOne({ + await Role.deleteOne({ id: role_id, guild_id: guild_id - }).exec(); + }); await emitEvent({ event: "GUILD_ROLE_DELETE", @@ -96,13 +96,13 @@ router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Res const { role_id } = req.params; const body = req.body as RoleModifySchema; - const guild = await GuildModel.findOne({ id: guild_id }, { id: true }).exec(); - const user = await UserModel.findOne({ id: req.user_id }).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }, { id: true }); + const user = await User.findOneOrFail({ id: req.user_id }); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_ROLES"); - const role = await RoleModel.findOneAndUpdate( + const role = await Role.findOneOrFailAndUpdate( { id: role_id, guild_id: guild_id @@ -110,7 +110,7 @@ router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Res // @ts-ignore body, { new: true } - ).exec(); + ); await emitEvent({ event: "GUILD_ROLE_UPDATE", @@ -121,7 +121,7 @@ router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Res } } as GuildRoleUpdateEvent); - res.json(toObject(role)); + res.json(role); }); export default router; diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts index e441ee12..13917dbd 100644 --- a/api/src/routes/guilds/#guild_id/templates.ts +++ b/api/src/routes/guilds/#guild_id/templates.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { TemplateModel, GuildModel, getPermission, toObject, UserModel, Snowflake } from "@fosscord/util"; +import { TemplateModel, Guild, getPermission, toObject, User, Snowflake } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { TemplateCreateSchema, TemplateModifySchema } from "../../../schema/Template"; import { check } from "../../../util/instanceOf"; @@ -27,20 +27,18 @@ const TemplateGuildProjection = { router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; - var templates = await TemplateModel.find({ source_guild_id: guild_id }).exec(); + var templates = await Template.find({ source_guild_id: guild_id }); - return res.json(toObject(templates)); + return res.json(templates); }); router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await GuildModel.findOne({ id: guild_id }, TemplateGuildProjection).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }, TemplateGuildProjection); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); - const exists = await TemplateModel.findOne({ id: guild_id }) - .exec() - .catch((e) => {}); + const exists = await Template.findOneOrFail({ id: guild_id }).catch((e) => {}); if (exists) throw new HTTPError("Template already exists", 400); const template = await new TemplateModel({ @@ -53,7 +51,7 @@ router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response serialized_source_guild: guild }).save(); - res.json(toObject(template)).send(); + res.json(template)).send(; }); router.delete("/:code", async (req: Request, res: Response) => { @@ -63,25 +61,25 @@ router.delete("/:code", async (req: Request, res: Response) => { const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); - const template = await TemplateModel.findOneAndDelete({ + const template = await Template.findOneOrFailAndDelete({ code - }).exec(); + }); - res.send(toObject(template)); + res.send(template); }); router.put("/:code", async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const { code } = req.params; - const guild = await GuildModel.findOne({ id: guild_id }, TemplateGuildProjection).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }, TemplateGuildProjection); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); - const template = await TemplateModel.findOneAndUpdate({ code }, { serialized_source_guild: guild }, { new: true }).exec(); + const template = await Template.findOneOrFailAndUpdate({ code }, { serialized_source_guild: guild }, { new: true }); - res.json(toObject(template)).send(); + res.json(template)).send(; }); router.patch("/:code", check(TemplateModifySchema), async (req: Request, res: Response) => { @@ -91,13 +89,9 @@ router.patch("/:code", check(TemplateModifySchema), async (req: Request, res: Re const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); - const template = await TemplateModel.findOneAndUpdate( - { code }, - { name: req.body.name, description: req.body.description }, - { new: true } - ).exec(); + const template = await Template.findOneOrFailAndUpdate({ code }, { name: req.body.name, description: req.body.description }, { new: true }); - res.json(toObject(template)).send(); + res.json(template)).send(; }); export default router; diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index 1e659d8d..335cea27 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -1,4 +1,4 @@ -import { ChannelModel, ChannelType, getPermission, GuildModel, InviteModel, trimSpecial } from "@fosscord/util"; +import { Channel, ChannelType, getPermission, Guild, InviteModel, trimSpecial } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; import { check, Length } from "../../../util/instanceOf"; @@ -14,9 +14,9 @@ router.get("/", async (req: Request, res: Response) => { const permission = await getPermission(req.user_id, guild_id); permission.hasThrow("MANAGE_GUILD"); - const guild = await GuildModel.findOne({ id: guild_id }).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }); if (!guild.vanity_url_code) return res.json({ code: null }); - const { uses } = await InviteModel.findOne({ code: guild.vanity_url_code }).exec(); + const { uses } = await Invite.findOneOrFail({ code: guild.vanity_url_code }); return res.json({ code: guild.vanity_url_code, uses }); }); @@ -27,23 +27,19 @@ router.patch("/", check({ code: new Length(String, 0, 20) }), async (req: Reques var code = req.body.code.replace(InviteRegex); if (!code) code = null; - const guild = await GuildModel.findOne({ id: guild_id }).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }); const permission = await getPermission(req.user_id, guild_id, undefined, { guild }); permission.hasThrow("MANAGE_GUILD"); const alreadyExists = await Promise.all([ - GuildModel.findOne({ vanity_url_code: code }) - .exec() - .catch(() => null), - InviteModel.findOne({ code: code }) - .exec() - .catch(() => null) + Guild.findOneOrFail({ vanity_url_code: code }).catch(() => null), + Invite.findOneOrFail({ code: code }).catch(() => null) ]); if (alreadyExists.some((x) => x)) throw new HTTPError("Vanity url already exists", 400); - await GuildModel.updateOne({ id: guild_id }, { vanity_url_code: code }).exec(); - const { id } = await ChannelModel.findOne({ guild_id, type: ChannelType.GUILD_TEXT }).exec(); - await InviteModel.updateOne( + await Guild.update({ id: guild_id }, { vanity_url_code: code }); + const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT }); + await Invite.update( { code: guild.vanity_url_code }, { code: code, @@ -53,7 +49,7 @@ router.patch("/", check({ code: new Length(String, 0, 20) }), async (req: Reques channel_id: id }, { upsert: true } - ).exec(); + ); return res.json({ code: code }); }); diff --git a/api/src/routes/guilds/#guild_id/welcome_screen.ts b/api/src/routes/guilds/#guild_id/welcome_screen.ts index c717042e..b457efb6 100644 --- a/api/src/routes/guilds/#guild_id/welcome_screen.ts +++ b/api/src/routes/guilds/#guild_id/welcome_screen.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { GuildModel, getPermission, toObject, Snowflake } from "@fosscord/util"; +import { Guild, getPermission, toObject, Snowflake } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { check } from "../../../util/instanceOf"; @@ -12,18 +12,18 @@ const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { const guild_id = req.params.guild_id; - const guild = await GuildModel.findOne({ id: guild_id }); + const guild = await Guild.findOneOrFail({ id: guild_id }); await isMember(req.user_id, guild_id); - res.json(toObject(guild.welcome_screen)); + res.json(guild.welcome_screen); }); router.post("/", check(GuildAddChannelToWelcomeScreenSchema), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; const body = req.body as GuildAddChannelToWelcomeScreenSchema; - const guild = await GuildModel.findOne({ id: guild_id }).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }); var channelObject = { ...body @@ -36,12 +36,12 @@ router.post("/", check(GuildAddChannelToWelcomeScreenSchema), async (req: Reques if (guild.welcome_screen.welcome_channels.some((channel) => channel.channel_id === body.channel_id)) throw new Error("Welcome Channel exists"); - await GuildModel.findOneAndUpdate( + await Guild.findOneOrFailAndUpdate( { id: guild_id }, { $push: { "welcome_screen.welcome_channels": channelObject } } - ).exec(); + ); res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/api/src/routes/guilds/#guild_id/widget.json.ts index 8719bd85..10bc3ac0 100644 --- a/api/src/routes/guilds/#guild_id/widget.json.ts +++ b/api/src/routes/guilds/#guild_id/widget.json.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { Config, Permissions, GuildModel, InviteModel, ChannelModel, MemberModel } from "@fosscord/util"; +import { Config, Permissions, Guild, InviteModel, Channel, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { random } from "../../../util/RandomInviteID"; @@ -17,11 +17,11 @@ const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await GuildModel.findOne({ id: guild_id }).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }); if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404); // Fetch existing widget invite for widget channel - var invite = await InviteModel.findOne({ channel_id: guild.widget_channel_id, inviter_id: { $type: 10 } }).exec(); + var invite = await Invite.findOneOrFail({ channel_id: guild.widget_channel_id, inviter_id: { $type: 10 } }); if (guild.widget_channel_id && !invite) { // Create invite for channel if none exists // TODO: Refactor invite create code to a shared function @@ -45,8 +45,7 @@ router.get("/", async (req: Request, res: Response) => { // Fetch voice channels, and the @everyone permissions object let channels: any[] = []; - await ChannelModel.find({ guild_id: guild_id, type: 2 }, { permission_overwrites: { $elemMatch: { id: guild_id } } }) - .lean() + await Channel.find({ guild_id: guild_id, type: 2 }, { permission_overwrites: { $elemMatch: { id: guild_id } } }) .select("id name position permission_overwrites") .sort({ position: 1 }) .cursor() @@ -67,8 +66,7 @@ router.get("/", async (req: Request, res: Response) => { // Fetch members // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file) let members: any[] = []; - await MemberModel.find({ guild_id: guild_id }) - .lean() + await Member.find({ guild_id: guild_id }) .populate({ path: "user", select: { _id: 0, username: 1, avatar: 1, presence: 1 } }) .select("id user nick deaf mute") .cursor() diff --git a/api/src/routes/guilds/#guild_id/widget.png.ts b/api/src/routes/guilds/#guild_id/widget.png.ts index 80dc9f2b..89b31153 100644 --- a/api/src/routes/guilds/#guild_id/widget.png.ts +++ b/api/src/routes/guilds/#guild_id/widget.png.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { GuildModel } from "@fosscord/util"; +import { Guild } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import fs from "fs"; import path from "path"; @@ -13,7 +13,7 @@ const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await GuildModel.findOne({ id: guild_id }).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }); if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404); // Fetch guild information diff --git a/api/src/routes/guilds/#guild_id/widget.ts b/api/src/routes/guilds/#guild_id/widget.ts index 85eed5e9..fcf71402 100644 --- a/api/src/routes/guilds/#guild_id/widget.ts +++ b/api/src/routes/guilds/#guild_id/widget.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { getPermission, GuildModel } from "@fosscord/util"; +import { getPermission, Guild } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { check } from "../../../util/instanceOf"; import { WidgetModifySchema } from "../../../schema/Widget"; @@ -13,7 +13,7 @@ router.get("/", async (req: Request, res: Response) => { const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); - const guild = await GuildModel.findOne({ id: guild_id }).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }); return res.json({ enabled: guild.widget_enabled || false, channel_id: guild.widget_channel_id || null }); }); @@ -26,7 +26,7 @@ router.patch("/", check(WidgetModifySchema), async (req: Request, res: Response) const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); - await GuildModel.updateOne({ id: guild_id }, { widget_enabled: body.enabled, widget_channel_id: body.channel_id }).exec(); + await Guild.update({ id: guild_id }, { widget_enabled: body.enabled, widget_channel_id: body.channel_id }); // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request return res.json(body); diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index 92feed4e..05be07d9 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,5 +1,5 @@ import { Router, Request, Response } from "express"; -import { RoleModel, GuildModel, Snowflake, Guild, RoleDocument, Config } from "@fosscord/util"; +import { Role, Guild, Snowflake, Guild, RoleDocument, Config } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { check } from "./../../util/instanceOf"; import { GuildCreateSchema } from "../../schema/Guild"; @@ -65,8 +65,8 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = }; const [guild_doc, role] = await Promise.all([ - new GuildModel(guild).save(), - new RoleModel({ + new Guild(guild).save(), + new Role({ id: guild_id, guild_id: guild_id, color: 0, diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index 7fed3c5d..ad8b676b 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -1,6 +1,6 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); -import { TemplateModel, GuildModel, toObject, UserModel, RoleModel, Snowflake, Guild, Config } from "@fosscord/util"; +import { TemplateModel, Guild, toObject, User, Role, Snowflake, Guild, Config } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { GuildTemplateCreateSchema } from "../../../schema/Guild"; import { getPublicUser } from "../../../util/User"; @@ -10,9 +10,9 @@ import { addMember } from "../../../util/Member"; router.get("/:code", async (req: Request, res: Response) => { const { code } = req.params; - const template = await TemplateModel.findOne({ code: code }).exec(); + const template = await Template.findOneOrFail({ code: code }); - res.json(toObject(template)).send(); + res.json(template)).send(; }); router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res: Response) => { @@ -26,7 +26,7 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res throw new HTTPError(`Maximum number of guilds reached ${maxGuilds}`, 403); } - const template = await TemplateModel.findOne({ code: code }).exec(); + const template = await Template.findOneOrFail({ code: code }); const guild_id = Snowflake.generate(); @@ -38,8 +38,8 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res }; const [guild_doc, role] = await Promise.all([ - new GuildModel(guild).save(), - new RoleModel({ + new Guild(guild).save(), + new Role({ id: guild_id, guild_id: guild_id, color: 0, diff --git a/api/src/routes/invites/index.ts b/api/src/routes/invites/index.ts index e7543dbb..e871af86 100644 --- a/api/src/routes/invites/index.ts +++ b/api/src/routes/invites/index.ts @@ -1,5 +1,5 @@ import { Router, Request, Response } from "express"; -import { getPermission, GuildModel, InviteModel, toObject } from "@fosscord/util"; +import { getPermission, Guild, InviteModel, toObject } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { addMember } from "../../util/Member"; const router: Router = Router(); @@ -7,42 +7,40 @@ const router: Router = Router(); router.get("/:code", async (req: Request, res: Response) => { const { code } = req.params; - const invite = await InviteModel.findOne({ code }).exec(); + const invite = await Invite.findOneOrFail({ code }); if (!invite) throw new HTTPError("Unknown Invite", 404); - res.status(200).send(toObject(invite)); + res.status(200).send(invite); }); router.post("/:code", async (req: Request, res: Response) => { const { code } = req.params; - const invite = await InviteModel.findOneAndUpdate({ code }, { $inc: { uses: 1 } }, { new: true }).exec(); + const invite = await Invite.findOneOrFailAndUpdate({ code }, { $inc: { uses: 1 } }, { new: true }); if (!invite) throw new HTTPError("Unknown Invite", 404); - if (invite.uses >= invite.max_uses) await InviteModel.deleteOne({ code }); + if (invite.uses >= invite.max_uses) await Invite.deleteOne({ code }); await addMember(req.user_id, invite.guild_id); - res.status(200).send(toObject(invite)); + res.status(200).send(invite); }); router.delete("/:code", async (req: Request, res: Response) => { const { code } = req.params; - const invite = await InviteModel.findOne({ code }).exec(); + const invite = await Invite.findOneOrFail({ code }); const { guild_id, channel_id } = invite; - const guild = await GuildModel.findOne({ id: guild_id }).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }); const permission = await getPermission(req.user_id, guild_id, channel_id, { guild }); if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS")) throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401); - await InviteModel.deleteOne({ code }).exec(); + await Invite.deleteOne({ code }); - await GuildModel.updateOne({ vanity_url_code: code }, { $unset: { vanity_url_code: 1 } }) - .exec() - .catch((e) => {}); + await Guild.update({ vanity_url_code: code }, { $unset: { vanity_url_code: 1 } }).catch((e) => {}); - res.status(200).send({ invite: toObject(invite) }); + res.status(200).send({ invite: invite) }; }); export default router; diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index 4b4b9439..46c96698 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -4,24 +4,24 @@ import { getPublicUser } from "../../../util/User"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - const user = await getPublicUser(req.params.id, { user_data: true }) + const user = await getPublicUser(req.params.id, { data: true }); - res.json({ - connected_accounts: user.user_data.connected_accounts, - premium_guild_since: null, // TODO - premium_since: null, // TODO - user: { - username: user.username, - discriminator: user.discriminator, - id: user.id, - public_flags: user.public_flags, - avatar: user.avatar, - accent_color: user.accent_color, - banner: user.banner, - bio: req.user_bot ? null : user.bio, - bot: user.bot, - } - }); + res.json({ + connected_accounts: user.data.connected_accounts, + premium_guild_since: null, // TODO + premium_since: null, // TODO + user: { + username: user.username, + discriminator: user.discriminator, + id: user.id, + public_flags: user.public_flags, + avatar: user.avatar, + accent_color: user.accent_color, + banner: user.banner, + bio: req.user_bot ? null : user.bio, + bot: user.bot + } + }); }); export default router; diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index db9f8832..28e77dd9 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,6 +1,6 @@ import { Router, Request, Response } from "express"; import { - ChannelModel, + Channel, ChannelCreateEvent, toObject, ChannelType, @@ -8,7 +8,7 @@ import { trimSpecial, Channel, DMChannel, - UserModel, + User, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; @@ -19,9 +19,9 @@ import { check } from "../../../util/instanceOf"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - var channels = await ChannelModel.find({ recipient_ids: req.user_id }).exec(); + var channels = await Channel.find({ recipient_ids: req.user_id }); - res.json(toObject(channels)); + res.json(channels); }); router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Response) => { @@ -29,14 +29,14 @@ router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Respons body.recipients = body.recipients.filter((x) => x !== req.user_id).unique(); - if (!(await Promise.all(body.recipients.map((x) => UserModel.exists({ id: x })))).every((x) => x)) { + if (!(await Promise.all(body.recipients.map((x) => User.exists({ id: x })))).every((x) => x)) { throw new HTTPError("Recipient not found"); } const type = body.recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM; const name = trimSpecial(body.name); - const channel = await new ChannelModel({ + const channel = await new Channel({ name, type, owner_id: req.user_id, @@ -46,9 +46,9 @@ router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Respons recipient_ids: [...body.recipients, req.user_id] }).save(); - await emitEvent({ event: "CHANNEL_CREATE", data: toObject(channel), user_id: req.user_id } as ChannelCreateEvent); + await emitEvent({ event: "CHANNEL_CREATE", data: channel), user_id: req.user_id } as ChannelCreateEvent; - res.json(toObject(channel)); + res.json(channel); }); export default router; diff --git a/api/src/routes/users/@me/delete.ts b/api/src/routes/users/@me/delete.ts index f863237d..fa8134cc 100644 --- a/api/src/routes/users/@me/delete.ts +++ b/api/src/routes/users/@me/delete.ts @@ -1,16 +1,16 @@ import { Router, Request, Response } from "express"; -import { GuildModel, MemberModel, UserModel } from "@fosscord/util"; +import { Guild, Member, User } from "@fosscord/util"; import bcrypt from "bcrypt"; const router = Router(); router.post("/", async (req: Request, res: Response) => { - const user = await UserModel.findOne({ id: req.user_id }).exec(); //User object + const user = await User.findOneOrFail({ id: req.user_id }); //User object - let correctpass = await bcrypt.compare(req.body.password, user!.user_data.hash); //Not sure if user typed right password :/ + let correctpass = await bcrypt.compare(req.body.password, user!.data.hash); //Not sure if user typed right password :/ if (correctpass) { await Promise.all([ - UserModel.deleteOne({ id: req.user_id }).exec(), //Yeetus user deletus - MemberModel.deleteMany({ id: req.user_id }).exec() + User.deleteOne({ id: req.user_id }), //Yeetus user deletus + Member.deleteMany({ id: req.user_id }) ]); res.sendStatus(204); diff --git a/api/src/routes/users/@me/disable.ts b/api/src/routes/users/@me/disable.ts index 2d3a9850..a40c9e59 100644 --- a/api/src/routes/users/@me/disable.ts +++ b/api/src/routes/users/@me/disable.ts @@ -1,15 +1,15 @@ -import { UserModel } from "@fosscord/util"; +import { User } from "@fosscord/util"; import { Router, Response, Request } from "express"; import bcrypt from "bcrypt"; const router = Router(); router.post("/", async (req: Request, res: Response) => { - const user = await UserModel.findOne({ id: req.user_id }).exec(); //User object + const user = await User.findOneOrFail({ id: req.user_id }); //User object - let correctpass = await bcrypt.compare(req.body.password, user!.user_data.hash); //Not sure if user typed right password :/ + let correctpass = await bcrypt.compare(req.body.password, user!.data.hash); //Not sure if user typed right password :/ if (correctpass) { - await UserModel.updateOne({ id: req.user_id }, { disabled: true }).exec(); + await User.update({ id: req.user_id }, { disabled: true }); res.sendStatus(204); } else { diff --git a/api/src/routes/users/@me/guilds.ts b/api/src/routes/users/@me/guilds.ts index a9b53b75..e40bfec9 100644 --- a/api/src/routes/users/@me/guilds.ts +++ b/api/src/routes/users/@me/guilds.ts @@ -1,5 +1,5 @@ import { Router, Request, Response } from "express"; -import { GuildModel, MemberModel, UserModel, GuildDeleteEvent, GuildMemberRemoveEvent, toObject, emitEvent } from "@fosscord/util"; +import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, toObject, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { getPublicUser } from "../../../util/User"; @@ -7,28 +7,25 @@ import { getPublicUser } from "../../../util/User"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - const user = await UserModel.findOne({ id: req.user_id }, { guilds: true }).exec(); + const user = await User.findOneOrFail({ id: req.user_id }, { guilds: true }); if (!user) throw new HTTPError("User not found", 404); var guildIDs = user.guilds || []; - var guild = await GuildModel.find({ id: { $in: guildIDs } }) - .populate({ path: "joined_at", match: { id: req.user_id } }) - .exec(); - - res.json(toObject(guild)); + var guild = await Guild.find({ id: { $in: guildIDs } }).populate({ path: "joined_at", match: { id: req.user_id } }); + res.json(guild); }); // user send to leave a certain guild router.delete("/:id", async (req: Request, res: Response) => { const guild_id = req.params.id; - const guild = await GuildModel.findOne({ id: guild_id }, { guild_id: true }).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }, { guild_id: true }); 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); await Promise.all([ - MemberModel.deleteOne({ id: req.user_id, guild_id: guild_id }).exec(), - UserModel.updateOne({ id: req.user_id }, { $pull: { guilds: guild_id } }).exec(), + Member.deleteOne({ id: req.user_id, guild_id: guild_id }), + User.update({ id: req.user_id }, { $pull: { guilds: guild_id } }), emitEvent({ event: "GUILD_DELETE", data: { diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 6ebc6634..903b24aa 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,5 +1,5 @@ import { Router, Request, Response } from "express"; -import { UserModel, toObject, PublicUserProjection } from "@fosscord/util"; +import { User, toObject, PublicUserProjection } from "@fosscord/util"; import { getPublicUser } from "../../../util/User"; import { UserModifySchema } from "../../../schema/User"; import { check } from "../../../util/instanceOf"; @@ -38,10 +38,10 @@ router.patch("/", check(UserModifySchema), async (req: Request, res: Response) = if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); - const user = await UserModel.findOneAndUpdate({ id: req.user_id }, body, { projection: UserUpdateProjection, new: true }).exec(); + const user = await User.findOneOrFailAndUpdate({ id: req.user_id }, body, { projection: UserUpdateProjection, new: true }); // TODO: dispatch user update event - res.json(toObject(user)); + res.json(user); }); export default router; diff --git a/api/src/routes/users/@me/profile.ts b/api/src/routes/users/@me/profile.ts index b67d1964..fdb969dc 100644 --- a/api/src/routes/users/@me/profile.ts +++ b/api/src/routes/users/@me/profile.ts @@ -4,24 +4,24 @@ import { getPublicUser } from "../../../util/User"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - const user = await getPublicUser(req.user_id, { user_data: true }) + const user = await getPublicUser(req.user_id, { data: true }); - res.json({ - connected_accounts: user.user_data.connected_accounts, - premium_guild_since: null, // TODO - premium_since: null, // TODO - user: { - username: user.username, - discriminator: user.discriminator, - id: user.id, - public_flags: user.public_flags, - avatar: user.avatar, - accent_color: user.accent_color, - banner: user.banner, - bio: user.bio, - bot: user.bot, - } - }); + res.json({ + connected_accounts: user.data.connected_accounts, + premium_guild_since: null, // TODO + premium_since: null, // TODO + user: { + username: user.username, + discriminator: user.discriminator, + id: user.id, + public_flags: user.public_flags, + avatar: user.avatar, + accent_color: user.accent_color, + banner: user.banner, + bio: user.bio, + bot: user.bot + } + }); }); export default router; diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 642ee5f9..9b8d6199 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -1,6 +1,6 @@ import { RelationshipAddEvent, - UserModel, + User, PublicUserProjection, toObject, RelationshipType, @@ -18,18 +18,18 @@ const router = Router(); const userProjection = { "user_data.relationships": true, ...PublicUserProjection }; router.get("/", async (req: Request, res: Response) => { - const user = await UserModel.findOne({ id: req.user_id }, { user_data: { relationships: true } }) - .populate({ path: "user_data.relationships.id", model: UserModel }) - .exec(); - - return res.json(toObject(user.user_data.relationships)); + const user = await User.findOneOrFail({ id: req.user_id }, { user_data: { relationships: true } }).populate({ + path: "user_data.relationships.id", + model: User + }); + return res.json(user.user_data.relationships); }); async function addRelationship(req: Request, res: Response, friend: UserDocument, type: RelationshipType) { const id = friend.id; if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); - const user = await UserModel.findOne({ id: req.user_id }, userProjection).exec(); + const user = await User.findOneOrFail({ id: req.user_id }, userProjection); const newUserRelationships = [...user.user_data.relationships]; const newFriendRelationships = [...friend.user_data.relationships]; @@ -48,7 +48,7 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument if (friendRequest && friendRequest.type !== RelationshipType.blocked) { newFriendRelationships.remove(friendRequest); await Promise.all([ - UserModel.updateOne({ id: friend.id }, { "user_data.relationships": newFriendRelationships }).exec(), + User.update({ id: friend.id }, { "user_data.relationships": newFriendRelationships }), emitEvent({ event: "RELATIONSHIP_REMOVE", data: friendRequest, @@ -58,12 +58,12 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument } await Promise.all([ - UserModel.updateOne({ id: req.user_id }, { "user_data.relationships": newUserRelationships }).exec(), + User.update({ id: req.user_id }, { "user_data.relationships": newUserRelationships }), emitEvent({ event: "RELATIONSHIP_ADD", data: { - ...toObject(relationship), - user: { ...toObject(friend), user_data: undefined } + ...relationship, + user: { ...friend, user_data: undefined } }, user_id: req.user_id } as RelationshipAddEvent) @@ -91,22 +91,22 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument } else newUserRelationships.push(outgoing_relationship); await Promise.all([ - UserModel.updateOne({ id: req.user_id }, { "user_data.relationships": newUserRelationships }).exec(), - UserModel.updateOne({ id: friend.id }, { "user_data.relationships": newFriendRelationships }).exec(), + User.update({ id: req.user_id }, { "user_data.relationships": newUserRelationships }), + User.update({ id: friend.id }, { "user_data.relationships": newFriendRelationships }), emitEvent({ event: "RELATIONSHIP_ADD", data: { ...outgoing_relationship, - user: { ...toObject(friend), user_data: undefined } + user: { ...friend, user_data: undefined } }, user_id: req.user_id } as RelationshipAddEvent), emitEvent({ event: "RELATIONSHIP_ADD", data: { - ...toObject(incoming_relationship), + ...incoming_relationship, should_notify: true, - user: { ...toObject(user), user_data: undefined } + user: { ...user, user_data: undefined } }, user_id: id } as RelationshipAddEvent) @@ -116,14 +116,14 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument } router.put("/:id", check({ $type: new Length(Number, 1, 4) }), async (req: Request, res: Response) => { - return await addRelationship(req, res, await UserModel.findOne({ id: req.params.id }), req.body.type); + return await addRelationship(req, res, await User.findOneOrFail({ id: req.params.id }), req.body.type); }); router.post("/", check({ discriminator: String, username: String }), async (req: Request, res: Response) => { return await addRelationship( req, res, - await UserModel.findOne(req.body as { discriminator: string; username: string }).exec(), + await User.findOneOrFail(req.body as { discriminator: string; username: string }), req.body.type ); }); @@ -132,10 +132,10 @@ router.delete("/:id", 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 UserModel.findOne({ id: req.user_id }).exec(); + const user = await User.findOneOrFail({ id: req.user_id }); if (!user) throw new HTTPError("Invalid token", 400); - const friend = await UserModel.findOne({ id }, userProjection).exec(); + const friend = await User.findOneOrFail({ id }, userProjection); if (!friend) throw new HTTPError("User not found", 404); const relationship = user.user_data.relationships.find((x) => x.id === id); diff --git a/api/src/routes/users/@me/settings.ts b/api/src/routes/users/@me/settings.ts index f045a010..90ee6372 100644 --- a/api/src/routes/users/@me/settings.ts +++ b/api/src/routes/users/@me/settings.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { UserModel, UserSettings } from "@fosscord/util"; +import { User, UserSettings } from "@fosscord/util"; import { check } from "../../../util/instanceOf"; import { UserSettingsSchema } from "../../../schema/User"; @@ -9,7 +9,7 @@ router.patch("/", check(UserSettingsSchema), async (req: Request, res: Response) const body = req.body as UserSettings; // only users can update user settings - await UserModel.updateOne({ id: req.user_id, bot: false }, body).exec(); + await User.update({ id: req.user_id, bot: false }, { settings: body }); res.sendStatus(204); }); diff --git a/api/src/test/mongo_test.ts b/api/src/test/mongo_test.ts index 44b04c5b..bf203ea6 100644 --- a/api/src/test/mongo_test.ts +++ b/api/src/test/mongo_test.ts @@ -2,12 +2,12 @@ import mongoose, { Schema, Types } from "mongoose"; require("mongoose-long")(mongoose); const userSchema = new Schema({ - id: String, + id: String }); const messageSchema = new Schema({ id: String, - content: String, + content: String }); const message = mongoose.model("message", messageSchema, "messages"); const user = mongoose.model("user", userSchema, "users"); @@ -16,7 +16,7 @@ messageSchema.virtual("u", { ref: user, localField: "id", foreignField: "id", - justOne: true, + justOne: true }); messageSchema.set("toObject", { virtuals: true }); @@ -25,14 +25,14 @@ messageSchema.set("toJSON", { virtuals: true }); async function main() { const conn = await mongoose.connect("mongodb://localhost:27017/lambert?readPreference=secondaryPreferred", { useNewUrlParser: true, - useUnifiedTopology: false, + useUnifiedTopology: false }); console.log("connected"); // const u = await new user({ name: "test" }).save(); // await new message({ user: u._id, content: "test" }).save(); - const test = await message.findOne({}).populate("u").exec(); + const test = await message.findOneOrFail({}).populate("u"); // @ts-ignore console.log(test?.toJSON()); } diff --git a/api/src/util/Channel.ts b/api/src/util/Channel.ts index fb6f9c8c..a618d2df 100644 --- a/api/src/util/Channel.ts +++ b/api/src/util/Channel.ts @@ -1,10 +1,10 @@ import { ChannelCreateEvent, - ChannelModel, + Channel, ChannelType, emitEvent, getPermission, - GuildModel, + Guild, Snowflake, TextChannel, toObject, @@ -29,7 +29,7 @@ export async function createChannel( case ChannelType.GUILD_TEXT: case ChannelType.GUILD_VOICE: if (channel.parent_id && !opts?.skipExistsCheck) { - const exists = await ChannelModel.findOne({ id: channel.parent_id }, { guild_id: true }).exec(); + const exists = await Channel.findOneOrFail({ id: channel.parent_id }, { guild_id: true }); if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400); if (exists.guild_id !== channel.guild_id) throw new HTTPError("The category channel needs to be in the guild"); } @@ -49,7 +49,7 @@ export async function createChannel( if (!channel.permission_overwrites) channel.permission_overwrites = []; // TODO: auto generate position - channel = await new ChannelModel({ + channel = await new Channel({ ...channel, ...(!opts?.keepId && { id: Snowflake.generate() }), created_at: new Date(), @@ -57,7 +57,7 @@ export async function createChannel( recipient_ids: null }).save(); - await emitEvent({ event: "CHANNEL_CREATE", data: toObject(channel), guild_id: channel.guild_id } as ChannelCreateEvent); + await emitEvent({ event: "CHANNEL_CREATE", data: channel), guild_id: channel.guild_id } as ChannelCreateEvent; return channel; } diff --git a/api/src/util/Member.ts b/api/src/util/Member.ts index da02735c..6cb14d71 100644 --- a/api/src/util/Member.ts +++ b/api/src/util/Member.ts @@ -5,11 +5,11 @@ import { GuildMemberAddEvent, GuildMemberRemoveEvent, GuildMemberUpdateEvent, - GuildModel, - MemberModel, - RoleModel, + Guild, + Member, + Role, toObject, - UserModel, + User, GuildDocument, Config, emitEvent @@ -32,7 +32,7 @@ export const PublicMemberProjection = { }; export async function isMember(user_id: string, guild_id: string) { - const exists = await MemberModel.exists({ id: user_id, guild_id }); + const exists = await Member.exists({ id: user_id, guild_id }); if (!exists) throw new HTTPError("You are not a member of this guild", 403); return exists; } @@ -45,11 +45,11 @@ export async function addMember(user_id: string, guild_id: string, cache?: { gui throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403); } - const guild = cache?.guild || (await GuildModel.findOne({ id: guild_id }).exec()); + const guild = cache?.guild || (await Guild.findOneOrFail({ id: guild_id })); if (!guild) throw new HTTPError("Guild not found", 404); - if (await MemberModel.exists({ id: user.id, guild_id })) throw new HTTPError("You are already a member of this guild", 400); + if (await Member.exists({ id: user.id, guild_id })) throw new HTTPError("You are already a member of this guild", 400); const member = { id: user_id, @@ -64,7 +64,7 @@ export async function addMember(user_id: string, guild_id: string, cache?: { gui }; await Promise.all([ - new MemberModel({ + new Member({ ...member, read_state: {}, settings: { @@ -79,8 +79,8 @@ export async function addMember(user_id: string, guild_id: string, cache?: { gui } }).save(), - UserModel.updateOne({ id: user_id }, { $push: { guilds: guild_id } }).exec(), - GuildModel.updateOne({ id: guild_id }, { $inc: { member_count: 1 } }).exec(), + User.update({ id: user_id }, { $push: { guilds: guild_id } }), + Guild.update({ id: guild_id }, { $inc: { member_count: 1 } }), emitEvent({ event: "GUILD_MEMBER_ADD", @@ -95,12 +95,10 @@ export async function addMember(user_id: string, guild_id: string, cache?: { gui await emitEvent({ event: "GUILD_CREATE", - data: toObject( - await guild - .populate({ path: "members", match: { guild_id } }) - .populate({ path: "joined_at", match: { id: user.id } }) - .execPopulate() - ), + data: await guild + .populate({ path: "members", match: { guild_id } }) + .populate({ path: "joined_at", match: { id: user.id } }) + .execPopulate(), user_id } as GuildCreateEvent); } @@ -108,19 +106,19 @@ export async function addMember(user_id: string, guild_id: string, cache?: { gui export async function removeMember(user_id: string, guild_id: string) { const user = await getPublicUser(user_id); - const guild = await GuildModel.findOne({ id: guild_id }, { owner_id: true }).exec(); + const guild = await Guild.findOneOrFail({ id: guild_id }, { owner_id: true }); if (!guild) throw new HTTPError("Guild not found", 404); if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild"); - if (!(await MemberModel.exists({ id: user.id, guild_id }))) throw new HTTPError("Is not member of this guild", 404); + if (!(await Member.exists({ id: user.id, guild_id }))) throw new HTTPError("Is not member of this guild", 404); // use promise all to execute all promises at the same time -> save time return Promise.all([ - MemberModel.deleteOne({ + Member.deleteOne({ id: user_id, guild_id: guild_id - }).exec(), - UserModel.updateOne({ id: user.id }, { $pull: { guilds: guild_id } }).exec(), - GuildModel.updateOne({ id: guild_id }, { $inc: { member_count: -1 } }).exec(), + }), + User.update({ id: user.id }, { $pull: { guilds: guild_id } }), + Guild.update({ id: guild_id }, { $inc: { member_count: -1 } }), emitEvent({ event: "GUILD_DELETE", @@ -143,17 +141,17 @@ export async function removeMember(user_id: string, guild_id: string) { export async function addRole(user_id: string, guild_id: string, role_id: string) { const user = await getPublicUser(user_id); - const role = await RoleModel.findOne({ id: role_id, guild_id: guild_id }).exec(); + const role = await Role.findOneOrFail({ id: role_id, guild_id: guild_id }); if (!role) throw new HTTPError("role not found", 404); - var memberObj = await MemberModel.findOneAndUpdate( + var memberObj = await Member.findOneOrFailAndUpdate( { id: user_id, guild_id: guild_id }, { $push: { roles: role_id } }, { new: true } - ).exec(); + ); if (!memberObj) throw new HTTPError("Member not found", 404); @@ -171,17 +169,17 @@ export async function addRole(user_id: string, guild_id: string, role_id: string export async function removeRole(user_id: string, guild_id: string, role_id: string) { const user = await getPublicUser(user_id); - const role = await RoleModel.findOne({ id: role_id, guild_id: guild_id }).exec(); + const role = await Role.findOneOrFail({ id: role_id, guild_id: guild_id }); if (!role) throw new HTTPError("role not found", 404); - var memberObj = await MemberModel.findOneAndUpdate( + var memberObj = await Member.findOneOrFailAndUpdate( { id: user_id, guild_id: guild_id }, { $pull: { roles: role_id } }, { new: true } - ).exec(); + ); if (!memberObj) throw new HTTPError("Member not found", 404); @@ -199,14 +197,14 @@ export async function removeRole(user_id: string, guild_id: string, role_id: str export async function changeNickname(user_id: string, guild_id: string, nickname: string) { const user = await getPublicUser(user_id); - var memberObj = await MemberModel.findOneAndUpdate( + var memberObj = await Member.findOneOrFailAndUpdate( { id: user_id, guild_id: guild_id }, { nick: nickname }, { new: true } - ).exec(); + ); if (!memberObj) throw new HTTPError("Member not found", 404); diff --git a/api/src/util/Message.ts b/api/src/util/Message.ts index 8a1e959e..5561904b 100644 --- a/api/src/util/Message.ts +++ b/api/src/util/Message.ts @@ -1,5 +1,5 @@ import { - ChannelModel, + Channel, Embed, emitEvent, Message, @@ -7,13 +7,11 @@ import { MessageUpdateEvent, getPermission, CHANNEL_MENTION, - toObject, - MessageModel, Snowflake, PublicMemberProjection, USER_MENTION, ROLE_MENTION, - RoleModel, + Role, EVERYONE_MENTION, HERE_MENTION } from "@fosscord/util"; @@ -37,13 +35,11 @@ const DEFAULT_FETCH_OPTIONS: any = { method: "GET" }; -export async function handleMessage(opts: Partial) { - const channel = await ChannelModel.findOne( +export async function handleMessage(opts: Partial): Promise { + const channel = await Channel.findOneOrFail( { id: opts.channel_id }, - { guild_id: true, type: true, permission_overwrites: true, recipient_ids: true, owner_id: true } - ) - .lean() // lean is needed, because we don't want to populate .recipients that also auto deletes .recipient_ids - .exec(); + { select: ["guild_id", "type", "permission_overwrites", "recipient_ids", "owner_id"] } + ); // lean is needed, because we don't want to populate .recipients that also auto deletes .recipient_ids if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404); // TODO: are tts messages allowed in dm channels? should permission be checked? @@ -83,7 +79,7 @@ export async function handleMessage(opts: Partial) { await Promise.all( Array.from(content.matchAll(ROLE_MENTION)).map(async ([_, mention]) => { - const role = await RoleModel.findOne({ id: mention, guild_id: channel.guild_id }).exec(); + const role = await Role.findOneOrFail({ id: mention, guild_id: channel.guild_id }); if (role.mentionable || permission.has("MANAGE_ROLES")) { mention_role_ids.push(mention); } @@ -160,16 +156,17 @@ export async function postHandleMessage(message: Message) { channel_id: message.channel_id, data } as MessageUpdateEvent), - MessageModel.updateOne({ id: message.id, channel_id: message.channel_id }, data).exec() + Message.update({ id: message.id, channel_id: message.channel_id }, data) ]); } export async function sendMessage(opts: Partial) { const message = await handleMessage({ ...opts, id: Snowflake.generate(), timestamp: new Date() }); - const data = toObject( - await new MessageModel(message).populate({ path: "member", select: PublicMemberProjection }).populate("referenced_message").save() - ); + const data = await new Message(message) + .populate({ path: "member", select: PublicMemberProjection }) + .populate("referenced_message") + .save(); await emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data } as MessageCreateEvent); diff --git a/api/src/util/User.ts b/api/src/util/User.ts index 392c7101..4d9065c4 100644 --- a/api/src/util/User.ts +++ b/api/src/util/User.ts @@ -1,16 +1,16 @@ -import { toObject, UserModel, PublicUserProjection } from "@fosscord/util"; +import { toObject, User, PublicUserProjection } from "@fosscord/util"; import { HTTPError } from "lambert-server"; export { PublicUserProjection }; export async function getPublicUser(user_id: string, additional_fields?: any) { - const user = await UserModel.findOne( + const user = await User.findOneOrFail( { id: user_id }, { ...PublicUserProjection, ...additional_fields } - ).exec(); + ); if (!user) throw new HTTPError("User not found", 404); - return toObject(user); + return user; } -- cgit 1.5.1 From 2dbbd21f734b6c0785d5a02d8aed71f7f32d4cff Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Fri, 27 Aug 2021 11:10:42 +0200 Subject: :construction: api --- api/__tests__/routes/auth/login.ts | 1 - api/jest.config.ts | 4 +- api/package-lock.json | 1131 ++++++++++++++++++++++++-- api/package.json | 6 +- api/scripts/setup_test.js | 1 + api/src/middlewares/ErrorHandler.ts | 1 + api/src/routes/auth/login.ts | 12 +- api/src/routes/auth/register.ts | 59 +- api/src/routes/guilds/#guild_id/channels.ts | 12 +- api/src/routes/guilds/#guild_id/templates.ts | 60 +- api/src/routes/guilds/templates/index.ts | 7 +- api/src/routes/invites/index.ts | 4 +- api/src/routes/users/#id/profile.ts | 2 +- api/src/routes/users/@me/relationships.ts | 36 +- api/src/util/Channel.ts | 2 +- api/src/util/User.ts | 16 - api/tests/routes/auth/login.ts | 1 + 17 files changed, 1160 insertions(+), 195 deletions(-) delete mode 100644 api/__tests__/routes/auth/login.ts create mode 100644 api/scripts/setup_test.js delete mode 100644 api/src/util/User.ts create mode 100644 api/tests/routes/auth/login.ts (limited to 'api/src') diff --git a/api/__tests__/routes/auth/login.ts b/api/__tests__/routes/auth/login.ts deleted file mode 100644 index 536e03ee..00000000 --- a/api/__tests__/routes/auth/login.ts +++ /dev/null @@ -1 +0,0 @@ -it("works", () => {}); diff --git a/api/jest.config.ts b/api/jest.config.ts index 153a9b2b..50550d9d 100644 --- a/api/jest.config.ts +++ b/api/jest.config.ts @@ -145,9 +145,9 @@ export default { // The glob patterns Jest uses to detect test files testMatch: [ - "**/__tests__/**/*.[jt]s?(x)", + "**/tests/**/*.[jt]s?(x)" // "**/?(*.)+(spec|test).[tj]s?(x)" - ], + ] // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped // testPathIgnorePatterns: [ diff --git a/api/package-lock.json b/api/package-lock.json index 3221969a..bff4e940 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -34,13 +34,15 @@ "mongoose-autopopulate": "^0.12.3", "mongoose-long": "^0.3.2", "multer": "^1.4.2", - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "typeorm": "^0.2.37" }, "devDependencies": { "@types/amqplib": "^0.8.1", "@types/bcrypt": "^5.0.0", "@types/express": "^4.17.9", "@types/i18next-node-fs-backend": "^2.1.0", + "@types/jest": "^27.0.1", "@types/jsonwebtoken": "^8.5.0", "@types/mongodb": "^3.6.9", "@types/mongoose": "^5.10.5", @@ -74,9 +76,9 @@ "jsonwebtoken": "^8.5.1", "missing-native-js-functions": "^1.2.10", "node-fetch": "^2.6.1", + "patch-package": "^6.4.7", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", - "ts-transform-json-schema": "^2.0.3", "typeorm": "^0.2.37", "typescript": "^4.3.5", "typescript-json-schema": "^0.50.1" @@ -1115,6 +1117,11 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@sqltools/formatter": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz", + "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==" + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -1282,6 +1289,101 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.1.tgz", + "integrity": "sha512-HTLpVXHrY69556ozYkcq47TtQJXpcWAWfkoqz+ZGz2JnmZhzlRjprCIyFnetSy8gpDWwTTGBcRVv1J1I1vBrHw==", + "dev": true, + "dependencies": { + "jest-diff": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "node_modules/@types/jest/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@types/jest/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/diff-sequences": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", + "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-diff": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.6.tgz", + "integrity": "sha512-Z1mqgkTCSYaFgwTlP/NUiRzdqgxmmhzHY1Tq17zL94morOHfHu3K4bgSgl+CR4GLhpV8VxkuOYuIWnQ9LnFqmg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, "node_modules/@types/jsonwebtoken": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz", @@ -1425,6 +1527,11 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, + "node_modules/@types/zen-observable": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", + "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" + }, "node_modules/@zerollup/ts-helpers": { "version": "1.7.18", "resolved": "https://registry.npmjs.org/@zerollup/ts-helpers/-/ts-helpers-1.7.18.tgz", @@ -1667,7 +1774,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, "engines": { "node": ">=8" } @@ -1692,6 +1798,11 @@ "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=", "dev": true }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -1705,6 +1816,14 @@ "node": ">= 8" } }, + "node_modules/app-root-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", + "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -2116,7 +2235,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -2927,6 +3045,111 @@ "node": ">=0.10.0" } }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "node_modules/cli-highlight/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, "node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -4114,8 +4337,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { "version": "1.0.2", @@ -4169,7 +4391,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -4183,7 +4404,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -4693,6 +4913,14 @@ "bser": "2.1.1" } }, + "node_modules/figlet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz", + "integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4908,7 +5136,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -5035,7 +5262,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, "dependencies": { "ansi-regex": "^2.0.0" }, @@ -5047,7 +5273,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5174,6 +5399,14 @@ "node": ">=10.0.0" } }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -5356,7 +5589,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -7394,6 +7626,16 @@ "integrity": "sha512-nU7mOEuaXiQIB/EgTIjYZJ7g8KqMm2D8l4qp+DqA4jxWOb/tnb1KEoqp+tlbdQIDIAiC1i7j7X/3yHDFXLxr9g==", "dev": true }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoassert": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", @@ -8004,6 +8246,14 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "node_modules/parent-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", + "integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/parents": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", @@ -8669,6 +8919,11 @@ "node": ">=8.10.0" } }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "node_modules/regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", @@ -8720,7 +8975,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9190,6 +9444,11 @@ "node": ">=6" } }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "node_modules/saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -9315,7 +9574,6 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -10119,7 +10377,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.0" }, @@ -10339,6 +10596,25 @@ "node": ">=8" } }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", @@ -10776,45 +11052,269 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true, + "node_modules/typeorm": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.37.tgz", + "integrity": "sha512-7rkW0yCgFC24I5T0f3S/twmLSuccPh1SQmxET/oDWn2sSDVzbyWdnItSdKy27CdJGTlKHYtUVeOcMYw5LRsXVw==", + "dependencies": { + "@sqltools/formatter": "^1.2.2", + "app-root-path": "^3.0.0", + "buffer": "^6.0.3", + "chalk": "^4.1.0", + "cli-highlight": "^2.1.11", + "debug": "^4.3.1", + "dotenv": "^8.2.0", + "glob": "^7.1.6", + "js-yaml": "^4.0.0", + "mkdirp": "^1.0.4", + "reflect-metadata": "^0.1.13", + "sha.js": "^2.4.11", + "tslib": "^2.1.0", + "xml2js": "^0.4.23", + "yargonaut": "^1.1.4", + "yargs": "^17.0.1", + "zen-observable-ts": "^1.0.0" + }, "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "typeorm": "cli.js" }, - "engines": { - "node": ">=4.2.0" + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@sap/hana-client": "*", + "better-sqlite3": "*", + "hdb-pool": "*", + "ioredis": "*", + "mongodb": "^3.6.0", + "mssql": "*", + "mysql2": "*", + "oracledb": "*", + "pg": "*", + "pg-native": "*", + "pg-query-stream": "*", + "redis": "*", + "sql.js": "*", + "sqlite3": "*", + "typeorm-aurora-data-api-driver": "*" + }, + "peerDependenciesMeta": { + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "hdb-pool": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } } }, - "node_modules/umd": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", - "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", - "dev": true, - "bin": { - "umd": "bin/cli.js" - } + "node_modules/typeorm/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, - "node_modules/undeclared-identifiers": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", - "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", - "dev": true, + "node_modules/typeorm/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "acorn-node": "^1.3.0", - "dash-ast": "^1.0.0", - "get-assigned-identifiers": "^1.2.0", - "simple-concat": "^1.0.0", - "xtend": "^4.0.1" - }, - "bin": { - "undeclared-identifiers": "bin.js" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/union-value": { + "node_modules/typeorm/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/typeorm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/typeorm/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/typeorm/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/typeorm/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/typeorm/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/typeorm/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/typeorm/node_modules/yargs": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.1.1.tgz", + "integrity": "sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/typeorm/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/typescript": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/umd": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", + "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", + "dev": true, + "bin": { + "umd": "bin/cli.js" + } + }, + "node_modules/undeclared-identifiers": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", + "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", + "dev": true, + "dependencies": { + "acorn-node": "^1.3.0", + "dash-ast": "^1.0.0", + "get-assigned-identifiers": "^1.2.0", + "simple-concat": "^1.0.0", + "xtend": "^4.0.1" + }, + "bin": { + "undeclared-identifiers": "bin.js" + } + }, + "node_modules/union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", @@ -11274,6 +11774,26 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -11299,6 +11819,66 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yargonaut": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz", + "integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==", + "dependencies": { + "chalk": "^1.1.1", + "figlet": "^1.1.1", + "parent-require": "^1.0.0" + } + }, + "node_modules/yargonaut/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargonaut/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargonaut/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargonaut/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargonaut/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -11366,6 +11946,20 @@ "node": ">=6" } }, + "node_modules/zen-observable": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", + "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" + }, + "node_modules/zen-observable-ts": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", + "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", + "dependencies": { + "@types/zen-observable": "0.8.3", + "zen-observable": "0.8.15" + } + }, "node_modules/zip-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", @@ -11884,9 +12478,9 @@ "jsonwebtoken": "^8.5.1", "missing-native-js-functions": "^1.2.10", "node-fetch": "^2.6.1", + "patch-package": "^6.4.7", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", - "ts-transform-json-schema": "^2.0.3", "typeorm": "^0.2.37", "typescript": "^4.3.5", "typescript-json-schema": "^0.50.1" @@ -12227,6 +12821,11 @@ "@sinonjs/commons": "^1.7.0" } }, + "@sqltools/formatter": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz", + "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==" + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -12391,6 +12990,82 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.1.tgz", + "integrity": "sha512-HTLpVXHrY69556ozYkcq47TtQJXpcWAWfkoqz+ZGz2JnmZhzlRjprCIyFnetSy8gpDWwTTGBcRVv1J1I1vBrHw==", + "dev": true, + "requires": { + "jest-diff": "^27.0.0", + "pretty-format": "^27.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "diff-sequences": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", + "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", + "dev": true + }, + "jest-diff": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.6.tgz", + "integrity": "sha512-Z1mqgkTCSYaFgwTlP/NUiRzdqgxmmhzHY1Tq17zL94morOHfHu3K4bgSgl+CR4GLhpV8VxkuOYuIWnQ9LnFqmg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.0.6" + } + }, + "jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true + }, + "pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "requires": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + } + } + } + }, "@types/jsonwebtoken": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz", @@ -12534,6 +13209,11 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, + "@types/zen-observable": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", + "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" + }, "@zerollup/ts-helpers": { "version": "1.7.18", "resolved": "https://registry.npmjs.org/@zerollup/ts-helpers/-/ts-helpers-1.7.18.tgz", @@ -12721,8 +13401,7 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { "version": "4.3.0", @@ -12738,6 +13417,11 @@ "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=", "dev": true }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -12748,6 +13432,11 @@ "picomatch": "^2.0.4" } }, + "app-root-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", + "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==" + }, "append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -13102,8 +13791,7 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bcrypt": { "version": "5.0.1", @@ -13797,6 +14485,85 @@ } } }, + "cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "requires": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "dependencies": { + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + } + } + }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -14824,8 +15591,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "encodeurl": { "version": "1.0.2", @@ -14869,8 +15635,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-html": { "version": "1.0.3", @@ -14880,8 +15645,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "2.0.0", @@ -15298,6 +16062,11 @@ "bser": "2.1.1" } }, + "figlet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz", + "integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -15470,8 +16239,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-package-type": { "version": "0.1.0", @@ -15562,7 +16330,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" }, @@ -15570,8 +16337,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" } } }, @@ -15676,6 +16442,11 @@ "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", "integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==" }, + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -15830,8 +16601,7 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { "version": "5.1.8", @@ -17453,6 +18223,16 @@ "integrity": "sha512-nU7mOEuaXiQIB/EgTIjYZJ7g8KqMm2D8l4qp+DqA4jxWOb/tnb1KEoqp+tlbdQIDIAiC1i7j7X/3yHDFXLxr9g==", "dev": true }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "nanoassert": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", @@ -17936,6 +18716,11 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "parent-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", + "integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=" + }, "parents": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", @@ -18491,6 +19276,11 @@ "picomatch": "^2.2.1" } }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", @@ -18532,8 +19322,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-from-string": { "version": "2.0.2", @@ -18884,6 +19673,11 @@ "sparse-bitfield": "^3.0.3" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -18994,7 +19788,6 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -19699,7 +20492,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -19864,6 +20656,22 @@ "minimatch": "^3.0.4" } }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, "throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", @@ -20207,6 +21015,118 @@ "is-typedarray": "^1.0.0" } }, + "typeorm": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.37.tgz", + "integrity": "sha512-7rkW0yCgFC24I5T0f3S/twmLSuccPh1SQmxET/oDWn2sSDVzbyWdnItSdKy27CdJGTlKHYtUVeOcMYw5LRsXVw==", + "requires": { + "@sqltools/formatter": "^1.2.2", + "app-root-path": "^3.0.0", + "buffer": "^6.0.3", + "chalk": "^4.1.0", + "cli-highlight": "^2.1.11", + "debug": "^4.3.1", + "dotenv": "^8.2.0", + "glob": "^7.1.6", + "js-yaml": "^4.0.0", + "mkdirp": "^1.0.4", + "reflect-metadata": "^0.1.13", + "sha.js": "^2.4.11", + "tslib": "^2.1.0", + "xml2js": "^0.4.23", + "yargonaut": "^1.1.4", + "yargs": "^17.0.1", + "zen-observable-ts": "^1.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.1.1.tgz", + "integrity": "sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + } + } + }, "typescript": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", @@ -20623,6 +21543,20 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -20645,6 +21579,53 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "yargonaut": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz", + "integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==", + "requires": { + "chalk": "^1.1.1", + "figlet": "^1.1.1", + "parent-require": "^1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -20699,6 +21680,20 @@ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true }, + "zen-observable": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", + "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" + }, + "zen-observable-ts": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", + "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", + "requires": { + "@types/zen-observable": "0.8.3", + "zen-observable": "0.8.15" + } + }, "zip-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", diff --git a/api/package.json b/api/package.json index 7b2de011..1310d577 100644 --- a/api/package.json +++ b/api/package.json @@ -5,7 +5,7 @@ "main": "dist/Server.js", "types": "dist/Server.d.ts", "scripts": { - "test": "jest", + "test": "npm run build && jest", "test:watch": "jest --watch", "start": "npm run build && node dist/start", "build": "npx tsc -b .", @@ -35,6 +35,7 @@ "@types/bcrypt": "^5.0.0", "@types/express": "^4.17.9", "@types/i18next-node-fs-backend": "^2.1.0", + "@types/jest": "^27.0.1", "@types/jsonwebtoken": "^8.5.0", "@types/mongodb": "^3.6.9", "@types/mongoose": "^5.10.5", @@ -79,6 +80,7 @@ "mongoose-autopopulate": "^0.12.3", "mongoose-long": "^0.3.2", "multer": "^1.4.2", - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "typeorm": "^0.2.37" } } diff --git a/api/scripts/setup_test.js b/api/scripts/setup_test.js new file mode 100644 index 00000000..4b692f7c --- /dev/null +++ b/api/scripts/setup_test.js @@ -0,0 +1 @@ +// TODO: start api diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index d080e498..8e2cd923 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -2,6 +2,7 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; import { FieldError } from "../util/instanceOf"; +// TODO: update with new body/typorm validation export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { if (!error) next(); diff --git a/api/src/routes/auth/login.ts b/api/src/routes/auth/login.ts index 579a097e..c0acad4e 100644 --- a/api/src/routes/auth/login.ts +++ b/api/src/routes/auth/login.ts @@ -21,9 +21,6 @@ router.post( async (req: Request, res: Response) => { const { login, password, captcha_key, undelete } = req.body; const email = adjustEmail(login); - const query: any[] = [{ phone: login }]; - if (email) query.push({ email }); - console.log(req.body, email); const config = Config.get(); @@ -41,11 +38,10 @@ router.post( // TODO: check captcha } - const user = await User.findOneOrFail( - { $or: query }, - { "data.hash": true, id: true, disabled: true, deleted: true, "settings.locale": true, "settings.theme": true } - ).catch((e) => { - console.log(e, query); + const user = await User.findOneOrFail({ + where: [{ phone: login }, { email: login }], + select: ["data", "id", "disabled", "deleted", "settings"] + }).catch((e) => { throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } }); }); diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index 1405e219..62b039d5 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -1,12 +1,12 @@ import { Request, Response, Router } from "express"; -import { trimSpecial, User, Snowflake, User, Config } from "@fosscord/util"; +import { trimSpecial, User, Snowflake, Config } from "@fosscord/util"; import bcrypt from "bcrypt"; import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "../../util/instanceOf"; import "missing-native-js-functions"; import { generateToken } from "./login"; import { getIpAdress, IPAnalysis, isProxy } from "../../util/ipAddress"; import { HTTPError } from "lambert-server"; -import RateLimit from "../../middlewares/RateLimit"; +import { In } from "typeorm"; const router: Router = Router(); @@ -55,13 +55,13 @@ router.post( // TODO: check password strength // adjusted_email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick - let adjusted_email: string | null = adjustEmail(email); + let adjusted_email = adjustEmail(email); // adjusted_password will be the hash of the password - let adjusted_password: string = ""; + let adjusted_password = ""; // trim special uf8 control characters -> Backspace, Newline, ... - let adjusted_username: string = trimSpecial(username); + let adjusted_username = trimSpecial(username); // discriminator will be randomly generated let discriminator = ""; @@ -129,7 +129,7 @@ router.post( if (!register.allowMultipleAccounts) { // TODO: check if fingerprint was eligible generated - const exists = await User.findOneOrFail({ fingerprints: fingerprint }).catch((e) => {}); + const exists = await User.findOne({ where: { fingerprints: In(fingerprint) } }); if (exists) { throw FieldErrors({ @@ -164,12 +164,8 @@ router.post( // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the mongodb database? for (let tries = 0; tries < 5; tries++) { discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); - try { - exists = await User.findOneOrFail({ discriminator, username: adjusted_username }, "id"); - } catch (error) { - // doesn't exist -> break - break; - } + exists = await User.findOne({ where: { discriminator, username: adjusted_username }, select: ["id"] }); + if (!exists) break; } if (exists) { @@ -185,35 +181,26 @@ router.post( // appearently discord doesn't save the date of birth and just calculate if nsfw is allowed // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false - const user: User = { + const user = await new User({ id: Snowflake.generate(), created_at: new Date(), username: adjusted_username, discriminator, - avatar: null, - accent_color: null, - banner: null, + avatar: undefined, + accent_color: undefined, + banner: undefined, bot: false, system: false, desktop: false, mobile: false, premium: true, premium_type: 2, - phone: null, + phone: undefined, bio: "", mfa_enabled: false, verified: false, disabled: false, deleted: false, - presence: { - activities: [], - client_status: { - desktop: undefined, - mobile: undefined, - web: undefined - }, - status: "offline" - }, email: adjusted_email, nsfw_allowed: true, // TODO: depending on age public_flags: 0n, @@ -221,10 +208,7 @@ router.post( guilds: [], data: { hash: adjusted_password, - valid_tokens_since: new Date(), - relationships: [], - connected_accounts: [], - fingerprints: [] + valid_tokens_since: new Date() }, settings: { afk_timeout: 300, @@ -234,10 +218,10 @@ router.post( contact_sync_enabled: false, convert_emoticons: false, custom_status: { - emoji_id: null, - emoji_name: null, - expires_at: null, - text: null + emoji_id: undefined, + emoji_name: undefined, + expires_at: undefined, + text: undefined }, default_guilds_restricted: false, detect_platform_accounts: true, @@ -265,16 +249,13 @@ router.post( timezone_offset: 0 // timezone_offset: // TODO: timezone from request } - }; - - // insert user into database - await new User(user).save(); + }).save(); return res.json({ token: await generateToken(user.id) }); } ); -export function adjustEmail(email: string): string | null { +export function adjustEmail(email: string): string | undefined { // body parser already checked if it is a valid email const parts = email.match(EMAIL_REGEX); // @ts-ignore diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index b53c9a5a..7b0e94b6 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { Channel, toObject, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; +import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { ChannelModifySchema } from "../../../schema/Channel"; @@ -48,15 +48,19 @@ router.patch( if (x.parent_id) { opts.parent_id = x.parent_id; - const parent_channel = await Channel.findOneOrFail({ id: x.parent_id, guild_id }, { permission_overwrites: true }); + const parent_channel = await Channel.findOneOrFail({ + where: { id: x.parent_id, guild_id }, + select: ["permission_overwrites"] + }); if (x.lock_permissions) { opts.permission_overwrites = parent_channel.permission_overwrites; } } - const channel = await Channel.findOneOrFailAndUpdate({ id: x.id, guild_id }, opts, { new: true }); + await Channel.update({ guild_id, id: x.id }, opts); + const channel = await Channel.findOneOrFail({ guild_id, id: x.id }); - await emitEvent({ event: "CHANNEL_UPDATE", data: channel), channel_id: x.id, guild_id } as ChannelUpdateEvent; + await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent); }) ]); diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts index 13917dbd..e1d2f5fd 100644 --- a/api/src/routes/guilds/#guild_id/templates.ts +++ b/api/src/routes/guilds/#guild_id/templates.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { TemplateModel, Guild, getPermission, toObject, User, Snowflake } from "@fosscord/util"; +import { Guild, getPermission, Template } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { TemplateCreateSchema, TemplateModifySchema } from "../../../schema/Template"; import { check } from "../../../util/instanceOf"; @@ -7,22 +7,22 @@ import { generateCode } from "../../../util/String"; const router: Router = Router(); -const TemplateGuildProjection = { - name: true, - description: true, - region: true, - verification_level: true, - default_message_notifications: true, - explicit_content_filter: true, - preferred_locale: true, - afk_timeout: true, - roles: true, - channels: true, - afk_channel_id: true, - system_channel_id: true, - system_channel_flags: true, - icon_hash: true -}; +const TemplateGuildProjection: (keyof Guild)[] = [ + "name", + "description", + "region", + "verification_level", + "default_message_notifications", + "explicit_content_filter", + "preferred_locale", + "afk_timeout", + "roles", + "channels", + "afk_channel_id", + "system_channel_id", + "system_channel_flags", + "icon" +]; router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; @@ -34,14 +34,14 @@ router.get("/", async (req: Request, res: Response) => { router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }, TemplateGuildProjection); + const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); const exists = await Template.findOneOrFail({ id: guild_id }).catch((e) => {}); if (exists) throw new HTTPError("Template already exists", 400); - const template = await new TemplateModel({ + const template = await new Template({ ...req.body, code: generateCode(), creator_id: req.user_id, @@ -51,7 +51,7 @@ router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response serialized_source_guild: guild }).save(); - res.json(template)).send(; + res.json(template); }); router.delete("/:code", async (req: Request, res: Response) => { @@ -61,37 +61,39 @@ router.delete("/:code", async (req: Request, res: Response) => { const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); - const template = await Template.findOneOrFailAndDelete({ + const template = await Template.delete({ code }); - res.send(template); + res.json(template); }); router.put("/:code", async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - const { code } = req.params; + // synchronizes the template + const { code, guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }, TemplateGuildProjection); + const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); - const template = await Template.findOneOrFailAndUpdate({ code }, { serialized_source_guild: guild }, { new: true }); + const template = await new Template({ code, serialized_source_guild: guild }).save(); - res.json(template)).send(; + res.json(template); }); router.patch("/:code", check(TemplateModifySchema), async (req: Request, res: Response) => { + // updates the template description const { guild_id } = req.params; const { code } = req.params; + const { name, description } = req.body; const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); - const template = await Template.findOneOrFailAndUpdate({ code }, { name: req.body.name, description: req.body.description }, { new: true }); + const template = await new Template({ code, name: name, description: description }).save(); - res.json(template)).send(; + res.json(template); }); export default router; diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index ad8b676b..b8c1012d 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -1,9 +1,8 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); -import { TemplateModel, Guild, toObject, User, Role, Snowflake, Guild, Config } from "@fosscord/util"; +import { Template, Guild, Role, Snowflake, Config, User } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { GuildTemplateCreateSchema } from "../../../schema/Guild"; -import { getPublicUser } from "../../../util/User"; import { check } from "../../../util/instanceOf"; import { addMember } from "../../../util/Member"; @@ -12,7 +11,7 @@ router.get("/:code", async (req: Request, res: Response) => { const template = await Template.findOneOrFail({ code: code }); - res.json(template)).send(; + res.json(template); }); router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res: Response) => { @@ -20,7 +19,7 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res const body = req.body as GuildTemplateCreateSchema; const { maxGuilds } = Config.get().limits.user; - const user = await getPublicUser(req.user_id, { guilds: true }); + const user = await User.getPublicUser(req.user_id, { guilds: true }); if (user.guilds.length >= maxGuilds) { throw new HTTPError(`Maximum number of guilds reached ${maxGuilds}`, 403); diff --git a/api/src/routes/invites/index.ts b/api/src/routes/invites/index.ts index e871af86..a488c44b 100644 --- a/api/src/routes/invites/index.ts +++ b/api/src/routes/invites/index.ts @@ -1,5 +1,5 @@ import { Router, Request, Response } from "express"; -import { getPermission, Guild, InviteModel, toObject } from "@fosscord/util"; +import { getPermission, Guild, Invite } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { addMember } from "../../util/Member"; const router: Router = Router(); @@ -40,7 +40,7 @@ router.delete("/:code", async (req: Request, res: Response) => { await Guild.update({ vanity_url_code: code }, { $unset: { vanity_url_code: 1 } }).catch((e) => {}); - res.status(200).send({ invite: invite) }; + res.json({ invite: invite }); }); export default router; diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index 46c96698..9b2e2d5e 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -7,7 +7,7 @@ router.get("/", async (req: Request, res: Response) => { const user = await getPublicUser(req.params.id, { data: true }); res.json({ - connected_accounts: user.data.connected_accounts, + connected_accounts: user.connected_accounts, premium_guild_since: null, // TODO premium_since: null, // TODO user: { diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 9b8d6199..8c7469ed 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -15,14 +15,14 @@ import { check, Length } from "../../../util/instanceOf"; const router = Router(); -const userProjection = { "user_data.relationships": true, ...PublicUserProjection }; +const userProjection = { "data.relationships": true, ...PublicUserProjection }; router.get("/", async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ id: req.user_id }, { user_data: { relationships: true } }).populate({ - path: "user_data.relationships.id", + const user = await User.findOneOrFail({ id: req.user_id }, { data: { relationships: true } }).populate({ + path: "data.relationships.id", model: User }); - return res.json(user.user_data.relationships); + return res.json(user.data.relationships); }); async function addRelationship(req: Request, res: Response, friend: UserDocument, type: RelationshipType) { @@ -30,8 +30,8 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); const user = await User.findOneOrFail({ id: req.user_id }, userProjection); - const newUserRelationships = [...user.user_data.relationships]; - const newFriendRelationships = [...friend.user_data.relationships]; + const newUserRelationships = [...user.data.relationships]; + const newFriendRelationships = [...friend.data.relationships]; var relationship = newUserRelationships.find((x) => x.id === id); const friendRequest = newFriendRelationships.find((x) => x.id === req.user_id); @@ -48,7 +48,7 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument if (friendRequest && friendRequest.type !== RelationshipType.blocked) { newFriendRelationships.remove(friendRequest); await Promise.all([ - User.update({ id: friend.id }, { "user_data.relationships": newFriendRelationships }), + User.update({ id: friend.id }, { "data.relationships": newFriendRelationships }), emitEvent({ event: "RELATIONSHIP_REMOVE", data: friendRequest, @@ -58,12 +58,12 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument } await Promise.all([ - User.update({ id: req.user_id }, { "user_data.relationships": newUserRelationships }), + User.update({ id: req.user_id }, { "data.relationships": newUserRelationships }), emitEvent({ event: "RELATIONSHIP_ADD", data: { ...relationship, - user: { ...friend, user_data: undefined } + user: { ...friend, data: undefined } }, user_id: req.user_id } as RelationshipAddEvent) @@ -91,13 +91,13 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument } else newUserRelationships.push(outgoing_relationship); await Promise.all([ - User.update({ id: req.user_id }, { "user_data.relationships": newUserRelationships }), - User.update({ id: friend.id }, { "user_data.relationships": newFriendRelationships }), + User.update({ id: req.user_id }, { "data.relationships": newUserRelationships }), + User.update({ id: friend.id }, { "data.relationships": newFriendRelationships }), emitEvent({ event: "RELATIONSHIP_ADD", data: { ...outgoing_relationship, - user: { ...friend, user_data: undefined } + user: { ...friend, data: undefined } }, user_id: req.user_id } as RelationshipAddEvent), @@ -106,7 +106,7 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument data: { ...incoming_relationship, should_notify: true, - user: { ...user, user_data: undefined } + user: { ...user, data: undefined } }, user_id: id } as RelationshipAddEvent) @@ -138,11 +138,11 @@ router.delete("/:id", async (req: Request, res: Response) => { const friend = await User.findOneOrFail({ id }, userProjection); if (!friend) throw new HTTPError("User not found", 404); - const relationship = user.user_data.relationships.find((x) => x.id === id); - const friendRequest = friend.user_data.relationships.find((x) => x.id === req.user_id); + const relationship = user.data.relationships.find((x) => x.id === id); + const friendRequest = friend.data.relationships.find((x) => x.id === req.user_id); if (relationship?.type === RelationshipType.blocked) { // unblock user - user.user_data.relationships.remove(relationship); + user.data.relationships.remove(relationship); await Promise.all([ user.save(), @@ -153,8 +153,8 @@ router.delete("/:id", async (req: Request, res: Response) => { if (!relationship || !friendRequest) throw new HTTPError("You are not friends with the user", 404); if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); - user.user_data.relationships.remove(relationship); - friend.user_data.relationships.remove(friendRequest); + user.data.relationships.remove(relationship); + friend.data.relationships.remove(friendRequest); await Promise.all([ user.save(), diff --git a/api/src/util/Channel.ts b/api/src/util/Channel.ts index a618d2df..f518aefd 100644 --- a/api/src/util/Channel.ts +++ b/api/src/util/Channel.ts @@ -57,7 +57,7 @@ export async function createChannel( recipient_ids: null }).save(); - await emitEvent({ event: "CHANNEL_CREATE", data: channel), guild_id: channel.guild_id } as ChannelCreateEvent; + await emitEvent({ event: "CHANNEL_CREATE", data: channel, guild_id: channel.guild_id } as ChannelCreateEvent); return channel; } diff --git a/api/src/util/User.ts b/api/src/util/User.ts deleted file mode 100644 index 4d9065c4..00000000 --- a/api/src/util/User.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { toObject, User, PublicUserProjection } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; - -export { PublicUserProjection }; - -export async function getPublicUser(user_id: string, additional_fields?: any) { - const user = await User.findOneOrFail( - { id: user_id }, - { - ...PublicUserProjection, - ...additional_fields - } - ); - if (!user) throw new HTTPError("User not found", 404); - return user; -} diff --git a/api/tests/routes/auth/login.ts b/api/tests/routes/auth/login.ts new file mode 100644 index 00000000..e1938b36 --- /dev/null +++ b/api/tests/routes/auth/login.ts @@ -0,0 +1 @@ +test("works", () => {}); -- cgit 1.5.1 From c9ff1774b435b5af72faa97386890b3cb659744c Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 29 Aug 2021 00:03:40 +0200 Subject: :sparkles: typeorm api rewrite done --- api/src/Server.ts | 23 +-- api/src/index.ts | 2 - api/src/middlewares/Authentication.ts | 2 +- api/src/routes/auth/register.ts | 56 ------ api/src/routes/channels/#channel_id/index.ts | 30 +-- api/src/routes/channels/#channel_id/invites.ts | 4 +- .../#channel_id/messages/#message_id/ack.ts | 2 +- .../#channel_id/messages/#message_id/index.ts | 28 +-- .../#channel_id/messages/#message_id/reactions.ts | 45 +++-- .../channels/#channel_id/messages/bulk-delete.ts | 7 +- .../routes/channels/#channel_id/messages/index.ts | 30 ++- api/src/routes/channels/#channel_id/permissions.ts | 29 +-- api/src/routes/channels/#channel_id/pins.ts | 76 +++---- api/src/routes/channels/#channel_id/typing.ts | 4 +- api/src/routes/channels/#channel_id/webhooks.ts | 2 +- api/src/routes/guilds/#guild_id/bans.ts | 71 ++++--- api/src/routes/guilds/#guild_id/delete.ts | 19 +- api/src/routes/guilds/#guild_id/index.ts | 39 ++-- api/src/routes/guilds/#guild_id/invites.ts | 2 +- .../guilds/#guild_id/members/#member_id/index.ts | 27 +-- .../guilds/#guild_id/members/#member_id/nick.ts | 5 +- .../members/#member_id/roles/#role_id/index.ts | 7 +- api/src/routes/guilds/#guild_id/members/index.ts | 21 +- api/src/routes/guilds/#guild_id/roles.ts | 68 +++---- api/src/routes/guilds/#guild_id/vanity-url.ts | 38 ++-- api/src/routes/guilds/#guild_id/welcome_screen.ts | 28 +-- api/src/routes/guilds/#guild_id/widget.json.ts | 95 ++------- api/src/routes/guilds/index.ts | 17 +- api/src/routes/guilds/templates/index.ts | 25 ++- api/src/routes/invites/index.ts | 19 +- api/src/routes/users/#id/index.ts | 5 +- api/src/routes/users/#id/profile.ts | 4 +- api/src/routes/users/@me/channels.ts | 23 +-- api/src/routes/users/@me/delete.ts | 13 +- api/src/routes/users/@me/disable.ts | 9 +- api/src/routes/users/@me/guilds.ts | 19 +- api/src/routes/users/@me/index.ts | 47 +++-- api/src/routes/users/@me/profile.ts | 6 +- api/src/routes/users/@me/relationships.ts | 85 ++++---- api/src/schema/Guild.ts | 80 ++------ api/src/schema/Message.ts | 8 +- api/src/schema/Roles.ts | 2 +- api/src/test/mongo_test.ts | 40 ---- api/src/test/server_benchmark.ts | 39 ---- api/src/test/test.ts | 3 - api/src/util/Channel.ts | 17 +- api/src/util/Member.ts | 220 --------------------- api/src/util/Message.ts | 19 +- 48 files changed, 467 insertions(+), 993 deletions(-) delete mode 100644 api/src/test/mongo_test.ts delete mode 100644 api/src/test/server_benchmark.ts delete mode 100644 api/src/test/test.ts delete mode 100644 api/src/util/Member.ts (limited to 'api/src') diff --git a/api/src/Server.ts b/api/src/Server.ts index adaf7f2c..0f444f86 100644 --- a/api/src/Server.ts +++ b/api/src/Server.ts @@ -2,7 +2,7 @@ import "missing-native-js-functions"; import { Connection } from "mongoose"; import { Server, ServerOptions } from "lambert-server"; import { Authentication, CORS } from "./middlewares/"; -import { Config, db, initEvent } from "@fosscord/util"; +import { Config, initDatabase, initEvent } from "@fosscord/util"; import { ErrorHandler } from "./middlewares/ErrorHandler"; import { BodyParser } from "./middlewares/BodyParser"; import { Router, Request, Response, NextFunction } from "express"; @@ -31,30 +31,13 @@ export class FosscordServer extends Server { super({ ...opts, errorHandler: false, jsonBody: false }); } - async setupSchema() { - return Promise.all([ - db.collection("users").createIndex({ id: 1 }, { unique: true }), - db.collection("messages").createIndex({ id: 1 }, { unique: true }), - db.collection("channels").createIndex({ id: 1 }, { unique: true }), - db.collection("guilds").createIndex({ id: 1 }, { unique: true }), - db.collection("members").createIndex({ id: 1, guild_id: 1 }, { unique: true }), - db.collection("roles").createIndex({ id: 1 }, { unique: true }), - db.collection("emojis").createIndex({ id: 1 }, { unique: true }), - db.collection("invites").createIndex({ code: 1 }, { unique: true }), - db.collection("invites").createIndex({ expires_at: 1 }, { expireAfterSeconds: 0 }), // after 0 seconds of expires_at the invite will get delete - db.collection("ratelimits").createIndex({ expires_at: 1 }, { expireAfterSeconds: 0 }) - ]); - } - async start() { - // @ts-ignore - await (db as Promise); - await this.setupSchema(); + await initDatabase(); await Config.init(); await initEvent(); this.app.use(CORS); - this.app.use(BodyParser({ inflate: true, limit: 1024 * 1024 * 10 })); // 2MB + this.app.use(BodyParser({ inflate: true, limit: 1024 * 1024 * 10 })); // 10MB const app = this.app; const api = Router(); // @ts-ignore diff --git a/api/src/index.ts b/api/src/index.ts index 73b9dbd2..0bba7f5e 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -8,8 +8,6 @@ export * from "./schema/Message"; export * from "./util/Constants"; export * from "./util/instanceOf"; export * from "./util/instanceOf"; -export * from "./util/Member"; export * from "./util/RandomInviteID"; export * from "./util/String"; -export * from "./util/User"; export { check as checkPassword } from "./util/passwordStrength"; diff --git a/api/src/middlewares/Authentication.ts b/api/src/middlewares/Authentication.ts index a8bfe196..34a66a6b 100644 --- a/api/src/middlewares/Authentication.ts +++ b/api/src/middlewares/Authentication.ts @@ -28,7 +28,7 @@ declare global { export async function Authentication(req: Request, res: Response, next: NextFunction) { if (req.method === "OPTIONS") return res.sendStatus(204); const url = req.url.replace(API_PREFIX, ""); - if (url.startsWith("/invites") && req.method === "GET") return next(); // @ts-ignore + if (url.startsWith("/invites") && req.method === "GET") return next(); if ( NO_AUTHORIZATION_ROUTES.some((x) => { if (typeof x === "string") return url.startsWith(x); diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index 62b039d5..5ad6d6a4 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -182,72 +182,16 @@ router.post( // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false const user = await new User({ - id: Snowflake.generate(), - created_at: new Date(), username: adjusted_username, discriminator, - avatar: undefined, - accent_color: undefined, - banner: undefined, - bot: false, - system: false, - desktop: false, - mobile: false, premium: true, premium_type: 2, - phone: undefined, - bio: "", - mfa_enabled: false, - verified: false, - disabled: false, - deleted: false, email: adjusted_email, nsfw_allowed: true, // TODO: depending on age - public_flags: 0n, - flags: 0n, // TODO: generate default flags guilds: [], data: { hash: adjusted_password, valid_tokens_since: new Date() - }, - settings: { - afk_timeout: 300, - allow_accessibility_detection: true, - animate_emoji: true, - animate_stickers: 0, - contact_sync_enabled: false, - convert_emoticons: false, - custom_status: { - emoji_id: undefined, - emoji_name: undefined, - expires_at: undefined, - text: undefined - }, - default_guilds_restricted: false, - detect_platform_accounts: true, - developer_mode: false, - disable_games_tab: false, - enable_tts_command: true, - explicit_content_filter: 0, - friend_source_flags: { all: true }, - gateway_connected: false, - gif_auto_play: true, - guild_folders: [], - guild_positions: [], - inline_attachment_media: true, - inline_embed_media: true, - locale: req.language, - message_display_compact: false, - native_phone_integration_enabled: true, - render_embeds: true, - render_reactions: true, - restricted_guilds: [], - show_current_game: true, - status: "offline", - stream_notifications_enabled: true, - theme: "dark", - timezone_offset: 0 - // timezone_offset: // TODO: timezone from request } }).save(); diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 91534602..4aa5a5b9 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,4 +1,4 @@ -import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, getPermission, GuildUpdateEvent, toObject } from "@fosscord/util"; +import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, getPermission } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; import { ChannelModifySchema } from "../../../schema/Channel"; @@ -23,7 +23,7 @@ router.delete("/", async (req: Request, res: Response) => { const channel = await Channel.findOneOrFail({ id: channel_id }); - const permission = await getPermission(req.user_id, channel?.guild_id, channel_id, { channel }); + const permission = await getPermission(req.user_id, channel?.guild_id, channel_id); permission.hasThrow("MANAGE_CHANNELS"); // TODO: Dm channel "close" not delete @@ -31,7 +31,7 @@ router.delete("/", async (req: Request, res: Response) => { await emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent); - await Channel.deleteOne({ id: channel_id }); + await Channel.delete({ id: channel_id }); res.send(data); }); @@ -43,17 +43,19 @@ router.patch("/", check(ChannelModifySchema), async (req: Request, res: Response const permission = await getPermission(req.user_id, undefined, channel_id); permission.hasThrow("MANAGE_CHANNELS"); - const channel = await Channel.findOneOrFailAndUpdate({ id: channel_id }, payload, { new: true }); - - const data = channel; - - await emitEvent({ - event: "CHANNEL_UPDATE", - data, - channel_id - } as ChannelUpdateEvent); - - res.send(data); + const channel = await Channel.findOneOrFail({ id: channel_id }); + channel.assign(payload); + + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + data: channel, + channel_id + } as ChannelUpdateEvent) + ]); + + res.send(channel); }); export default router; diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index cc77df26..35006ac4 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -6,7 +6,7 @@ import { random } from "../../../util/RandomInviteID"; import { InviteCreateSchema } from "../../../schema/Invite"; -import { getPermission, Channel, InviteModel, InviteCreateEvent, toObject, emitEvent } from "@fosscord/util"; +import { getPermission, Channel, Invite, InviteCreateEvent, emitEvent } from "@fosscord/util"; const router: Router = Router(); @@ -38,7 +38,7 @@ router.post("/", check(InviteCreateSchema), async (req: Request, res: Response) inviter_id: user_id }; - await new InviteModel(invite).save(); + await new Invite(invite).save(); await emitEvent({ event: "INVITE_CREATE", data: invite, guild_id } as InviteCreateEvent); res.status(201).send(invite); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts index ff576d90..0fd5f2be 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts @@ -14,7 +14,7 @@ router.post("/", check({ $manual: Boolean, $mention_count: Number }), async (req const permission = await getPermission(req.user_id, undefined, channel_id); permission.hasThrow("VIEW_CHANNEL"); - await ReadState.update({ user_id: req.user_id, channel_id, message_id }, { user_id: req.user_id, channel_id, message_id }); + await ReadState.update({ user_id: req.user_id, channel_id }, { user_id: req.user_id, channel_id, last_message_id: message_id }); await emitEvent({ event: "MESSAGE_ACK", diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index 058e2390..32478763 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -1,6 +1,5 @@ -import { Channel, emitEvent, getPermission, MessageDeleteEvent, Message, MessageUpdateEvent, toObject } from "@fosscord/util"; +import { Channel, emitEvent, getPermission, MessageDeleteEvent, Message, MessageUpdateEvent } from "@fosscord/util"; import { Router, Response, Request } from "express"; -import { HTTPError } from "lambert-server"; import { MessageCreateSchema } from "../../../../../schema/Message"; import { check } from "../../../../../util/instanceOf"; @@ -12,7 +11,7 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response const { message_id, channel_id } = req.params; var body = req.body as MessageCreateSchema; - var message = await Message.findOneOrFail({ id: message_id, channel_id }, { author_id: true, message_reference: true }); + const message = await Message.findOneOrFail({ id: message_id, channel_id }); const permissions = await getPermission(req.user_id, undefined, channel_id); @@ -29,15 +28,16 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response id: message_id, edited_timestamp: new Date() }); + message.assign(opts); - // @ts-ignore - message = await Message.findOneOrFailAndUpdate({ id: message_id }, opts, { new: true }).populate("author"); - - await emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: { ...message, nonce: undefined } - } as MessageUpdateEvent); + await Promise.all([ + message.save(), + await emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: { ...message, nonce: undefined } + } as MessageUpdateEvent) + ]); postHandleMessage(message); @@ -49,13 +49,13 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response router.delete("/", async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); - const message = await Message.findOneOrFail({ id: message_id }, { author_id: true }); + const channel = await Channel.findOneOrFail({ id: channel_id }); + const message = await Message.findOneOrFail({ id: message_id }); const permission = await getPermission(req.user_id, channel.guild_id, channel_id); if (message.author_id !== req.user_id) permission.hasThrow("MANAGE_MESSAGES"); - await Message.deleteOne({ id: message_id }); + await Message.delete({ id: message_id }); await emitEvent({ event: "MESSAGE_DELETE", diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts index 9a6e4436..f60484b5 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts @@ -1,7 +1,7 @@ import { Channel, emitEvent, - EmojiModel, + Emoji, getPermission, Member, Message, @@ -11,11 +11,11 @@ import { MessageReactionRemoveEvent, PartialEmoji, PublicUserProjection, - toObject, User } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; +import { In } from "typeorm"; const router = Router(); // TODO: check if emoji is really an unicode emoji or a prperly encoded external emoji @@ -38,12 +38,12 @@ function getEmoji(emoji: string): PartialEmoji { router.delete("/", async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); + const channel = await Channel.findOneOrFail({ id: channel_id }); const permissions = await getPermission(req.user_id, undefined, channel_id); permissions.hasThrow("MANAGE_MESSAGES"); - await Message.findOneOrFailAndUpdate({ id: message_id, channel_id }, { reactions: [] }, { new: true }); + await Message.update({ id: message_id, channel_id }, { reactions: [] }); await emitEvent({ event: "MESSAGE_REACTION_REMOVE_ALL", @@ -62,8 +62,6 @@ router.delete("/:emoji", async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); - const permissions = await getPermission(req.user_id, undefined, channel_id); permissions.hasThrow("MANAGE_MESSAGES"); @@ -73,18 +71,19 @@ router.delete("/:emoji", async (req: Request, res: Response) => { if (!already_added) throw new HTTPError("Reaction not found", 404); message.reactions.remove(already_added); - await Message.update({ id: message_id, channel_id }, message); - - await emitEvent({ - event: "MESSAGE_REACTION_REMOVE_EMOJI", - channel_id, - data: { + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_REACTION_REMOVE_EMOJI", channel_id, - message_id, - guild_id: channel.guild_id, - emoji - } - } as MessageReactionRemoveEmojiEvent); + data: { + channel_id, + message_id, + guild_id: message.guild_id, + emoji + } + } as MessageReactionRemoveEmojiEvent) + ]); res.sendStatus(204); }); @@ -94,14 +93,18 @@ router.get("/:emoji", async (req: Request, res: Response) => { const emoji = getEmoji(req.params.emoji); const message = await Message.findOneOrFail({ id: message_id, channel_id }); - if (!message) throw new HTTPError("Message not found", 404); const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); if (!reaction) throw new HTTPError("Reaction not found", 404); const permissions = await getPermission(req.user_id, undefined, channel_id); permissions.hasThrow("VIEW_CHANNEL"); - const users = await User.find({ id: { $in: reaction.user_ids } }, PublicUserProjection); + const users = await User.find({ + where: { + id: In(reaction.user_ids) + }, + select: PublicUserProjection + }); res.json(users); }); @@ -111,7 +114,7 @@ router.put("/:emoji/:user_id", async (req: Request, res: Response) => { if (user_id !== "@me") throw new HTTPError("Invalid user"); const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); + const channel = await Channel.findOneOrFail({ id: channel_id }); const message = await Message.findOneOrFail({ id: message_id, channel_id }); const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); @@ -156,7 +159,7 @@ router.delete("/:emoji/:user_id", async (req: Request, res: Response) => { const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); + const channel = await Channel.findOneOrFail({ id: channel_id }); const message = await Message.findOneOrFail({ id: message_id, channel_id }); const permissions = await getPermission(req.user_id, undefined, channel_id); diff --git a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts index 028a5d73..5c486676 100644 --- a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts @@ -3,6 +3,7 @@ import { Channel, Config, emitEvent, getPermission, MessageDeleteBulkEvent, Mess import { HTTPError } from "lambert-server"; import { check } from "../../../../util/instanceOf"; +import { In } from "typeorm"; const router: Router = Router(); @@ -13,10 +14,10 @@ export default router; // https://discord.com/developers/docs/resources/channel#bulk-delete-messages router.post("/", check({ messages: [String] }), async (req: Request, res: Response) => { const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }, { permission_overwrites: true, guild_id: true }); + const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400); - const permission = await getPermission(req.user_id, channel?.guild_id, channel_id, { channel }); + const permission = await getPermission(req.user_id, channel?.guild_id, channel_id); permission.hasThrow("MANAGE_MESSAGES"); const { maxBulkDelete } = Config.get().limits.message; @@ -25,7 +26,7 @@ router.post("/", check({ messages: [String] }), async (req: Request, res: Respon if (messages.length < 2) throw new HTTPError("You must at least specify 2 messages to bulk delete"); if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`); - await Message.deleteMany({ id: { $in: messages } }); + await Message.delete({ id: In(messages) }); await emitEvent({ event: "MESSAGE_DELETE_BULK", diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index b5c650bb..6307c022 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { Attachment, Channel, ChannelType, getPermission, MessageDocument, Message, toObject } from "@fosscord/util"; +import { Attachment, Channel, ChannelType, getPermission, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { MessageCreateSchema } from "../../../../schema/Message"; import { check, instanceOf, Length } from "../../../../util/instanceOf"; @@ -7,6 +7,7 @@ import multer from "multer"; import { Query } from "mongoose"; import { sendMessage } from "../../../../util/Message"; import { uploadFile } from "../../../../util/cdn"; +import { FindManyOptions, LessThan, MoreThan } from "typeorm"; const router: Router = Router(); @@ -55,24 +56,21 @@ router.get("/", async (req: Request, res: Response) => { permissions.hasThrow("VIEW_CHANNEL"); if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); - var query: Query; - if (after) query = Message.find({ channel_id, id: { $gt: after } }); - else if (before) query = Message.find({ channel_id, id: { $lt: before } }); - else if (around) - query = Message.find({ - channel_id, - id: { $gt: (BigInt(around) - BigInt(halfLimit)).toString(), $lt: (BigInt(around) + BigInt(halfLimit)).toString() } - }); - else { - query = Message.find({ channel_id }); - } + var query: FindManyOptions & { where: { id?: any } } = { order: { id: "DESC" }, take: limit, where: { channel_id } }; - query = query.sort({ id: -1 }); + if (after) query.where.id = MoreThan(after); + else if (before) query.where.id = LessThan(before); + else if (around) { + query.where.id = [ + MoreThan((BigInt(around) - BigInt(halfLimit)).toString()), + LessThan((BigInt(around) + BigInt(halfLimit)).toString()) + ]; + } - const messages = await query.limit(limit); + const messages = await Message.find(query); return res.json(messages).map((x) => { - (x.reactions || []).forEach((x) => { + (x.reactions || []).forEach((x: any) => { // @ts-ignore if ((x.user_ids || []).includes(req.user_id)) x.me = true; // @ts-ignore @@ -135,7 +133,7 @@ router.post("/", messageUpload.single("file"), async (req: Request, res: Respons embeds, channel_id, attachments, - edited_timestamp: null + edited_timestamp: undefined }); return res.send(data); diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts index 2990e35b..9c49542b 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/api/src/routes/channels/#channel_id/permissions.ts @@ -1,4 +1,4 @@ -import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, getPermission, Member, Role, toObject } from "@fosscord/util"; +import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, getPermission, Member, Role } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; @@ -11,16 +11,16 @@ router.put("/:overwrite_id", check({ allow: String, deny: String, type: Number, const { channel_id, overwrite_id } = req.params; const body = req.body as { allow: bigint; deny: bigint; type: number; id: string }; - var channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true, permission_overwrites: true }); + var channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); permissions.hasThrow("MANAGE_ROLES"); if (body.type === 0) { - if (!(await Role.exists({ id: overwrite_id }))) throw new HTTPError("role not found", 404); + if (!(await Role.count({ id: overwrite_id }))) throw new HTTPError("role not found", 404); } else if (body.type === 1) { - if (!(await Member.exists({ id: overwrite_id }))) throw new HTTPError("user not found", 404); + if (!(await Member.count({ id: overwrite_id }))) throw new HTTPError("user not found", 404); } else throw new HTTPError("type not supported", 501); // @ts-ignore @@ -57,18 +57,19 @@ router.delete("/:overwrite_id", async (req: Request, res: Response) => { const permissions = await getPermission(req.user_id, undefined, channel_id); permissions.hasThrow("MANAGE_ROLES"); - const channel = await Channel.findOneOrFailAndUpdate( - { id: channel_id }, - { $pull: { permission_overwrites: { id: overwrite_id } } }, - { new: true } - ); + const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); - await emitEvent({ - event: "CHANNEL_UPDATE", - channel_id, - data: channel - } as ChannelUpdateEvent); + channel.permission_overwrites = channel.permission_overwrites.filter((x) => x.id === overwrite_id); + + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + channel_id, + data: channel + } as ChannelUpdateEvent) + ]); return res.sendStatus(204); }); diff --git a/api/src/routes/channels/#channel_id/pins.ts b/api/src/routes/channels/#channel_id/pins.ts index 3ed42ab4..d83e36ed 100644 --- a/api/src/routes/channels/#channel_id/pins.ts +++ b/api/src/routes/channels/#channel_id/pins.ts @@ -1,4 +1,4 @@ -import { Channel, ChannelPinsUpdateEvent, Config, emitEvent, getPermission, Message, MessageUpdateEvent, toObject } from "@fosscord/util"; +import { Channel, ChannelPinsUpdateEvent, Config, emitEvent, getPermission, Message, MessageUpdateEvent } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; @@ -6,35 +6,36 @@ const router: Router = Router(); router.put("/:message_id", async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }); - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); + + const message = await Message.findOneOrFail({ id: message_id }); + const permission = await getPermission(req.user_id, message.guild_id, channel_id); permission.hasThrow("VIEW_CHANNEL"); // * in dm channels anyone can pin messages -> only check for guilds - if (channel.guild_id) permission.hasThrow("MANAGE_MESSAGES"); + if (message.guild_id) permission.hasThrow("MANAGE_MESSAGES"); - const pinned_count = await Messagecount({ channel_id, pinned: true }); + const pinned_count = await Message.count({ channel_id, pinned: true }); const { maxPins } = Config.get().limits.channel; if (pinned_count >= maxPins) throw new HTTPError("Max pin count reached: " + maxPins); - await Message.update({ id: message_id }, { pinned: true }); - const message = await Message.findOneOrFail({ id: message_id }); - - await emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message - } as MessageUpdateEvent); + await Promise.all([ + Message.update({ id: message_id }, { pinned: true }), + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message + } as MessageUpdateEvent), - await emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { + emitEvent({ + event: "CHANNEL_PINS_UPDATE", channel_id, - guild_id: channel.guild_id, - last_pin_timestamp: undefined - } - } as ChannelPinsUpdateEvent); + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined + } + } as ChannelPinsUpdateEvent) + ]); res.sendStatus(204); }); @@ -48,23 +49,28 @@ router.delete("/:message_id", async (req: Request, res: Response) => { permission.hasThrow("VIEW_CHANNEL"); if (channel.guild_id) permission.hasThrow("MANAGE_MESSAGES"); - const message = await Message.findOneOrFailAndUpdate({ id: message_id }, { pinned: false }, { new: true }); + const message = await Message.findOneOrFail({ id: message_id }); + message.pinned = false; - await emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message - } as MessageUpdateEvent); + await Promise.all([ + message.save(), + + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message + } as MessageUpdateEvent), - await emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { + emitEvent({ + event: "CHANNEL_PINS_UPDATE", channel_id, - guild_id: channel.guild_id, - last_pin_timestamp: undefined - } - } as ChannelPinsUpdateEvent); + data: { + channel_id, + guild_id: channel.guild_id, + last_pin_timestamp: undefined + } + } as ChannelPinsUpdateEvent) + ]); res.sendStatus(204); }); diff --git a/api/src/routes/channels/#channel_id/typing.ts b/api/src/routes/channels/#channel_id/typing.ts index 298d2f50..2305e8e8 100644 --- a/api/src/routes/channels/#channel_id/typing.ts +++ b/api/src/routes/channels/#channel_id/typing.ts @@ -1,4 +1,4 @@ -import { Channel, emitEvent, Member, toObject, TypingStartEvent } from "@fosscord/util"; +import { Channel, emitEvent, Member, TypingStartEvent } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; @@ -17,7 +17,7 @@ router.post("/", async (req: Request, res: Response) => { channel_id: channel_id, data: { // this is the paylod - member: member, + member: { ...member, roles: member.role_ids }, channel_id, timestamp, user_id, diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index 38a8226b..775053ba 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -10,7 +10,7 @@ const router: Router = Router(); // TODO: use Image Data Type for avatar instead of String router.post("/", check({ name: new Length(String, 1, 80), $avatar: String }), async (req: Request, res: Response) => { const channel_id = req.params.channel_id; - const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true, type: true }); + const channel = await Channel.findOneOrFail({ id: channel_id }); isTextChannel(channel.type); if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400); diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index cbc0b0fa..b84a68a7 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -1,22 +1,17 @@ import { Request, Response, Router } from "express"; -import { BanModel, emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, toObject } from "@fosscord/util"; +import { emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { getIpAdress } from "../../../util/ipAddress"; import { BanCreateSchema } from "../../../schema/Ban"; import { check } from "../../../util/instanceOf"; -import { removeMember } from "../../../util/Member"; -import { getPublicUser } from "../../../util/User"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await Guild.exists({ id: guild_id }); - if (!guild) throw new HTTPError("Guild not found", 404); - - var bans = await Ban.find({ guild_id: guild_id }, { user_id: true, reason: true }); + var bans = await Ban.find({ guild_id: guild_id }); return res.json(bans); }); @@ -32,29 +27,32 @@ router.put("/:user_id", check(BanCreateSchema), async (req: Request, res: Respon const { guild_id } = req.params; const banned_user_id = req.params.user_id; - const banned_user = await getPublicUser(banned_user_id); + const banned_user = await User.getPublicUser(banned_user_id); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("BAN_MEMBERS"); if (req.user_id === banned_user_id) throw new HTTPError("You can't ban yourself", 400); + if (perms.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); - await removeMember(banned_user_id, guild_id); - - const ban = await new BanModel({ + const ban = new Ban({ user_id: banned_user_id, guild_id: guild_id, ip: getIpAdress(req), executor_id: req.user_id, reason: req.body.reason // || otherwise empty - }).save(); + }); - await emitEvent({ - event: "GUILD_BAN_ADD", - data: { - guild_id: guild_id, - user: banned_user - }, - guild_id: guild_id - } as GuildBanAddEvent); + await Promise.all([ + Member.removeFromGuild(banned_user_id, guild_id), + ban.save(), + emitEvent({ + event: "GUILD_BAN_ADD", + data: { + guild_id: guild_id, + user: banned_user + }, + guild_id: guild_id + } as GuildBanAddEvent) + ]); return res.json(ban); }); @@ -63,26 +61,25 @@ router.delete("/:user_id", async (req: Request, res: Response) => { var { guild_id } = req.params; var banned_user_id = req.params.user_id; - const banned_user = await getPublicUser(banned_user_id); - const guild = await Guild.exists({ id: guild_id }); - if (!guild) throw new HTTPError("Guild not found", 404); - + const banned_user = await User.getPublicUser(banned_user_id); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("BAN_MEMBERS"); - await Ban.deleteOne({ - user_id: banned_user_id, - guild_id - }); - - await emitEvent({ - event: "GUILD_BAN_REMOVE", - data: { - guild_id, - user: banned_user - }, - guild_id - } as GuildBanRemoveEvent); + await Promise.all([ + Ban.delete({ + user_id: banned_user_id, + guild_id + }), + + emitEvent({ + event: "GUILD_BAN_REMOVE", + data: { + guild_id, + user: banned_user + }, + guild_id + } as GuildBanRemoveEvent) + ]); return res.status(204).send(); }); diff --git a/api/src/routes/guilds/#guild_id/delete.ts b/api/src/routes/guilds/#guild_id/delete.ts index a53271ce..043260e9 100644 --- a/api/src/routes/guilds/#guild_id/delete.ts +++ b/api/src/routes/guilds/#guild_id/delete.ts @@ -1,4 +1,4 @@ -import { Channel, emitEvent, EmojiModel, GuildDeleteEvent, Guild, InviteModel, Member, Message, Role, User } from "@fosscord/util"; +import { Channel, emitEvent, GuildDeleteEvent, Guild, Member, Message, Role, Invite, Emoji } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; @@ -9,7 +9,7 @@ const router = Router(); router.post("/", async (req: Request, res: Response) => { var { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }, "owner_id"); + 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 emitEvent({ @@ -21,14 +21,13 @@ router.post("/", async (req: Request, res: Response) => { } as GuildDeleteEvent); await Promise.all([ - Guild.deleteOne({ id: guild_id }), - User.updateMany({ guilds: guild_id }, { $pull: { guilds: guild_id } }), - Role.deleteMany({ guild_id }), - Channel.deleteMany({ guild_id }), - Emoji.deleteMany({ guild_id }), - Invite.deleteMany({ guild_id }), - Message.deleteMany({ guild_id }), - Member.deleteMany({ guild_id }) + Guild.delete({ id: guild_id }), + Role.delete({ guild_id }), + Channel.delete({ guild_id }), + Emoji.delete({ guild_id }), + Invite.delete({ guild_id }), + Message.delete({ guild_id }), + Member.delete({ guild_id }) ]); return res.sendStatus(204); diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index af9ea9d6..d205b164 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -1,19 +1,5 @@ import { Request, Response, Router } from "express"; -import { - Channel, - emitEvent, - EmojiModel, - getPermission, - GuildDeleteEvent, - Guild, - GuildUpdateEvent, - InviteModel, - Member, - Message, - Role, - toObject, - User -} from "@fosscord/util"; +import { emitEvent, getPermission, Guild, GuildUpdateEvent, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { GuildUpdateSchema } from "../../../schema/Guild"; @@ -26,9 +12,15 @@ const router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }).populate({ path: "joined_at", match: { id: req.user_id } }); - const member = await Member.exists({ 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); + const [guild, member_count, member] = await Promise.all([ + Guild.findOneOrFail({ id: guild_id }), + Member.count({ guild_id: guild_id, id: req.user_id }), + Member.findOneOrFail(req.user_id) + ]); + if (!member_count) throw new HTTPError("You are not a member of the guild you are trying to access", 401); + + // @ts-ignore + guild.joined_at = member?.joined_at; return res.json(guild); }); @@ -45,15 +37,12 @@ router.patch("/", check(GuildUpdateSchema), async (req: Request, res: Response) if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner); if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash); - const guild = await Guild.findOneOrFailAndUpdate({ id: guild_id }, body, { new: true }).populate({ - path: "joined_at", - match: { id: req.user_id } - }); - const data = guild; + const guild = await Guild.findOneOrFail({ id: guild_id }); + guild.assign(body); - emitEvent({ event: "GUILD_UPDATE", data: data, guild_id } as GuildUpdateEvent); + await Promise.all([guild.save(), emitEvent({ event: "GUILD_UPDATE", data: guild, guild_id } as GuildUpdateEvent)]); - return res.json(data); + return res.json(guild); }); export default router; diff --git a/api/src/routes/guilds/#guild_id/invites.ts b/api/src/routes/guilds/#guild_id/invites.ts index ca72cce8..1843b689 100644 --- a/api/src/routes/guilds/#guild_id/invites.ts +++ b/api/src/routes/guilds/#guild_id/invites.ts @@ -1,4 +1,4 @@ -import { getPermission, InviteModel, toObject } from "@fosscord/util"; +import { getPermission, Invite } from "@fosscord/util"; import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts index 1dacbdad..db29cd08 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts @@ -3,7 +3,6 @@ import { Guild, Member, User, - toObject, GuildMemberAddEvent, getPermission, PermissionResolvable, @@ -12,15 +11,15 @@ import { emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { addMember, isMember, removeMember } from "../../../../../util/Member"; import { check } from "../../../../../util/instanceOf"; import { MemberChangeSchema } from "../../../../../schema/Member"; +import { In } from "typeorm"; const router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id, member_id } = req.params; - await isMember(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); const member = await Member.findOneOrFail({ id: member_id, guild_id }); @@ -31,18 +30,22 @@ router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) const { guild_id, member_id } = req.params; const body = req.body as MemberChangeSchema; if (body.roles) { - const roles = await Role.find({ id: { $in: body.roles } }); + const roles = await Role.find({ id: In(body.roles) }); if (body.roles.length !== roles.length) throw new HTTPError("Roles not found", 404); // TODO: check if user has permission to add role } - const member = await Member.findOneOrFailAndUpdate({ id: member_id, guild_id }, body, { new: true }); + const member = await Member.findOneOrFail({ id: member_id, guild_id }); + member.assign(req.body); - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - guild_id, - data: member - } as GuildMemberUpdateEvent); + Promise.all([ + member.save(), + emitEvent({ + event: "GUILD_MEMBER_UPDATE", + guild_id, + data: { ...member, roles: member.role_ids } + } as GuildMemberUpdateEvent) + ]); res.json(member); }); @@ -52,7 +55,7 @@ router.put("/", async (req: Request, res: Response) => { throw new HTTPError("Maintenance: Currently you can't add a member", 403); // TODO: only for oauth2 applications - await addMember(member_id, guild_id); + await Member.addToGuild(member_id, guild_id); res.sendStatus(204); }); @@ -62,7 +65,7 @@ router.delete("/", async (req: Request, res: Response) => { const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("KICK_MEMBERS"); - await removeMember(member_id, guild_id); + await Member.removeFromGuild(member_id, guild_id); res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts b/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts index e4308364..3f2975e6 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts @@ -1,8 +1,7 @@ -import { getPermission, PermissionResolvable } from "@fosscord/util"; +import { getPermission, Member, PermissionResolvable } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { check } from "lambert-server"; import { MemberNickChangeSchema } from "../../../../../schema/Member"; -import { changeNickname } from "../../../../../util/Member"; const router = Router(); @@ -17,7 +16,7 @@ router.patch("/", check(MemberNickChangeSchema), async (req: Request, res: Respo const perms = await getPermission(req.user_id, guild_id); perms.hasThrow(permissionString); - await changeNickname(member_id, guild_id, req.body.nick); + await Member.changeNickname(member_id, guild_id, req.body.nick); res.status(200).send(); }); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts index fad0695e..cb9bad9a 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts @@ -1,6 +1,5 @@ -import { getPermission } from "@fosscord/util"; +import { getPermission, Member } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { addRole, removeRole } from "../../../../../../../util/Member"; const router = Router(); @@ -10,7 +9,7 @@ router.delete("/:member_id/roles/:role_id", async (req: Request, res: Response) const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_ROLES"); - await removeRole(member_id, guild_id, role_id); + await Member.removeRole(member_id, guild_id, role_id); res.sendStatus(204); }); @@ -20,7 +19,7 @@ router.put("/:member_id/roles/:role_id", async (req: Request, res: Response) => const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_ROLES"); - await addRole(member_id, guild_id, role_id); + await Member.addRole(member_id, guild_id, role_id); res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/api/src/routes/guilds/#guild_id/members/index.ts index 656d3acd..0bfd71cb 100644 --- a/api/src/routes/guilds/#guild_id/members/index.ts +++ b/api/src/routes/guilds/#guild_id/members/index.ts @@ -1,8 +1,7 @@ import { Request, Response, Router } from "express"; -import { Guild, Member, toObject } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; +import { Guild, Member, PublicMemberProjection } from "@fosscord/util"; import { instanceOf, Length } from "../../../../util/instanceOf"; -import { PublicMemberProjection, isMember } from "../../../../util/Member"; +import { MoreThan } from "typeorm"; const router = Router(); @@ -11,7 +10,7 @@ const router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); - await isMember(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); try { instanceOf({ $limit: new Length(Number, 1, 1000), $after: String }, req.query, { @@ -23,12 +22,16 @@ router.get("/", async (req: Request, res: Response) => { return res.status(400).json({ code: 50035, message: "Invalid Query", success: false, errors: error }); } - // @ts-ignore - if (!req.query.limit) req.query.limit = 1; - const { limit, after } = (req.query) as { limit: number; after: string }; - const query = after ? { id: { $gt: after } } : {}; + const { limit, after } = (req.query) as { limit?: number; after?: string }; + const query = after ? { id: MoreThan(after) } : {}; + + const members = await Member.find({ + where: { guild_id, ...query }, + select: PublicMemberProjection, + take: limit || 1, + order: { id: "ASC" } + }); - var members = await Member.find({ guild_id, ...query }, PublicMemberProjection).limit(limit); return res.json(members); }); diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index 5ebc0580..e9e777b9 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -1,10 +1,7 @@ import { Request, Response, Router } from "express"; import { Role, - Guild, getPermission, - toObject, - User, Snowflake, Member, GuildRoleCreateEvent, @@ -16,15 +13,13 @@ import { HTTPError } from "lambert-server"; import { check } from "../../../util/instanceOf"; import { RoleModifySchema } from "../../../schema/Roles"; -import { getPublicUser } from "../../../util/User"; -import { isMember } from "../../../util/Member"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { const guild_id = req.params.guild_id; - await isMember(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); const roles = await Role.find({ guild_id: guild_id }); @@ -35,12 +30,8 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => const guild_id = req.params.guild_id; const body = req.body as RoleModifySchema; - const guild = await Guild.findOneOrFail({ id: guild_id }, { id: true }); - const user = await User.findOneOrFail({ id: req.user_id }); - const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_ROLES"); - if (!body.name) throw new HTTPError("You need to specify a name"); const role = await new Role({ ...body, @@ -49,7 +40,7 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => managed: false, position: 0, tags: null, - permissions: body.permissions || 0n + permissions: perms.bitfield & (body.permissions || 0n) }).save(); await emitEvent({ @@ -72,19 +63,20 @@ router.delete("/:role_id", async (req: Request, res: Response) => { const permissions = await getPermission(req.user_id, guild_id); permissions.hasThrow("MANAGE_ROLES"); - await Role.deleteOne({ - id: role_id, - guild_id: guild_id - }); - - await emitEvent({ - event: "GUILD_ROLE_DELETE", - guild_id, - data: { + await Promise.all([ + Role.delete({ + id: role_id, + guild_id: guild_id + }), + emitEvent({ + event: "GUILD_ROLE_DELETE", guild_id, - role_id - } - } as GuildRoleDeleteEvent); + data: { + guild_id, + role_id + } + } as GuildRoleDeleteEvent) + ]); res.sendStatus(204); }); @@ -96,30 +88,22 @@ router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Res const { role_id } = req.params; const body = req.body as RoleModifySchema; - const guild = await Guild.findOneOrFail({ id: guild_id }, { id: true }); - const user = await User.findOneOrFail({ id: req.user_id }); - const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_ROLES"); - const role = await Role.findOneOrFailAndUpdate( - { - id: role_id, - guild_id: guild_id - }, - // @ts-ignore - body, - { new: true } - ); + const role = new Role({ ...body, role_id, guild_id, permissions: perms.bitfield & (body.permissions || 0n) }); - await emitEvent({ - event: "GUILD_ROLE_UPDATE", - guild_id, - data: { + await Promise.all([ + role.save(), + emitEvent({ + event: "GUILD_ROLE_UPDATE", guild_id, - role - } - } as GuildRoleUpdateEvent); + data: { + guild_id, + role + } + } as GuildRoleUpdateEvent) + ]); res.json(role); }); diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index 335cea27..58940b42 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -1,8 +1,7 @@ -import { Channel, ChannelType, getPermission, Guild, InviteModel, trimSpecial } from "@fosscord/util"; +import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; import { check, Length } from "../../../util/instanceOf"; -import { isMember } from "../../../util/Member"; const router = Router(); @@ -14,42 +13,37 @@ router.get("/", async (req: Request, res: Response) => { const permission = await getPermission(req.user_id, guild_id); permission.hasThrow("MANAGE_GUILD"); - const guild = await Guild.findOneOrFail({ id: guild_id }); - if (!guild.vanity_url_code) return res.json({ code: null }); - const { uses } = await Invite.findOneOrFail({ code: guild.vanity_url_code }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id }, relations: ["vanity_url"] }); + if (!guild.vanity_url) return res.json({ code: null }); - return res.json({ code: guild.vanity_url_code, uses }); + return res.json({ code: guild.vanity_url_code, uses: guild.vanity_url.uses }); }); // TODO: check if guild is elgible for vanity url router.patch("/", check({ code: new Length(String, 0, 20) }), async (req: Request, res: Response) => { const { guild_id } = req.params; - var code = req.body.code.replace(InviteRegex); - if (!code) code = null; + const code = req.body.code.replace(InviteRegex); + + await Invite.findOneOrFail({ code }); const guild = await Guild.findOneOrFail({ id: guild_id }); - const permission = await getPermission(req.user_id, guild_id, undefined, { guild }); + const permission = await getPermission(req.user_id, guild_id); permission.hasThrow("MANAGE_GUILD"); - const alreadyExists = await Promise.all([ - Guild.findOneOrFail({ vanity_url_code: code }).catch(() => null), - Invite.findOneOrFail({ code: code }).catch(() => null) - ]); - if (alreadyExists.some((x) => x)) throw new HTTPError("Vanity url already exists", 400); - - await Guild.update({ id: guild_id }, { vanity_url_code: code }); const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT }); - await Invite.update( - { code: guild.vanity_url_code }, - { + guild.vanity_url_code = code; + + Promise.all([ + guild.save(), + Invite.delete({ code: guild.vanity_url_code }), + new Invite({ code: code, uses: 0, created_at: new Date(), guild_id, channel_id: id - }, - { upsert: true } - ); + }).save() + ]); return res.json({ code: code }); }); diff --git a/api/src/routes/guilds/#guild_id/welcome_screen.ts b/api/src/routes/guilds/#guild_id/welcome_screen.ts index b457efb6..defbcd40 100644 --- a/api/src/routes/guilds/#guild_id/welcome_screen.ts +++ b/api/src/routes/guilds/#guild_id/welcome_screen.ts @@ -1,11 +1,9 @@ import { Request, Response, Router } from "express"; -import { Guild, getPermission, toObject, Snowflake } from "@fosscord/util"; +import { Guild, getPermission, Snowflake, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { check } from "../../../util/instanceOf"; -import { isMember } from "../../../util/Member"; -import { GuildAddChannelToWelcomeScreenSchema } from "../../../schema/Guild"; -import { getPublicUser } from "../../../util/User"; +import { GuildUpdateWelcomeScreenSchema } from "../../../schema/Guild"; const router: Router = Router(); @@ -14,34 +12,24 @@ router.get("/", async (req: Request, res: Response) => { const guild = await Guild.findOneOrFail({ id: guild_id }); - await isMember(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); res.json(guild.welcome_screen); }); -router.post("/", check(GuildAddChannelToWelcomeScreenSchema), async (req: Request, res: Response) => { +router.patch("/", check(GuildUpdateWelcomeScreenSchema), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; - const body = req.body as GuildAddChannelToWelcomeScreenSchema; + const body = req.body as GuildUpdateWelcomeScreenSchema; const guild = await Guild.findOneOrFail({ id: guild_id }); - var channelObject = { - ...body - }; - const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); if (!guild.welcome_screen.enabled) throw new HTTPError("Welcome screen disabled", 400); - if (guild.welcome_screen.welcome_channels.some((channel) => channel.channel_id === body.channel_id)) - throw new Error("Welcome Channel exists"); - - await Guild.findOneOrFailAndUpdate( - { - id: guild_id - }, - { $push: { "welcome_screen.welcome_channels": channelObject } } - ); + if (body.welcome_channels) guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid + if (body.description) guild.welcome_screen.description = body.description; + if (body.enabled != null) guild.welcome_screen.enabled = body.enabled; res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/api/src/routes/guilds/#guild_id/widget.json.ts index 10bc3ac0..ae1f0599 100644 --- a/api/src/routes/guilds/#guild_id/widget.json.ts +++ b/api/src/routes/guilds/#guild_id/widget.json.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { Config, Permissions, Guild, InviteModel, Channel, Member } from "@fosscord/util"; +import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { random } from "../../../util/RandomInviteID"; @@ -21,7 +21,8 @@ router.get("/", async (req: Request, res: Response) => { if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404); // Fetch existing widget invite for widget channel - var invite = await Invite.findOneOrFail({ channel_id: guild.widget_channel_id, inviter_id: { $type: 10 } }); + var invite = await Invite.findOne({ 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 @@ -40,85 +41,29 @@ router.get("/", async (req: Request, res: Response) => { inviter_id: null }; - invite = await new InviteModel(body).save(); + invite = await new Invite(body).save(); } // Fetch voice channels, and the @everyone permissions object - let channels: any[] = []; - await Channel.find({ guild_id: guild_id, type: 2 }, { permission_overwrites: { $elemMatch: { id: guild_id } } }) - .select("id name position permission_overwrites") - .sort({ position: 1 }) - .cursor() - .eachAsync((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, - position: doc.position - }); - } - }); + const channels = [] as any[]; + + (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, + position: doc.position + }); + } + }); // Fetch members // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file) - let members: any[] = []; - await Member.find({ guild_id: guild_id }) - .populate({ path: "user", select: { _id: 0, username: 1, avatar: 1, presence: 1 } }) - .select("id user nick deaf mute") - .cursor() - .eachAsync((doc) => { - const status = doc.user?.presence?.status || "offline"; - if (status == "offline") return; - - let item = {}; - - item = { - ...item, - id: null, // this is updated during the sort outside of the query - username: doc.nick || doc.user?.username, - discriminator: "0000", // intended (https://github.com/discord/discord-api-docs/issues/1287) - avatar: null, // intended, avatar_url below will return a unique guild + user url to the avatar - status: status - }; - - const activity = doc.user?.presence?.activities?.[0]; - if (activity) { - item = { - ...item, - game: { name: activity.name } - }; - } - - // TODO: If the member is in a voice channel, return extra widget details - // Extra fields returned include deaf, mute, self_deaf, self_mute, supress, and channel_id (voice channel connected to) - // Get this from VoiceState - - // TODO: Implement a widget-avatar endpoint on the CDN, and implement logic here to request it - // Get unique avatar url for guild user, cdn to serve the actual avatar image on this url - /* - const avatar = doc.user?.avatar; - if (avatar) { - const CDN_HOST = Config.get().cdn.endpoint || "http://localhost:3003"; - const avatar_url = "/widget-avatars/" + ; - item = { - ...item, - avatar_url: avatar_url - } - } - */ - - members.push(item); - }); - - // Sort members, and update ids (Unable to do under the mongoose query due to https://mongoosejs.com/docs/faq.html#populate_sort_order) - members = members.sort((first, second) => 0 - (first.username > second.username ? -1 : 1)); - members.forEach((x, i) => { - x.id = i; - }); + let members = await Member.find({ where: { guild_id: guild_id } }); // Construct object to respond with const data = { diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index 05be07d9..c158c7d4 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,10 +1,8 @@ import { Router, Request, Response } from "express"; -import { Role, Guild, Snowflake, Guild, RoleDocument, Config } from "@fosscord/util"; +import { Role, Guild, Snowflake, Config, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { check } from "./../../util/instanceOf"; import { GuildCreateSchema } from "../../schema/Guild"; -import { getPublicUser } from "../../util/User"; -import { addMember } from "../../util/Member"; import { createChannel } from "../../util/Channel"; const router: Router = Router(); @@ -15,14 +13,13 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = const body = req.body as GuildCreateSchema; const { maxGuilds } = Config.get().limits.user; - const user = await getPublicUser(req.user_id, { guilds: true }); - - if (user.guilds.length >= maxGuilds) { + const guild_count = await Member.count({ where: { id: req.user_id } }); + if (guild_count >= maxGuilds) { throw new HTTPError(`Maximum number of guilds reached ${maxGuilds}`, 403); } const guild_id = Snowflake.generate(); - const guild: Guild = { + const guild = new Guild({ name: body.name, region: Config.get().regions.default, owner_id: req.user_id, @@ -62,7 +59,7 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = }, widget_channel_id: undefined, widget_enabled: false - }; + }); const [guild_doc, role] = await Promise.all([ new Guild(guild).save(), @@ -94,14 +91,14 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = body.channels?.map((x) => { var id = ids.get(x.id) || Snowflake.generate(); - // TODO: should we abort if parent_id is a category? (or not to allow sub category channels) + // TODO: should we abort if parent_id is a category? (to disallow sub category channels) var parent_id = ids.get(x.parent_id); return createChannel({ ...x, guild_id, id, parent_id }, req.user_id, { keepId: true, skipExistsCheck: true }); }) ); - await addMember(req.user_id, guild_id); + await Member.addToGuild(req.user_id, guild_id); res.status(201).json({ id: guild.id }); }); diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index b8c1012d..7a8ac886 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -1,10 +1,9 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); -import { Template, Guild, Role, Snowflake, Config, User } from "@fosscord/util"; +import { Template, Guild, Role, Snowflake, Config, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { GuildTemplateCreateSchema } from "../../../schema/Guild"; import { check } from "../../../util/instanceOf"; -import { addMember } from "../../../util/Member"; router.get("/:code", async (req: Request, res: Response) => { const { code } = req.params; @@ -19,9 +18,9 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res const body = req.body as GuildTemplateCreateSchema; const { maxGuilds } = Config.get().limits.user; - const user = await User.getPublicUser(req.user_id, { guilds: true }); - if (user.guilds.length >= maxGuilds) { + const guild_count = await Member.count({ id: req.user_id }); + if (guild_count >= maxGuilds) { throw new HTTPError(`Maximum number of guilds reached ${maxGuilds}`, 403); } @@ -29,15 +28,13 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res const guild_id = Snowflake.generate(); - const guild: Guild = { - ...body, - ...template.serialized_source_guild, - id: guild_id, - owner_id: req.user_id - }; - - const [guild_doc, role] = await Promise.all([ - new Guild(guild).save(), + const [guild, role] = await Promise.all([ + new Guild({ + ...body, + ...template.serialized_source_guild, + id: guild_id, + owner_id: req.user_id + }).save(), new Role({ id: guild_id, guild_id: guild_id, @@ -52,7 +49,7 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res }).save() ]); - await addMember(req.user_id, guild_id, { guild: guild_doc }); + await Member.addToGuild(req.user_id, guild_id); res.status(201).json({ id: guild.id }); }); diff --git a/api/src/routes/invites/index.ts b/api/src/routes/invites/index.ts index a488c44b..23fa3aec 100644 --- a/api/src/routes/invites/index.ts +++ b/api/src/routes/invites/index.ts @@ -1,14 +1,12 @@ import { Router, Request, Response } from "express"; -import { getPermission, Guild, Invite } from "@fosscord/util"; +import { getPermission, Guild, Invite, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { addMember } from "../../util/Member"; const router: Router = Router(); router.get("/:code", async (req: Request, res: Response) => { const { code } = req.params; const invite = await Invite.findOneOrFail({ code }); - if (!invite) throw new HTTPError("Unknown Invite", 404); res.status(200).send(invite); }); @@ -16,11 +14,11 @@ router.get("/:code", async (req: Request, res: Response) => { router.post("/:code", async (req: Request, res: Response) => { const { code } = req.params; - const invite = await Invite.findOneOrFailAndUpdate({ code }, { $inc: { uses: 1 } }, { new: true }); - if (!invite) throw new HTTPError("Unknown Invite", 404); - if (invite.uses >= invite.max_uses) await Invite.deleteOne({ code }); + const invite = await Invite.findOneOrFail({ code }); + if (invite.uses++ >= invite.max_uses) await Invite.delete({ code }); + else await invite.save(); - await addMember(req.user_id, invite.guild_id); + await Member.addToGuild(req.user_id, invite.guild_id); res.status(200).send(invite); }); @@ -30,15 +28,12 @@ router.delete("/:code", async (req: Request, res: Response) => { const invite = await Invite.findOneOrFail({ code }); const { guild_id, channel_id } = invite; - const guild = await Guild.findOneOrFail({ id: guild_id }); - const permission = await getPermission(req.user_id, guild_id, channel_id, { guild }); + const permission = await getPermission(req.user_id, guild_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 Invite.deleteOne({ code }); - - await Guild.update({ vanity_url_code: code }, { $unset: { vanity_url_code: 1 } }).catch((e) => {}); + await Promise.all([Invite.delete({ code }), Guild.update({ vanity_url_code: code }, { vanity_url_code: undefined })]); res.json({ invite: invite }); }); diff --git a/api/src/routes/users/#id/index.ts b/api/src/routes/users/#id/index.ts index a2ad3ae6..3841756b 100644 --- a/api/src/routes/users/#id/index.ts +++ b/api/src/routes/users/#id/index.ts @@ -1,13 +1,12 @@ import { Router, Request, Response } from "express"; -import { getPublicUser } from "../../../util/User"; -import { HTTPError } from "lambert-server"; +import { User } from "../../../../../util/dist"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { const { id } = req.params; - res.json(await getPublicUser(id)); + res.json(await User.getPublicUser(id)); }); export default router; diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index 9b2e2d5e..afccfed5 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -1,10 +1,10 @@ import { Router, Request, Response } from "express"; -import { getPublicUser } from "../../../util/User"; +import { User } from "../../../../../util/dist"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - const user = await getPublicUser(req.params.id, { data: true }); + const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] }); res.json({ connected_accounts: user.connected_accounts, diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index 28e77dd9..ab203571 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,20 +1,10 @@ import { Router, Request, Response } from "express"; -import { - Channel, - ChannelCreateEvent, - toObject, - ChannelType, - Snowflake, - trimSpecial, - Channel, - DMChannel, - User, - emitEvent -} from "@fosscord/util"; +import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { DmChannelCreateSchema } from "../../../schema/Channel"; import { check } from "../../../util/instanceOf"; +import { In } from "typeorm"; const router: Router = Router(); @@ -29,8 +19,10 @@ router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Respons body.recipients = body.recipients.filter((x) => x !== req.user_id).unique(); - if (!(await Promise.all(body.recipients.map((x) => User.exists({ id: x })))).every((x) => x)) { - throw new HTTPError("Recipient not found"); + const recipients = await User.find({ id: In(body.recipients) }); + + if (recipients.length !== body.recipients.length) { + throw new HTTPError("Recipient/s not found"); } const type = body.recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM; @@ -40,13 +32,12 @@ router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Respons name, type, owner_id: req.user_id, - id: Snowflake.generate(), created_at: new Date(), last_message_id: null, recipient_ids: [...body.recipients, req.user_id] }).save(); - await emitEvent({ event: "CHANNEL_CREATE", data: channel), user_id: req.user_id } as ChannelCreateEvent; + await emitEvent({ event: "CHANNEL_CREATE", data: channel, user_id: req.user_id } as ChannelCreateEvent); res.json(channel); }); diff --git a/api/src/routes/users/@me/delete.ts b/api/src/routes/users/@me/delete.ts index fa8134cc..6bfe0b93 100644 --- a/api/src/routes/users/@me/delete.ts +++ b/api/src/routes/users/@me/delete.ts @@ -5,12 +5,19 @@ const router = Router(); router.post("/", async (req: Request, res: Response) => { const user = await User.findOneOrFail({ id: req.user_id }); //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 :/ + } + + // TODO: decrement guild member count - let correctpass = await bcrypt.compare(req.body.password, user!.data.hash); //Not sure if user typed right password :/ if (correctpass) { await Promise.all([ - User.deleteOne({ id: req.user_id }), //Yeetus user deletus - Member.deleteMany({ id: req.user_id }) + User.delete({ id: req.user_id }), //Yeetus user deletus + Member.delete({ id: req.user_id }) ]); res.sendStatus(204); diff --git a/api/src/routes/users/@me/disable.ts b/api/src/routes/users/@me/disable.ts index a40c9e59..ed1dedcc 100644 --- a/api/src/routes/users/@me/disable.ts +++ b/api/src/routes/users/@me/disable.ts @@ -5,9 +5,14 @@ import bcrypt from "bcrypt"; const router = Router(); router.post("/", async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ id: req.user_id }); //User object + const user = await User.findOneOrFail(req.user_id); //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 :/ + } - let 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 }); diff --git a/api/src/routes/users/@me/guilds.ts b/api/src/routes/users/@me/guilds.ts index e40bfec9..fb88281b 100644 --- a/api/src/routes/users/@me/guilds.ts +++ b/api/src/routes/users/@me/guilds.ts @@ -1,31 +1,26 @@ import { Router, Request, Response } from "express"; -import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, toObject, emitEvent } from "@fosscord/util"; +import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; - -import { getPublicUser } from "../../../util/User"; +import { In } from "typeorm"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ id: req.user_id }, { guilds: true }); - if (!user) throw new HTTPError("User not found", 404); + const members = await Member.find({ relations: ["guild"], where: { id: req.user_id } }); - var guildIDs = user.guilds || []; - var guild = await Guild.find({ id: { $in: guildIDs } }).populate({ path: "joined_at", match: { id: req.user_id } }); - res.json(guild); + res.json(members.map((x) => x.guild)); }); // user send to leave a certain guild router.delete("/:id", async (req: Request, res: Response) => { const guild_id = req.params.id; - const guild = await Guild.findOneOrFail({ id: guild_id }, { guild_id: true }); + 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); await Promise.all([ - Member.deleteOne({ id: req.user_id, guild_id: guild_id }), - User.update({ id: req.user_id }, { $pull: { guilds: guild_id } }), + Member.delete({ id: req.user_id, guild_id: guild_id }), emitEvent({ event: "GUILD_DELETE", data: { @@ -35,7 +30,7 @@ router.delete("/:id", async (req: Request, res: Response) => { } as GuildDeleteEvent) ]); - const user = await getPublicUser(req.user_id); + const user = await User.getPublicUser(req.user_id); await emitEvent({ event: "GUILD_MEMBER_REMOVE", diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 903b24aa..274cfb24 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,6 +1,5 @@ import { Router, Request, Response } from "express"; -import { User, toObject, PublicUserProjection } from "@fosscord/util"; -import { getPublicUser } from "../../../util/User"; +import { User } from "@fosscord/util"; import { UserModifySchema } from "../../../schema/User"; import { check } from "../../../util/instanceOf"; import { handleFile } from "../../../util/cdn"; @@ -8,29 +7,29 @@ import { handleFile } from "../../../util/cdn"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - res.json(await getPublicUser(req.user_id)); + res.json(await User.getPublicUser(req.user_id)); }); -const UserUpdateProjection = { - accent_color: true, - avatar: true, - banner: true, - bio: true, - bot: true, - discriminator: true, - email: true, - flags: true, - id: true, - locale: true, - mfa_enabled: true, - nsfw_alllowed: true, - phone: true, - public_flags: true, - purchased_flags: true, - // token: true, // this isn't saved in the db and needs to be set manually - username: true, - verified: true -}; +const UserUpdateProjection = [ + "accent_color", + "avatar", + "banner", + "bio", + "bot", + "discriminator", + "email", + "flags", + "id", + "locale", + "mfa_enabled", + "nsfw_alllowed", + "phone", + "public_flags", + "purchased_flags", + // "token", // this isn't saved in the db and needs to be set manually + "username", + "verified" +]; router.patch("/", check(UserModifySchema), async (req: Request, res: Response) => { const body = req.body as UserModifySchema; @@ -38,7 +37,7 @@ router.patch("/", check(UserModifySchema), async (req: Request, res: Response) = if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); - const user = await User.findOneOrFailAndUpdate({ id: req.user_id }, body, { projection: UserUpdateProjection, new: true }); + const user = await new User({ ...body }, { id: req.user_id }).save(); // TODO: dispatch user update event res.json(user); diff --git a/api/src/routes/users/@me/profile.ts b/api/src/routes/users/@me/profile.ts index fdb969dc..5ba03c68 100644 --- a/api/src/routes/users/@me/profile.ts +++ b/api/src/routes/users/@me/profile.ts @@ -1,13 +1,13 @@ import { Router, Request, Response } from "express"; -import { getPublicUser } from "../../../util/User"; +import { User } from "../../../../../util/dist"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - const user = await getPublicUser(req.user_id, { data: true }); + const user = await User.getPublicUser(req.user_id, { relations: ["connected_accounts"] }); res.json({ - connected_accounts: user.data.connected_accounts, + connected_accounts: user.connected_accounts, premium_guild_since: null, // TODO premium_since: null, // TODO user: { diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 8c7469ed..1a89b110 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -2,11 +2,10 @@ import { RelationshipAddEvent, User, PublicUserProjection, - toObject, RelationshipType, RelationshipRemoveEvent, - UserDocument, - emitEvent + emitEvent, + Relationship } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; @@ -15,40 +14,36 @@ import { check, Length } from "../../../util/instanceOf"; const router = Router(); -const userProjection = { "data.relationships": true, ...PublicUserProjection }; +const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection]; router.get("/", async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ id: req.user_id }, { data: { relationships: true } }).populate({ - path: "data.relationships.id", - model: User - }); - return res.json(user.data.relationships); + const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["relationships"] }); + + return res.json(user.relationships); }); -async function addRelationship(req: Request, res: Response, friend: UserDocument, type: RelationshipType) { +async function updateRelationship(req: Request, res: Response, friend: User, type: RelationshipType) { const id = friend.id; if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); - const user = await User.findOneOrFail({ id: req.user_id }, userProjection); - const newUserRelationships = [...user.data.relationships]; - const newFriendRelationships = [...friend.data.relationships]; + const user = await User.findOneOrFail(req.user_id, { relations: ["relationships"], select: userProjection }); - var relationship = newUserRelationships.find((x) => x.id === id); - const friendRequest = newFriendRelationships.find((x) => x.id === req.user_id); + var relationship = user.relationships.find((x) => x.id === id); + const friendRequest = friend.relationships.find((x) => x.id === req.user_id); if (type === RelationshipType.blocked) { if (relationship) { if (relationship.type === RelationshipType.blocked) throw new HTTPError("You already blocked the user"); relationship.type = RelationshipType.blocked; } else { - relationship = { id, type: RelationshipType.blocked }; - newUserRelationships.push(relationship); + relationship = new Relationship({ id, type: RelationshipType.blocked }); + user.relationships.push(relationship); } if (friendRequest && friendRequest.type !== RelationshipType.blocked) { - newFriendRelationships.remove(friendRequest); + friend.relationships.remove(friendRequest); await Promise.all([ - User.update({ id: friend.id }, { "data.relationships": newFriendRelationships }), + user.save(), emitEvent({ event: "RELATIONSHIP_REMOVE", data: friendRequest, @@ -58,12 +53,12 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument } await Promise.all([ - User.update({ id: req.user_id }, { "data.relationships": newUserRelationships }), + user.save(), emitEvent({ event: "RELATIONSHIP_ADD", data: { ...relationship, - user: { ...friend, data: undefined } + user: { ...friend } }, user_id: req.user_id } as RelationshipAddEvent) @@ -72,32 +67,31 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument return res.sendStatus(204); } - var incoming_relationship = { id: req.user_id, nickname: undefined, type: RelationshipType.incoming }; - var outgoing_relationship = { id, nickname: undefined, type: RelationshipType.outgoing }; + var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming }, { id: req.user_id }); + var outgoing_relationship = new Relationship({ nickname: undefined, type: RelationshipType.outgoing }, { id }); if (friendRequest) { if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); // accept friend request - // @ts-ignore incoming_relationship = friendRequest; incoming_relationship.type = RelationshipType.friends; outgoing_relationship.type = RelationshipType.friends; - } else newFriendRelationships.push(incoming_relationship); + } else friend.relationships.push(incoming_relationship); if (relationship) { if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request"); if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request"); if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); - } else newUserRelationships.push(outgoing_relationship); + } else user.relationships.push(outgoing_relationship); await Promise.all([ - User.update({ id: req.user_id }, { "data.relationships": newUserRelationships }), - User.update({ id: friend.id }, { "data.relationships": newFriendRelationships }), + user.save(), + friend.save(), emitEvent({ event: "RELATIONSHIP_ADD", data: { ...outgoing_relationship, - user: { ...friend, data: undefined } + user: { ...friend } }, user_id: req.user_id } as RelationshipAddEvent), @@ -106,7 +100,7 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument data: { ...incoming_relationship, should_notify: true, - user: { ...user, data: undefined } + user: { ...user } }, user_id: id } as RelationshipAddEvent) @@ -116,14 +110,23 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument } router.put("/:id", check({ $type: new Length(Number, 1, 4) }), async (req: Request, res: Response) => { - return await addRelationship(req, res, await User.findOneOrFail({ id: req.params.id }), req.body.type); + return await updateRelationship( + req, + res, + await User.findOneOrFail(req.params.id, { relations: ["relationships"], select: userProjection }), + req.body.type + ); }); router.post("/", check({ discriminator: String, username: String }), async (req: Request, res: Response) => { - return await addRelationship( + return await updateRelationship( req, res, - await User.findOneOrFail(req.body as { discriminator: string; username: string }), + await User.findOneOrFail({ + relations: ["relationships"], + select: userProjection, + where: req.body as { discriminator: string; username: string } + }), req.body.type ); }); @@ -132,17 +135,15 @@ router.delete("/:id", 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({ id: req.user_id }); - if (!user) throw new HTTPError("Invalid token", 400); + const user = await User.findOneOrFail(req.user_id, { select: userProjection, relations: ["relationships"] }); + const friend = await User.findOneOrFail(id, { select: userProjection, relations: ["relationships"] }); - const friend = await User.findOneOrFail({ id }, userProjection); - if (!friend) throw new HTTPError("User not found", 404); + const relationship = user.relationships.find((x) => x.id === id); + const friendRequest = friend.relationships.find((x) => x.id === req.user_id); - const relationship = user.data.relationships.find((x) => x.id === id); - const friendRequest = friend.data.relationships.find((x) => x.id === req.user_id); if (relationship?.type === RelationshipType.blocked) { // unblock user - user.data.relationships.remove(relationship); + user.relationships.remove(relationship); await Promise.all([ user.save(), @@ -153,8 +154,8 @@ router.delete("/:id", async (req: Request, res: Response) => { if (!relationship || !friendRequest) throw new HTTPError("You are not friends with the user", 404); if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); - user.data.relationships.remove(relationship); - friend.data.relationships.remove(friendRequest); + user.relationships.remove(relationship); + friend.relationships.remove(friendRequest); await Promise.all([ user.save(), diff --git a/api/src/schema/Guild.ts b/api/src/schema/Guild.ts index e5971baf..01690ae9 100644 --- a/api/src/schema/Guild.ts +++ b/api/src/schema/Guild.ts @@ -1,4 +1,4 @@ -import { ChannelSchema, GuildChannel } from "@fosscord/util"; +import { Channel } from "@fosscord/util"; import { Length } from "../util/instanceOf"; import { ChannelModifySchema } from "./Channel"; @@ -59,54 +59,6 @@ export interface GuildUpdateSchema extends Omit { preferred_locale?: string; } -export const GuildGetSchema = { - id: true, - name: true, - icon: true, - splash: true, - discovery_splash: true, - owner: true, - owner_id: true, - permissions: true, - region: true, - afk_channel_id: true, - afk_timeout: true, - widget_enabled: true, - widget_channel_id: true, - verification_level: true, - default_message_notifications: true, - explicit_content_filter: true, - roles: true, - emojis: true, - features: true, - mfa_level: true, - application_id: true, - system_channel_id: true, - system_channel_flags: true, - rules_channel_id: true, - joined_at: true, - // large: true, - // unavailable: true, - member_count: true, - // voice_states: true, - // members: true, - // channels: true, - // presences: true, - max_presences: true, - max_members: true, - vanity_url_code: true, - description: true, - banner: true, - premium_tier: true, - premium_subscription_count: true, - preferred_locale: true, - public_updates_channel_id: true, - max_video_channel_users: true, - approximate_member_count: true, - approximate_presence_count: true - // welcome_screen: true, -}; - export const GuildTemplateCreateSchema = { name: String, $avatar: String @@ -117,16 +69,26 @@ export interface GuildTemplateCreateSchema { avatar?: string; } -export const GuildAddChannelToWelcomeScreenSchema = { - channel_id: String, - description: String, - $emoji_id: String, - emoji_name: String +export const GuildUpdateWelcomeScreenSchema = { + $welcome_channels: [ + { + channel_id: String, + description: String, + $emoji_id: String, + emoji_name: String + } + ], + $enabled: Boolean, + $description: new Length(String, 0, 140) }; -export interface GuildAddChannelToWelcomeScreenSchema { - channel_id: string; - description: string; - emoji_id?: string; - emoji_name: string; +export interface GuildUpdateWelcomeScreenSchema { + welcome_channels?: { + channel_id: string; + description: string; + emoji_id?: string; + emoji_name: string; + }[]; + enabled?: boolean; + description?: string; } diff --git a/api/src/schema/Message.ts b/api/src/schema/Message.ts index 2dd54f0c..f9bfcc67 100644 --- a/api/src/schema/Message.ts +++ b/api/src/schema/Message.ts @@ -1,6 +1,12 @@ -import { Embed, EmbedImage } from "@fosscord/util"; +import { Embed } from "@fosscord/util"; import { Length } from "../util/instanceOf"; +export const EmbedImage = { + $url: String, + $width: Number, + $height: Number +}; + export const MessageCreateSchema = { $content: new Length(String, 0, 2000), $nonce: String, diff --git a/api/src/schema/Roles.ts b/api/src/schema/Roles.ts index f662e61b..1e5f560f 100644 --- a/api/src/schema/Roles.ts +++ b/api/src/schema/Roles.ts @@ -9,7 +9,7 @@ export const RoleModifySchema = { export interface RoleModifySchema { name?: string; - permissions?: BigInt; + permissions?: bigint; color?: number; hoist?: boolean; // whether the role should be displayed separately in the sidebar mentionable?: boolean; // whether the role should be mentionable diff --git a/api/src/test/mongo_test.ts b/api/src/test/mongo_test.ts deleted file mode 100644 index bf203ea6..00000000 --- a/api/src/test/mongo_test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import mongoose, { Schema, Types } from "mongoose"; -require("mongoose-long")(mongoose); - -const userSchema = new Schema({ - id: String -}); - -const messageSchema = new Schema({ - id: String, - content: String -}); -const message = mongoose.model("message", messageSchema, "messages"); -const user = mongoose.model("user", userSchema, "users"); - -messageSchema.virtual("u", { - ref: user, - localField: "id", - foreignField: "id", - justOne: true -}); - -messageSchema.set("toObject", { virtuals: true }); -messageSchema.set("toJSON", { virtuals: true }); - -async function main() { - const conn = await mongoose.connect("mongodb://localhost:27017/lambert?readPreference=secondaryPreferred", { - useNewUrlParser: true, - useUnifiedTopology: false - }); - console.log("connected"); - - // const u = await new user({ name: "test" }).save(); - // await new message({ user: u._id, content: "test" }).save(); - - const test = await message.findOneOrFail({}).populate("u"); - // @ts-ignore - console.log(test?.toJSON()); -} - -main(); diff --git a/api/src/test/server_benchmark.ts b/api/src/test/server_benchmark.ts deleted file mode 100644 index c582ee89..00000000 --- a/api/src/test/server_benchmark.ts +++ /dev/null @@ -1,39 +0,0 @@ -// @ts-nocheck -import "missing-native-js-functions"; -import { config } from "dotenv"; -config(); -import { DiscordServer } from "../Server"; -import fetch from "node-fetch"; -import { promises } from "fs"; -const count = 100; - -async function main() { - const server = new DiscordServer({ port: 3000 }); - await server.start(); - - const tasks = []; - for (let i = 0; i < count; i++) { - tasks.push(test()); - } - - await Promise.all(tasks); - - console.log("logging in 5secs"); - setTimeout(async () => { - await test(); - - process.exit(); - }, 5000); -} -main(); - -async function test() { - const res = await fetch("http://localhost:3000/api/v8/guilds/813524615463698433/members/813524464300982272", { - headers: { - authorization: - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjgxMzUyNDQ2NDMwMDk4MjI3MiIsImlhdCI6MTYxNDAyOTc0Nn0.6WQiU4D5HHRi3sliHOQe1hsW-hZTEttvdtZuNIdviNI", - }, - }); - - return await res.text(); -} diff --git a/api/src/test/test.ts b/api/src/test/test.ts deleted file mode 100644 index c37c762b..00000000 --- a/api/src/test/test.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Snowflake } from "@fosscord/util"; - -console.log(Snowflake.deconstruct("0")); diff --git a/api/src/util/Channel.ts b/api/src/util/Channel.ts index f518aefd..bc9217ce 100644 --- a/api/src/util/Channel.ts +++ b/api/src/util/Channel.ts @@ -1,20 +1,9 @@ -import { - ChannelCreateEvent, - Channel, - ChannelType, - emitEvent, - getPermission, - Guild, - Snowflake, - TextChannel, - toObject, - VoiceChannel -} from "@fosscord/util"; +import { ChannelCreateEvent, Channel, ChannelType, emitEvent, getPermission, Snowflake } from "@fosscord/util"; import { HTTPError } from "lambert-server"; // TODO: DM channel export async function createChannel( - channel: Partial, + channel: Partial, user_id: string = "0", opts?: { keepId?: boolean; @@ -29,7 +18,7 @@ export async function createChannel( case ChannelType.GUILD_TEXT: case ChannelType.GUILD_VOICE: if (channel.parent_id && !opts?.skipExistsCheck) { - const exists = await Channel.findOneOrFail({ id: channel.parent_id }, { guild_id: true }); + const exists = await Channel.findOneOrFail({ id: channel.parent_id }); if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400); if (exists.guild_id !== channel.guild_id) throw new HTTPError("The category channel needs to be in the guild"); } diff --git a/api/src/util/Member.ts b/api/src/util/Member.ts deleted file mode 100644 index 6cb14d71..00000000 --- a/api/src/util/Member.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { - Guild, - GuildCreateEvent, - GuildDeleteEvent, - GuildMemberAddEvent, - GuildMemberRemoveEvent, - GuildMemberUpdateEvent, - Guild, - Member, - Role, - toObject, - User, - GuildDocument, - Config, - emitEvent -} from "@fosscord/util"; - -import { HTTPError } from "lambert-server"; - -import { getPublicUser } from "./User"; - -export const PublicMemberProjection = { - id: true, - guild_id: true, - nick: true, - roles: true, - joined_at: true, - pending: true, - deaf: true, - mute: true, - premium_since: true -}; - -export async function isMember(user_id: string, guild_id: string) { - const exists = await Member.exists({ id: user_id, guild_id }); - if (!exists) throw new HTTPError("You are not a member of this guild", 403); - return exists; -} - -export async function addMember(user_id: string, guild_id: string, cache?: { guild?: GuildDocument }) { - const user = await getPublicUser(user_id, { guilds: true }); - - const { maxGuilds } = Config.get().limits.user; - if (user.guilds.length >= maxGuilds) { - throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403); - } - - const guild = cache?.guild || (await Guild.findOneOrFail({ id: guild_id })); - - if (!guild) throw new HTTPError("Guild not found", 404); - - if (await Member.exists({ id: user.id, guild_id })) throw new HTTPError("You are already a member of this guild", 400); - - const member = { - id: user_id, - guild_id: guild_id, - nick: undefined, - roles: [guild_id], // @everyone role - joined_at: new Date(), - premium_since: undefined, - deaf: false, - mute: false, - pending: false - }; - - await Promise.all([ - new Member({ - ...member, - read_state: {}, - settings: { - channel_overrides: [], - message_notifications: 0, - mobile_push: true, - mute_config: null, - muted: false, - suppress_everyone: false, - suppress_roles: false, - version: 0 - } - }).save(), - - User.update({ id: user_id }, { $push: { guilds: guild_id } }), - Guild.update({ id: guild_id }, { $inc: { member_count: 1 } }), - - emitEvent({ - event: "GUILD_MEMBER_ADD", - data: { - ...member, - user, - guild_id: guild_id - }, - guild_id: guild_id - } as GuildMemberAddEvent) - ]); - - await emitEvent({ - event: "GUILD_CREATE", - data: await guild - .populate({ path: "members", match: { guild_id } }) - .populate({ path: "joined_at", match: { id: user.id } }) - .execPopulate(), - user_id - } as GuildCreateEvent); -} - -export async function removeMember(user_id: string, guild_id: string) { - const user = await getPublicUser(user_id); - - const guild = await Guild.findOneOrFail({ id: guild_id }, { owner_id: true }); - if (!guild) throw new HTTPError("Guild not found", 404); - if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild"); - if (!(await Member.exists({ id: user.id, guild_id }))) throw new HTTPError("Is not member of this guild", 404); - - // use promise all to execute all promises at the same time -> save time - return Promise.all([ - Member.deleteOne({ - id: user_id, - guild_id: guild_id - }), - User.update({ id: user.id }, { $pull: { guilds: guild_id } }), - Guild.update({ id: guild_id }, { $inc: { member_count: -1 } }), - - emitEvent({ - event: "GUILD_DELETE", - data: { - id: guild_id - }, - user_id: user_id - } as GuildDeleteEvent), - emitEvent({ - event: "GUILD_MEMBER_REMOVE", - data: { - guild_id: guild_id, - user: user - }, - guild_id: guild_id - } as GuildMemberRemoveEvent) - ]); -} - -export async function addRole(user_id: string, guild_id: string, role_id: string) { - const user = await getPublicUser(user_id); - - const role = await Role.findOneOrFail({ id: role_id, guild_id: guild_id }); - if (!role) throw new HTTPError("role not found", 404); - - var memberObj = await Member.findOneOrFailAndUpdate( - { - id: user_id, - guild_id: guild_id - }, - { $push: { roles: role_id } }, - { new: true } - ); - - if (!memberObj) throw new HTTPError("Member not found", 404); - - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - data: { - guild_id: guild_id, - user: user, - roles: memberObj.roles - }, - guild_id: guild_id - } as GuildMemberUpdateEvent); -} - -export async function removeRole(user_id: string, guild_id: string, role_id: string) { - const user = await getPublicUser(user_id); - - const role = await Role.findOneOrFail({ id: role_id, guild_id: guild_id }); - if (!role) throw new HTTPError("role not found", 404); - - var memberObj = await Member.findOneOrFailAndUpdate( - { - id: user_id, - guild_id: guild_id - }, - { $pull: { roles: role_id } }, - { new: true } - ); - - if (!memberObj) throw new HTTPError("Member not found", 404); - - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - data: { - guild_id: guild_id, - user: user, - roles: memberObj.roles - }, - guild_id: guild_id - } as GuildMemberUpdateEvent); -} - -export async function changeNickname(user_id: string, guild_id: string, nickname: string) { - const user = await getPublicUser(user_id); - - var memberObj = await Member.findOneOrFailAndUpdate( - { - id: user_id, - guild_id: guild_id - }, - { nick: nickname }, - { new: true } - ); - - if (!memberObj) throw new HTTPError("Member not found", 404); - - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - data: { - guild_id: guild_id, - user: user, - nick: nickname - }, - guild_id: guild_id - } as GuildMemberUpdateEvent); -} diff --git a/api/src/util/Message.ts b/api/src/util/Message.ts index 5561904b..70989301 100644 --- a/api/src/util/Message.ts +++ b/api/src/util/Message.ts @@ -8,18 +8,17 @@ import { getPermission, CHANNEL_MENTION, Snowflake, - PublicMemberProjection, USER_MENTION, ROLE_MENTION, Role, EVERYONE_MENTION, - HERE_MENTION + HERE_MENTION, + MessageType } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import fetch from "node-fetch"; import cheerio from "cheerio"; -import { MessageType } from "@fosscord/util/dist/util/Constants"; // TODO: check webhook, application, system author const LINK_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g; @@ -61,7 +60,7 @@ export async function handleMessage(opts: Partial): Promise { } var content = opts.content; - var mention_channels_ids = [] as string[]; + var mention_channel_ids = [] as string[]; var mention_role_ids = [] as string[]; var mention_user_ids = [] as string[]; var mention_everyone = false; @@ -70,7 +69,7 @@ export async function handleMessage(opts: Partial): Promise { if (content) { content = content.trim(); for (const [_, mention] of content.matchAll(CHANNEL_MENTION)) { - if (!mention_channels_ids.includes(mention)) mention_channels_ids.push(mention); + if (!mention_channel_ids.includes(mention)) mention_channel_ids.push(mention); } for (const [_, mention] of content.matchAll(USER_MENTION)) { @@ -92,11 +91,12 @@ export async function handleMessage(opts: Partial): Promise { } // TODO: check and put it all in the body + return { ...opts, guild_id: channel.guild_id, channel_id: opts.channel_id, - mention_channels_ids, + mention_channel_ids, mention_role_ids, mention_user_ids, mention_everyone, @@ -104,7 +104,7 @@ export async function handleMessage(opts: Partial): Promise { embeds: opts.embeds || [], reactions: opts.reactions || [], type: opts.type ?? 0 - }; + } as Message; } // TODO: cache link result in db @@ -163,10 +163,7 @@ export async function postHandleMessage(message: Message) { export async function sendMessage(opts: Partial) { const message = await handleMessage({ ...opts, id: Snowflake.generate(), timestamp: new Date() }); - const data = await new Message(message) - .populate({ path: "member", select: PublicMemberProjection }) - .populate("referenced_message") - .save(); + const data = await new Message(message).save(); await emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data } as MessageCreateEvent); -- cgit 1.5.1 From e0f2a5548ddb8db509898d4913b503c9fbfa2279 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 29 Aug 2021 16:58:23 +0200 Subject: fix rate limit --- api/src/middlewares/ErrorHandler.ts | 5 +- api/src/middlewares/RateLimit.ts | 95 +++++++++++++++++++------------------ 2 files changed, 52 insertions(+), 48 deletions(-) (limited to 'api/src') diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index 8e2cd923..0ed37bb4 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -4,7 +4,7 @@ import { FieldError } from "../util/instanceOf"; // TODO: update with new body/typorm validation export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { - if (!error) next(); + if (!error) return next(); try { let code = 400; @@ -18,7 +18,6 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne message = error.message; errors = error.errors; } else { - console.error(error); if (req.server?.options?.production) { message = "Internal Server Error"; } @@ -27,7 +26,7 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne if (httpcode > 511) httpcode = 400; - console.error(`[Error] ${code} ${req.url} ${message}`, errors || error); + console.error(`[Error] ${code} ${req.url}`, errors || error, "body:", req.body); res.status(httpcode).json({ code: code, message, errors }); } catch (error) { diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index 9601dad3..e0cf103a 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -1,6 +1,6 @@ -// @ts-nocheck -import { db, Bucket, Config, listenEvent, emitEvent } from "@fosscord/util"; +import { Config, listenEvent, emitEvent, RateLimit } from "@fosscord/util"; import { NextFunction, Request, Response, Router } from "express"; +import { LessThan } from "typeorm"; import { getIpAdress } from "../util/ipAddress"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; @@ -18,10 +18,10 @@ TODO: different for methods (GET/POST) */ -var Cache = new Map(); -const EventRateLimit = "ratelimit"; +var Cache = new Map(); +const EventRateLimit = "RATELIMIT"; -export default function RateLimit(opts: { +export default function rateLimit(opts: { bucket?: string; window: number; count: number; @@ -36,15 +36,15 @@ export default function RateLimit(opts: { }): any { return async (req: Request, res: Response, next: NextFunction): Promise => { const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); - var user_id = getIpAdress(req); - if (!opts.onlyIp && req.user_id) user_id = req.user_id; + var executor_id = getIpAdress(req); + if (!opts.onlyIp && req.user_id) executor_id = req.user_id; var max_hits = opts.count; if (opts.bot && req.user_bot) max_hits = opts.bot; if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET; else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY; - const offender = Cache.get(user_id + bucket_id) as Bucket | null; + const offender = Cache.get(executor_id + bucket_id); if (offender && offender.blocked) { const reset = offender.expires_at.getTime(); @@ -72,12 +72,12 @@ export default function RateLimit(opts: { offender.expires_at = new Date(Date.now() + opts.window * 1000); offender.blocked = false; // mongodb ttl didn't update yet -> manually update/delete - db.collection("ratelimits").update({ id: bucket_id, user_id }, { $set: offender }); - Cache.delete(user_id + bucket_id); + RateLimit.delete({ id: bucket_id, executor_id }); + Cache.delete(executor_id + bucket_id); } } next(); - const hitRouteOpts = { bucket_id, user_id, max_hits, window: opts.window }; + const hitRouteOpts = { bucket_id, executor_id, max_hits, window: opts.window }; if (opts.error || opts.success) { res.once("finish", () => { @@ -97,69 +97,74 @@ export default function RateLimit(opts: { export async function initRateLimits(app: Router) { const { routes, global, ip, error } = Config.get().limits.rate; await listenEvent(EventRateLimit, (event) => { - Cache.set(event.channel_id, event.data); + Cache.set(event.channel_id as string, event.data); event.acknowledge?.(); }); + await RateLimit.delete({ expires_at: LessThan(new Date()) }); // clean up if not already deleted + const limits = await RateLimit.find({ blocked: true }); + limits.forEach((limit) => { + Cache.set(limit.executor_id, limit); + }); setInterval(() => { Cache.forEach((x, key) => { - if (Date.now() > x.expires_at) Cache.delete(key); + if (new Date() > x.expires_at) { + Cache.delete(key); + RateLimit.delete({ executor_id: key }); + } }); }, 1000 * 60 * 10); app.use( - RateLimit({ + rateLimit({ bucket: "global", onlyIp: true, ...ip }) ); - app.use(RateLimit({ bucket: "global", ...global })); + app.use(rateLimit({ bucket: "global", ...global })); app.use( - RateLimit({ + rateLimit({ bucket: "error", error: true, onlyIp: true, ...error }) ); - app.use("/guilds/:id", RateLimit(routes.guild)); - app.use("/webhooks/:id", RateLimit(routes.webhook)); - app.use("/channels/:id", RateLimit(routes.channel)); - app.use("/auth/login", RateLimit(routes.auth.login)); - app.use("/auth/register", RateLimit({ onlyIp: true, success: true, ...routes.auth.register })); + app.use("/guilds/:id", rateLimit(routes.guild)); + app.use("/webhooks/:id", rateLimit(routes.webhook)); + app.use("/channels/:id", rateLimit(routes.channel)); + app.use("/auth/login", rateLimit(routes.auth.login)); + app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register })); } -async function hitRoute(opts: { user_id: string; bucket_id: string; max_hits: number; window: number }) { - const filter = { id: opts.bucket_id, user_id: opts.user_id }; - const { value } = await db.collection("ratelimits").findOneOrFailAndUpdate( - filter, - { - $setOnInsert: { - id: opts.bucket_id, - user_id: opts.user_id, - expires_at: new Date(Date.now() + opts.window * 1000) - }, - $inc: { - hits: 1 - } - // Conditionally update blocked doesn't work - }, - { upsert: true, returnDocument: "before" } - ); - if (!value) return; - const updateBlock = !value.blocked && value.hits >= opts.max_hits; +async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) { + var ratelimit = await RateLimit.findOne({ id: opts.bucket_id, executor_id: opts.executor_id }); + if (!ratelimit) { + ratelimit = new RateLimit({ + id: opts.bucket_id, + executor_id: opts.executor_id, + expires_at: new Date(Date.now() + opts.window * 1000), + hits: 0, + blocked: false + }); + } + + ratelimit.hits++; + + const updateBlock = !ratelimit.blocked && ratelimit.hits >= opts.max_hits; if (updateBlock) { - value.blocked = true; - Cache.set(opts.user_id + opts.bucket_id, value); + ratelimit.blocked = true; + Cache.set(opts.executor_id + opts.bucket_id, ratelimit); await emitEvent({ channel_id: EventRateLimit, event: EventRateLimit, - data: value + data: ratelimit }); - await db.collection("ratelimits").update(filter, { $set: { blocked: true } }); } else { - Cache.delete(opts.user_id); + Cache.delete(opts.executor_id); } + + await ratelimit.save(); } -- cgit 1.5.1 From 6c7abc2c7f9f6cf860751938a6900de2d9b228cc Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 29 Aug 2021 16:58:46 +0200 Subject: fix login register + message --- api/package-lock.json | 40 ++++++++++++++++++++++------------------ api/package.json | 4 ++-- api/src/routes/auth/login.ts | 2 +- api/src/routes/auth/register.ts | 18 +++++++++++++++--- api/src/schema/Message.ts | 4 ++-- 5 files changed, 42 insertions(+), 26 deletions(-) (limited to 'api/src') diff --git a/api/package-lock.json b/api/package-lock.json index bff4e940..1b673d4a 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -28,8 +28,8 @@ "i18next-http-middleware": "^3.1.3", "i18next-node-fs-backend": "^2.1.3", "jsonwebtoken": "^8.5.1", - "lambert-server": "^1.2.8", - "missing-native-js-functions": "^1.2.10", + "lambert-server": "^1.2.10", + "missing-native-js-functions": "^1.2.11", "mongoose": "^5.12.3", "mongoose-autopopulate": "^0.12.3", "mongoose-long": "^0.3.2", @@ -74,9 +74,11 @@ "dot-prop": "^6.0.1", "env-paths": "^2.2.1", "jsonwebtoken": "^8.5.1", - "missing-native-js-functions": "^1.2.10", + "lambert-server": "^1.2.10", + "missing-native-js-functions": "^1.2.11", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", + "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", "typeorm": "^0.2.37", @@ -6927,16 +6929,16 @@ } }, "node_modules/lambert-server": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.8.tgz", - "integrity": "sha512-vi/Ku/QudY+WIdGO9bc0qLfVhfuJFWXk1+etesPW1vW29sPbmevLL6IwfvCtw+/MyzRAJLOyCBfQ310a68+2QQ==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.10.tgz", + "integrity": "sha512-BHGPmpUrRklFJHPu0vAA8NBewtEd4IX80FRpV4nX9z8kHTUYHqnYHoBeUEWoUmxAeFQvQae1Axk5RQXRQk4VNw==", "dependencies": { "body-parser": "^1.19.0", "chalk": "^4.1.1", "express": "^4.17.1", "express-async-errors": "^3.1.1", "helmet": "^4.4.1", - "missing-native-js-functions": "^1.1.8" + "missing-native-js-functions": "^1.2.11" } }, "node_modules/lazystream": { @@ -7356,9 +7358,9 @@ } }, "node_modules/missing-native-js-functions": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.10.tgz", - "integrity": "sha512-sq+oAw/C3OtUyKopLNOf/+U85YNx7db6fy5nVfGVKlGdcV8tX24GjOSkcZeCAnAIjMEnlQBWTr17JXa3OJj22g==" + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.11.tgz", + "integrity": "sha512-U97IscNBL4Wg9adYjEBT46Hb0Ld5dPT8vbdwFX+TNzGrFQCc4WqoGAZouaLNFwUqxzzHZ9DVg59unwnQyeIIQg==" }, "node_modules/mixin-deep": { "version": "1.3.2", @@ -12476,9 +12478,11 @@ "env-paths": "^2.2.1", "jest": "^27.0.6", "jsonwebtoken": "^8.5.1", - "missing-native-js-functions": "^1.2.10", + "lambert-server": "^1.2.10", + "missing-native-js-functions": "^1.2.11", "node-fetch": "^2.6.1", "patch-package": "^6.4.7", + "pg": "^8.7.1", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", "typeorm": "^0.2.37", @@ -17639,16 +17643,16 @@ } }, "lambert-server": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.8.tgz", - "integrity": "sha512-vi/Ku/QudY+WIdGO9bc0qLfVhfuJFWXk1+etesPW1vW29sPbmevLL6IwfvCtw+/MyzRAJLOyCBfQ310a68+2QQ==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.10.tgz", + "integrity": "sha512-BHGPmpUrRklFJHPu0vAA8NBewtEd4IX80FRpV4nX9z8kHTUYHqnYHoBeUEWoUmxAeFQvQae1Axk5RQXRQk4VNw==", "requires": { "body-parser": "^1.19.0", "chalk": "^4.1.1", "express": "^4.17.1", "express-async-errors": "^3.1.1", "helmet": "^4.4.1", - "missing-native-js-functions": "^1.1.8" + "missing-native-js-functions": "^1.2.11" } }, "lazystream": { @@ -18009,9 +18013,9 @@ } }, "missing-native-js-functions": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.10.tgz", - "integrity": "sha512-sq+oAw/C3OtUyKopLNOf/+U85YNx7db6fy5nVfGVKlGdcV8tX24GjOSkcZeCAnAIjMEnlQBWTr17JXa3OJj22g==" + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.11.tgz", + "integrity": "sha512-U97IscNBL4Wg9adYjEBT46Hb0Ld5dPT8vbdwFX+TNzGrFQCc4WqoGAZouaLNFwUqxzzHZ9DVg59unwnQyeIIQg==" }, "mixin-deep": { "version": "1.3.2", diff --git a/api/package.json b/api/package.json index 1310d577..eef2d069 100644 --- a/api/package.json +++ b/api/package.json @@ -74,8 +74,8 @@ "i18next-http-middleware": "^3.1.3", "i18next-node-fs-backend": "^2.1.3", "jsonwebtoken": "^8.5.1", - "lambert-server": "^1.2.8", - "missing-native-js-functions": "^1.2.10", + "lambert-server": "^1.2.10", + "missing-native-js-functions": "^1.2.11", "mongoose": "^5.12.3", "mongoose-autopopulate": "^0.12.3", "mongoose-long": "^0.3.2", diff --git a/api/src/routes/auth/login.ts b/api/src/routes/auth/login.ts index c0acad4e..7fd0f870 100644 --- a/api/src/routes/auth/login.ts +++ b/api/src/routes/auth/login.ts @@ -21,7 +21,7 @@ router.post( async (req: Request, res: Response) => { const { login, password, captcha_key, undelete } = req.body; const email = adjustEmail(login); - console.log(req.body, email); + console.log("login", email); const config = Config.get(); diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index 5ad6d6a4..b0d8c9bd 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { trimSpecial, User, Snowflake, Config } from "@fosscord/util"; +import { trimSpecial, User, Snowflake, Config, defaultSettings } from "@fosscord/util"; import bcrypt from "bcrypt"; import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "../../util/instanceOf"; import "missing-native-js-functions"; @@ -182,17 +182,29 @@ router.post( // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false const user = await new User({ + created_at: new Date(), username: adjusted_username, discriminator, + bot: false, + system: false, + desktop: false, + mobile: false, premium: true, premium_type: 2, + bio: "", + mfa_enabled: false, + verified: false, + disabled: false, + deleted: false, email: adjusted_email, nsfw_allowed: true, // TODO: depending on age - guilds: [], + public_flags: "0", + flags: "0", // TODO: generate data: { hash: adjusted_password, valid_tokens_since: new Date() - } + }, + settings: defaultSettings }).save(); return res.json({ token: await generateToken(user.id) }); diff --git a/api/src/schema/Message.ts b/api/src/schema/Message.ts index f9bfcc67..bf10c037 100644 --- a/api/src/schema/Message.ts +++ b/api/src/schema/Message.ts @@ -11,7 +11,7 @@ export const MessageCreateSchema = { $content: new Length(String, 0, 2000), $nonce: String, $tts: Boolean, - $flags: BigInt, + $flags: String, $embed: { $title: new Length(String, 0, 256), //title of embed $type: String, // type of embed (always "rich" for webhook embeds) @@ -69,7 +69,7 @@ export interface MessageCreateSchema { content?: string; nonce?: string; tts?: boolean; - flags?: bigint; + flags?: string; embed?: Embed & { timestamp?: string }; allowed_mentions?: { parse?: string[]; -- cgit 1.5.1 From d0292ef96bedde2dd23962c15b2a0446b8492fb0 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 29 Aug 2021 17:12:46 +0200 Subject: :bug: convert bigint -> string --- api/src/middlewares/RateLimit.ts | 4 ++-- .../routes/channels/#channel_id/messages/index.ts | 2 +- api/src/routes/guilds/index.ts | 2 +- api/src/schema/Guild.ts | 2 +- bundle/database.db | Bin 225280 -> 225280 bytes gateway/src/opcodes/Identify.ts | 2 +- gateway/src/schema/Activity.ts | 4 ++-- util/src/entities/Application.ts | 2 +- util/src/interfaces/Event.ts | 6 +++--- 9 files changed, 12 insertions(+), 12 deletions(-) (limited to 'api/src') diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index e0cf103a..ed6b951a 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -1,6 +1,6 @@ import { Config, listenEvent, emitEvent, RateLimit } from "@fosscord/util"; import { NextFunction, Request, Response, Router } from "express"; -import { LessThan } from "typeorm"; +import { LessThan, MoreThan } from "typeorm"; import { getIpAdress } from "../util/ipAddress"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; @@ -100,7 +100,7 @@ export async function initRateLimits(app: Router) { Cache.set(event.channel_id as string, event.data); event.acknowledge?.(); }); - await RateLimit.delete({ expires_at: LessThan(new Date()) }); // clean up if not already deleted + await RateLimit.delete({ expires_at: MoreThan(new Date()) }); // cleans up if not already deleted, morethan -> older date const limits = await RateLimit.find({ blocked: true }); limits.forEach((limit) => { Cache.set(limit.executor_id, limit); diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index 6307c022..17944548 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -77,7 +77,7 @@ router.get("/", async (req: Request, res: Response) => { delete x.user_ids; }); // @ts-ignore - if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: 0n, avatar: null }; + if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: "0", avatar: null }; return x; }); diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index c158c7d4..1e83cf13 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -47,7 +47,7 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = premium_tier: 0, public_updates_channel_id: undefined, rules_channel_id: undefined, - system_channel_flags: 0, + system_channel_flags: "0", system_channel_id: undefined, unavailable: false, vanity_url_code: undefined, diff --git a/api/src/schema/Guild.ts b/api/src/schema/Guild.ts index 01690ae9..3e98fe76 100644 --- a/api/src/schema/Guild.ts +++ b/api/src/schema/Guild.ts @@ -33,7 +33,7 @@ export const GuildUpdateSchema = { $icon: String, $verification_level: Number, $default_message_notifications: Number, - $system_channel_flags: Number, + $system_channel_flags: String, $system_channel_id: String, $explicit_content_filter: Number, $public_updates_channel_id: String, diff --git a/bundle/database.db b/bundle/database.db index 9572c45e..2d4abd49 100644 Binary files a/bundle/database.db and b/bundle/database.db differ diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 5be2acce..87008998 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -42,7 +42,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { } } - const members = await Member.find({ where: { id: this.user_id }, relations: ["guilds"] }); + const members = await Member.find({ where: { id: this.user_id }, relations: ["guild"] }); const merged_members = members.map((x: any) => { const y = { ...x, user_id: x.id }; delete y.settings; diff --git a/gateway/src/schema/Activity.ts b/gateway/src/schema/Activity.ts index 0fd0592f..f1665efd 100644 --- a/gateway/src/schema/Activity.ts +++ b/gateway/src/schema/Activity.ts @@ -39,7 +39,7 @@ export const ActivitySchema = { $match: String, }, $instance: Boolean, - $flags: BigInt, + $flags: String, }, ], $since: Number, // unix time (in milliseconds) of when the client went idle, or null if the client is not idle @@ -79,7 +79,7 @@ export interface ActivitySchema { match?: string; // the secret for a specific instanced match }; instance?: boolean; - flags: bigint; // activity flags OR d together, describes what the payload includes + flags: string; // activity flags OR d together, describes what the payload includes } ]; since?: number; // unix time (in milliseconds) of when the client went idle, or null if the client is not idle diff --git a/util/src/entities/Application.ts b/util/src/entities/Application.ts index a87b5cea..b179d171 100644 --- a/util/src/entities/Application.ts +++ b/util/src/entities/Application.ts @@ -62,7 +62,7 @@ export class Application extends BaseClass { cover_image?: string; // the application's default rich presence invite cover image hash @Column() - flags: number; // the application's public flags + flags: string; // the application's public flags } export interface ApplicationCommand { diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts index 0de55f71..814a8beb 100644 --- a/util/src/interfaces/Event.ts +++ b/util/src/interfaces/Event.ts @@ -36,7 +36,7 @@ export interface ReadyEventData { mobile: boolean; desktop: boolean; email: string | undefined; - flags: bigint; + flags: string; mfa_enabled: boolean; nsfw_allowed: boolean; phone: string | undefined; @@ -85,7 +85,7 @@ export interface ReadyEventData { }; application?: { id: string; - flags: bigint; + flags: string; }; merged_members?: Omit[][]; // probably all users who the user is in contact with @@ -95,7 +95,7 @@ export interface ReadyEventData { id: string; username: string; bot: boolean; - public_flags: bigint; + public_flags: string; }[]; } -- cgit 1.5.1 From 954700b2d5c4090fdf9d7d25beef3d6529afa8d3 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 30 Aug 2021 12:14:32 +0200 Subject: :zap: only local rate limit to prevent to much pressure on the database --- api/src/middlewares/RateLimit.ts | 70 +++++++++++++++++++++++++++++----------- util/src/entities/RateLimit.ts | 3 +- 2 files changed, 52 insertions(+), 21 deletions(-) (limited to 'api/src') diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index ed6b951a..dffbc0d9 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -1,11 +1,12 @@ -import { Config, listenEvent, emitEvent, RateLimit } from "@fosscord/util"; +import { Config, listenEvent } from "@fosscord/util"; import { NextFunction, Request, Response, Router } from "express"; -import { LessThan, MoreThan } from "typeorm"; import { getIpAdress } from "../util/ipAddress"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; // Docs: https://discord.com/developers/docs/topics/rate-limits +// TODO: use better caching (e.g. redis) as else it creates to much pressure on the database + /* ? bucket limit? Max actions/sec per bucket? @@ -18,6 +19,14 @@ TODO: different for methods (GET/POST) */ +type RateLimit = { + id: "global" | "error" | string; + executor_id: string; + hits: number; + blocked: boolean; + expires_at: Date; +}; + var Cache = new Map(); const EventRateLimit = "RATELIMIT"; @@ -46,13 +55,22 @@ export default function rateLimit(opts: { const offender = Cache.get(executor_id + bucket_id); - if (offender && offender.blocked) { + if (offender) { const reset = offender.expires_at.getTime(); const resetAfterMs = reset - Date.now(); const resetAfterSec = resetAfterMs / 1000; - const global = bucket_id === "global"; - if (resetAfterMs > 0) { + if (resetAfterMs <= 0) { + offender.hits = 0; + offender.expires_at = new Date(Date.now() + opts.window * 1000); + offender.blocked = false; + + Cache.delete(executor_id + bucket_id); + } + + if (offender.blocked) { + const global = bucket_id === "global"; + console.log("blocked bucket: " + bucket_id, { resetAfterMs }); return ( res @@ -67,15 +85,9 @@ export default function rateLimit(opts: { // TODO: error rate limit message translation .send({ message: "You are being rate limited.", retry_after: resetAfterSec, global }) ); - } else { - offender.hits = 0; - offender.expires_at = new Date(Date.now() + opts.window * 1000); - offender.blocked = false; - // mongodb ttl didn't update yet -> manually update/delete - RateLimit.delete({ id: bucket_id, executor_id }); - Cache.delete(executor_id + bucket_id); } } + next(); const hitRouteOpts = { bucket_id, executor_id, max_hits, window: opts.window }; @@ -100,20 +112,20 @@ export async function initRateLimits(app: Router) { Cache.set(event.channel_id as string, event.data); event.acknowledge?.(); }); - await RateLimit.delete({ expires_at: MoreThan(new Date()) }); // cleans up if not already deleted, morethan -> older date - const limits = await RateLimit.find({ blocked: true }); - limits.forEach((limit) => { - Cache.set(limit.executor_id, limit); - }); + // await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date + // const limits = await RateLimit.find({ blocked: true }); + // limits.forEach((limit) => { + // Cache.set(limit.executor_id, limit); + // }); setInterval(() => { Cache.forEach((x, key) => { if (new Date() > x.expires_at) { Cache.delete(key); - RateLimit.delete({ executor_id: key }); + // RateLimit.delete({ executor_id: key }); } }); - }, 1000 * 60 * 10); + }, 1000 * 60); app.use( rateLimit({ @@ -139,6 +151,25 @@ export async function initRateLimits(app: Router) { } async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) { + const id = opts.executor_id + opts.bucket_id; + var limit = Cache.get(id); + if (!limit) { + limit = { + id: opts.bucket_id, + executor_id: opts.executor_id, + expires_at: new Date(Date.now() + opts.window * 1000), + hits: 0, + blocked: false + }; + Cache.set(id, limit); + } + + limit.hits++; + if (limit.hits >= opts.max_hits) { + limit.blocked = true; + } + + /* var ratelimit = await RateLimit.findOne({ id: opts.bucket_id, executor_id: opts.executor_id }); if (!ratelimit) { ratelimit = new RateLimit({ @@ -167,4 +198,5 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits } await ratelimit.save(); + */ } diff --git a/util/src/entities/RateLimit.ts b/util/src/entities/RateLimit.ts index 49af0416..fa9c32c1 100644 --- a/util/src/entities/RateLimit.ts +++ b/util/src/entities/RateLimit.ts @@ -1,6 +1,5 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity } from "typeorm"; import { BaseClass } from "./BaseClass"; -import { User } from "./User"; @Entity("rate_limits") export class RateLimit extends BaseClass { -- cgit 1.5.1 From 4abc758991d3f92a6404f269770fb92f5375d1e4 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 30 Aug 2021 12:15:06 +0200 Subject: :construction: typeorm --- api/src/routes/guilds/#guild_id/roles.ts | 2 +- api/src/routes/guilds/#guild_id/templates.ts | 2 +- api/src/routes/guilds/index.ts | 88 +++++++++++++--------------- util/src/entities/BaseClass.ts | 3 +- util/src/entities/Emoji.ts | 2 +- util/src/entities/Guild.ts | 24 ++++---- util/src/entities/Member.ts | 6 +- util/src/entities/Role.ts | 4 +- util/src/util/Config.ts | 3 +- util/src/util/Permissions.ts | 9 +-- 10 files changed, 67 insertions(+), 76 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index e9e777b9..796a8eb8 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -40,7 +40,7 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => managed: false, position: 0, tags: null, - permissions: perms.bitfield & (body.permissions || 0n) + permissions: String(perms.bitfield & (body.permissions || 0n)) }).save(); await emitEvent({ diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts index e1d2f5fd..a7613abf 100644 --- a/api/src/routes/guilds/#guild_id/templates.ts +++ b/api/src/routes/guilds/#guild_id/templates.ts @@ -17,7 +17,7 @@ const TemplateGuildProjection: (keyof Guild)[] = [ "preferred_locale", "afk_timeout", "roles", - "channels", + // "channels", "afk_channel_id", "system_channel_id", "system_channel_flags", diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index 1e83cf13..020aba6a 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -19,63 +19,55 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = } const guild_id = Snowflake.generate(); - const guild = new Guild({ - name: body.name, - region: Config.get().regions.default, - owner_id: req.user_id, - icon: undefined, - afk_channel_id: undefined, - afk_timeout: 300, - application_id: undefined, - banner: undefined, - default_message_notifications: 0, - description: undefined, - splash: undefined, - discovery_splash: undefined, - explicit_content_filter: 0, - features: [], - id: guild_id, - large: undefined, - max_members: 250000, - max_presences: 250000, - max_video_channel_users: 25, - presence_count: 0, - member_count: 0, // will automatically be increased by addMember() - mfa_level: 0, - preferred_locale: "en-US", - premium_subscription_count: 0, - premium_tier: 0, - public_updates_channel_id: undefined, - rules_channel_id: undefined, - system_channel_flags: "0", - system_channel_id: undefined, - unavailable: false, - vanity_url_code: undefined, - verification_level: 0, - welcome_screen: { - enabled: false, - description: "No description", - welcome_channels: [] - }, - widget_channel_id: undefined, - widget_enabled: false - }); - - const [guild_doc, role] = await Promise.all([ - new Guild(guild).save(), - new Role({ + const guild = new Guild( + { + name: body.name, + region: Config.get().regions.default, + owner_id: req.user_id, + afk_timeout: 300, + default_message_notifications: 0, + explicit_content_filter: 0, + features: [], id: guild_id, + max_members: 250000, + max_presences: 250000, + max_video_channel_users: 25, + presence_count: 0, + member_count: 0, // will automatically be increased by addMember() + mfa_level: 0, + preferred_locale: "en-US", + premium_subscription_count: 0, + premium_tier: 0, + system_channel_flags: "0", + unavailable: false, + verification_level: 0, + welcome_screen: { + enabled: false, + description: "No description", + welcome_channels: [] + }, + widget_enabled: false + }, + { id: guild_id } + ); + const role = new Role( + { guild_id: guild_id, color: 0, hoist: false, managed: false, mentionable: false, name: "@everyone", - permissions: 2251804225n, + permissions: String("2251804225"), position: 0, tags: null - }).save() - ]); + }, + { + id: guild_id + } + ); + + await Promise.all([guild.save(), role.save()]); if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general" }]; diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts index 31338ff6..63ce5836 100644 --- a/util/src/entities/BaseClass.ts +++ b/util/src/entities/BaseClass.ts @@ -51,7 +51,8 @@ export class BaseClass extends BaseEntity { if (setter) { setter.call(this, props[key]); } else { - Object.defineProperty(this, key, { value: props[key] }); + // @ts-ignore + this[key] = props[key]; } } } diff --git a/util/src/entities/Emoji.ts b/util/src/entities/Emoji.ts index 0686d476..4c0fccd3 100644 --- a/util/src/entities/Emoji.ts +++ b/util/src/entities/Emoji.ts @@ -15,7 +15,7 @@ export class Emoji extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, (guild: Guild) => guild.id) + @ManyToOne(() => Guild, (guild: Guild) => guild.emojis) guild: Guild; @Column() diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts index e6a93824..3e7e8917 100644 --- a/util/src/entities/Guild.ts +++ b/util/src/entities/Guild.ts @@ -1,4 +1,4 @@ -import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Channel } from "./Channel"; import { Emoji } from "./Emoji"; @@ -68,35 +68,35 @@ export class Guild extends BaseClass { member_ids: string[]; @JoinColumn({ name: "member_ids" }) - @ManyToMany(() => Member, (member: Member) => member.id) + @OneToMany(() => Member, (member: Member) => member.guild) members: Member[]; @RelationId((guild: Guild) => guild.roles) role_ids: string[]; @JoinColumn({ name: "role_ids" }) - @ManyToMany(() => Role, (role: Role) => role.id) + @OneToMany(() => Role, (role: Role) => role.guild) roles: Role[]; @RelationId((guild: Guild) => guild.channels) channel_ids: string[]; @JoinColumn({ name: "channel_ids" }) - @ManyToMany(() => Channel, (channel: Channel) => channel.id) + @OneToMany(() => Channel, (channel: Channel) => channel.guild) channels: Channel[]; @RelationId((guild: Guild) => guild.emojis) emoji_ids: string[]; @JoinColumn({ name: "emoji_ids" }) - @ManyToMany(() => Emoji, (emoji: Emoji) => emoji.id) + @OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild) emojis: Emoji[]; @RelationId((guild: Guild) => guild.voice_states) voice_state_ids: string[]; @JoinColumn({ name: "voice_state_ids" }) - @ManyToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.id) + @OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild) voice_states: VoiceState[]; @Column({ nullable: true }) @@ -109,7 +109,7 @@ export class Guild extends BaseClass { owner_id: string; @JoinColumn({ name: "owner_id" }) - @ManyToOne(() => User, (user: User) => user.id) + @OneToOne(() => User) owner: User; @Column({ nullable: true }) @@ -125,14 +125,14 @@ export class Guild extends BaseClass { public_updates_channel_id: string; @JoinColumn({ name: "public_updates_channel_id" }) - @ManyToOne(() => Channel, (channel: Channel) => channel.id) + @OneToOne(() => Channel, (channel: Channel) => channel.id) public_updates_channel?: Channel; @RelationId((guild: Guild) => guild.rules_channel) rules_channel_id?: string; @JoinColumn({ name: "rules_channel_id" }) - @ManyToOne(() => Channel, (channel: Channel) => channel.id) + @OneToOne(() => Channel, (channel: Channel) => channel.id) rules_channel?: string; @Column({ nullable: true }) @@ -145,7 +145,7 @@ export class Guild extends BaseClass { system_channel_id?: string; @JoinColumn({ name: "system_channel_id" }) - @ManyToMany(() => Channel, (channel: Channel) => channel.id) + @OneToOne(() => Channel, (channel: Channel) => channel.id) system_channel?: Channel; @Column({ nullable: true }) @@ -158,7 +158,7 @@ export class Guild extends BaseClass { vanity_url_code?: string; @JoinColumn({ name: "vanity_url_code" }) - @ManyToOne(() => Invite) + @OneToOne(() => Invite) vanity_url?: Invite; @Column({ nullable: true }) @@ -180,7 +180,7 @@ export class Guild extends BaseClass { widget_channel_id?: string; @JoinColumn({ name: "widget_channel_id" }) - @ManyToOne(() => Channel, (channel: Channel) => channel.id) + @OneToOne(() => Channel, (channel: Channel) => channel.id) widget_channel?: Channel; @Column({ nullable: true }) diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts index 5b588d70..c5d289ef 100644 --- a/util/src/entities/Member.ts +++ b/util/src/entities/Member.ts @@ -1,6 +1,6 @@ import { PublicUser, User } from "./User"; import { BaseClass } from "./BaseClass"; -import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm"; import { Guild } from "./Guild"; import { Config, emitEvent } from "../util"; import { @@ -26,7 +26,7 @@ export class Member extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, (guild: Guild) => guild.id) + @ManyToOne(() => Guild, (guild: Guild) => guild.members) guild: Guild; @Column({ nullable: true }) @@ -35,7 +35,7 @@ export class Member extends BaseClass { @RelationId((member: Member) => member.roles) role_ids: string[]; - @JoinColumn({ name: "role_ids" }) + @JoinTable() @ManyToMany(() => Role) roles: Role[]; diff --git a/util/src/entities/Role.ts b/util/src/entities/Role.ts index ddae7e40..be8c731d 100644 --- a/util/src/entities/Role.ts +++ b/util/src/entities/Role.ts @@ -27,8 +27,8 @@ export class Role extends BaseClass { @Column() name: string; - @Column({ type: "bigint" }) - permissions: bigint; + @Column() + permissions: string; @Column() position: number; diff --git a/util/src/util/Config.ts b/util/src/util/Config.ts index f16921bd..508a8901 100644 --- a/util/src/util/Config.ts +++ b/util/src/util/Config.ts @@ -7,7 +7,8 @@ var config: ConfigEntity; export const Config = { init: async function init() { if (config) return config; - config = new ConfigEntity({}, { id: "0" }); + config = (await ConfigEntity.findOne({ id: "0" })) || new ConfigEntity({}, { id: "0" }); + return this.set((config.value || {}).merge(DefaultConfigOptions)); }, get: function get() { diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts index 5082d8ea..ab6aa795 100644 --- a/util/src/util/Permissions.ts +++ b/util/src/util/Permissions.ts @@ -212,18 +212,15 @@ export async function getPermission(user_id?: string, guild_id?: string, channel var guild: Guild | undefined; if (channel_id) { - channel = await Channel.findOneOrFail( - { id: channel_id }, - { select: ["permission_overwrites", "recipients", "owner", "guild"] } - ); + channel = await Channel.findOneOrFail({ id: channel_id }); if (channel.guild_id) guild_id = channel.guild_id; // derive guild_id from the channel } if (guild_id) { - guild = await Guild.findOneOrFail({ id: guild_id }, { select: ["owner"] }); + guild = await Guild.findOneOrFail({ id: guild_id }); if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR); - member = await Member.findOneOrFail({ guild_id, id: user_id }, { select: ["roles"] }); + member = await Member.findOneOrFail({ where: { guild_id, id: user_id }, relations: ["roles"] }); } var permission = Permissions.finalPermission({ -- cgit 1.5.1 From 3db1abdb50a5528893c1325d907c69d5534a5d31 Mon Sep 17 00:00:00 2001 From: xnacly Date: Mon, 30 Aug 2021 13:44:52 +0200 Subject: fixed translation path to display correct error msg --- api/src/middlewares/Translation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'api/src') diff --git a/api/src/middlewares/Translation.ts b/api/src/middlewares/Translation.ts index edc14707..baabf221 100644 --- a/api/src/middlewares/Translation.ts +++ b/api/src/middlewares/Translation.ts @@ -19,7 +19,7 @@ export async function initTranslation(router: Router) { fallbackLng: "en", ns, backend: { - loadPath: __dirname + "/../locales/{{lng}}/{{ns}}.json" + loadPath: __dirname + "/../../locales/{{lng}}/{{ns}}.json" }, load: "all" }); -- cgit 1.5.1 From e2a61b44bbb1f4e24f2226db5ef7f9d388c5d961 Mon Sep 17 00:00:00 2001 From: xnacly Date: Mon, 30 Aug 2021 16:10:58 +0200 Subject: fixed wrong statuscode in docs --- api/src/routes/auth/register.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'api/src') diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index b0d8c9bd..b41ef82c 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -237,6 +237,6 @@ export default router; * Field Error * @returns { "code": 50035, "errors": { "consent": { "_errors": [{ "code": "CONSENT_REQUIRED", "message": "You must agree to Discord's Terms of Service and Privacy Policy." }]}}, "message": "Invalid Form Body"} * - * Success 201: + * Success 200: * @returns {token: "OMITTED"} */ -- cgit 1.5.1 From b59cbdb081da601cb7c19cb6010474def1bdcffb Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Mon, 30 Aug 2021 21:12:13 +0200 Subject: Implemented voice apis #127 and #78 --- api/src/routes/guilds/#guild_id/regions.ts | 9 +++++++-- api/src/routes/voice/regions.ts | 11 ++++++++++ api/src/util/Voice.ts | 32 ++++++++++++++++++++++++++++++ api/src/util/ipAddress.ts | 17 ++++++++++++++++ util/src/entities/Config.ts | 10 ++++++++-- util/src/entities/Guild.ts | 2 +- 6 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 api/src/routes/voice/regions.ts create mode 100644 api/src/util/Voice.ts (limited to 'api/src') diff --git a/api/src/routes/guilds/#guild_id/regions.ts b/api/src/routes/guilds/#guild_id/regions.ts index 5ec649ee..212c9bcd 100644 --- a/api/src/routes/guilds/#guild_id/regions.ts +++ b/api/src/routes/guilds/#guild_id/regions.ts @@ -1,10 +1,15 @@ -import { Config } from "@fosscord/util"; +import {Config, Guild, Member} from "@fosscord/util"; import { Request, Response, Router } from "express"; +import {getVoiceRegions} from "../../../util/Voice"; +import {getIpAdress} from "../../../util/ipAddress"; const router = Router(); router.get("/", async (req: Request, res: Response) => { - return res.json(Config.get().regions.available); + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ 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/api/src/routes/voice/regions.ts b/api/src/routes/voice/regions.ts new file mode 100644 index 00000000..812aa8f6 --- /dev/null +++ b/api/src/routes/voice/regions.ts @@ -0,0 +1,11 @@ +import { Router, Request, Response } from "express"; +import {getIpAdress} from "../../util/ipAddress"; +import {getVoiceRegions} from "../../util/Voice"; + +const router: Router = Router(); + +router.get("/", async (req: Request, res: Response) => { + res.json(await getVoiceRegions(getIpAdress(req), true))//vip true? +}); + +export default router; diff --git a/api/src/util/Voice.ts b/api/src/util/Voice.ts new file mode 100644 index 00000000..087bdfa8 --- /dev/null +++ b/api/src/util/Voice.ts @@ -0,0 +1,32 @@ +import {Config} from "@fosscord/util"; +import {distanceBetweenLocations, IPAnalysis} from "./ipAddress"; + +export async function getVoiceRegions(ipAddress: string, vip: boolean) { + const regions = Config.get().regions; + const availableRegions = regions.available.filter(ar => vip ? true : !ar.vip); + let optimalId = regions.default + + if(!regions.useDefaultAsOptimal) { + const clientIpAnalysis = await IPAnalysis(ipAddress) + + let min = Number.POSITIVE_INFINITY + + for (let ar of availableRegions) { + //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call + const dist = distanceBetweenLocations(clientIpAnalysis, ar.location || (await IPAnalysis(ar.endpoint))) + + if(dist < min) { + min = dist + optimalId = ar.id + } + } + } + + return availableRegions.map(ar => ({ + id: ar.id, + name: ar.name, + custom: ar.custom, + deprecated: ar.deprecated, + optimal: ar.id === optimalId + })) +} \ No newline at end of file diff --git a/api/src/util/ipAddress.ts b/api/src/util/ipAddress.ts index 0a724daa..c6239426 100644 --- a/api/src/util/ipAddress.ts +++ b/api/src/util/ipAddress.ts @@ -60,6 +60,7 @@ const exampleData = { status: 200 }; +//TODO add function that support both ip and domain names export async function IPAnalysis(ip: string): Promise { const { ipdataApiKey } = Config.get().security; if (!ipdataApiKey) return { ...exampleData, ip }; @@ -79,3 +80,19 @@ export function getIpAdress(req: Request): string { // @ts-ignore return req.headers[Config.get().security.forwadedFor] || req.socket.remoteAddress; } + + +export function distanceBetweenLocations(loc1: any, loc2: any): number { + return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude); +} + +//Haversine function +function distanceBetweenCoords(lat1: number, lon1: number, lat2: number, lon2: number) { + const p = 0.017453292519943295; // Math.PI / 180 + const c = Math.cos; + const a = 0.5 - c((lat2 - lat1) * p) / 2 + + c(lat1 * p) * c(lat2 * p) * + (1 - c((lon2 - lon1) * p)) / 2; + + return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km +} \ No newline at end of file diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts index 320a729c..f030b167 100644 --- a/util/src/entities/Config.ts +++ b/util/src/entities/Config.ts @@ -19,10 +19,14 @@ export interface RateLimitOptions { export interface Region { id: string; name: string; + endpoint: string; + location?: { + latitude: number; + longitude: number; + }; vip: boolean; custom: boolean; deprecated: boolean; - optimal: boolean; } export interface KafkaBroker { @@ -128,6 +132,7 @@ export interface ConfigValue { }; regions: { default: string; + useDefaultAsOptimal: boolean; available: Region[]; }; rabbitmq: { @@ -263,7 +268,8 @@ export const DefaultConfigOptions: ConfigValue = { }, regions: { default: "fosscord", - available: [{ id: "fosscord", name: "Fosscord", vip: false, custom: false, deprecated: false, optimal: false }], + useDefaultAsOptimal: true, + available: [{ id: "fosscord", name: "Fosscord", endpoint: "127.0.0.1", vip: false, custom: false, deprecated: false }], }, rabbitmq: { host: null, diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts index 3e7e8917..21b059a3 100644 --- a/util/src/entities/Guild.ts +++ b/util/src/entities/Guild.ts @@ -41,7 +41,7 @@ export class Guild extends BaseClass { explicit_content_filter?: number; @Column({ type: "simple-array" }) - features: string[]; + features: string[]; //TODO use enum @Column({ nullable: true }) icon?: string; -- cgit 1.5.1 From 3def279c5a0f23a2a62097979e96a6c9b78b5db9 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:54:57 +0200 Subject: :sparkles: typeorm error handler --- api/src/middlewares/ErrorHandler.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'api/src') diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index 0ed37bb4..e1ab592c 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -1,5 +1,6 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; +import { EntityNotFoundError } from "typeorm"; import { FieldError } from "../util/instanceOf"; // TODO: update with new body/typorm validation @@ -13,12 +14,18 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne let errors = undefined; if (error instanceof HTTPError && error.code) code = httpcode = error.code; - else if (error instanceof FieldError) { + else if (error instanceof EntityNotFoundError) { + message = `${(error as any).stringifyTarget} can not be found`; + code = 404; + } else if (error instanceof FieldError) { code = Number(error.code); message = error.message; errors = error.errors; } else { + console.error(`[Error] ${code} ${req.url}`, errors || error, "body:", req.body); + if (req.server?.options?.production) { + // don't expose internal errors to the user, instead human errors should be thrown as HTTPError message = "Internal Server Error"; } code = httpcode = 500; @@ -26,8 +33,6 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne if (httpcode > 511) httpcode = 400; - console.error(`[Error] ${code} ${req.url}`, errors || error, "body:", req.body); - res.status(httpcode).json({ code: code, message, errors }); } catch (error) { console.error(`[Internal Server Error] 500`, error); -- cgit 1.5.1 From 5d820f35347fe328605edfbcce9e26dcbf75af07 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:55:23 +0200 Subject: :bug: fix message sending --- .../#channel_id/messages/#message_id/index.ts | 7 +- api/src/util/Message.ts | 95 +++++++++++++++------- 2 files changed, 68 insertions(+), 34 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index 32478763..b9d46c4f 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -20,7 +20,9 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response body = { flags: body.flags }; // admins can only suppress embeds of other messages } - const opts = await handleMessage({ + const new_message = await handleMessage({ + // TODO: should be message_reference overridable? + // @ts-ignore message_reference: message.message_reference, ...body, author_id: message.author_id, @@ -28,10 +30,9 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response id: message_id, edited_timestamp: new Date() }); - message.assign(opts); await Promise.all([ - message.save(), + new_message.save(), await emitEvent({ event: "MESSAGE_UPDATE", channel_id, diff --git a/api/src/util/Message.ts b/api/src/util/Message.ts index 70989301..fea553bc 100644 --- a/api/src/util/Message.ts +++ b/api/src/util/Message.ts @@ -13,11 +13,16 @@ import { Role, EVERYONE_MENTION, HERE_MENTION, - MessageType + MessageType, + User, + Application, + Webhook, + Attachment } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import fetch from "node-fetch"; import cheerio from "cheerio"; +import { MessageCreateSchema } from "../schema/Message"; // TODO: check webhook, application, system author @@ -34,17 +39,37 @@ const DEFAULT_FETCH_OPTIONS: any = { method: "GET" }; -export async function handleMessage(opts: Partial): Promise { - const channel = await Channel.findOneOrFail( - { id: opts.channel_id }, - { select: ["guild_id", "type", "permission_overwrites", "recipient_ids", "owner_id"] } - ); // lean is needed, because we don't want to populate .recipients that also auto deletes .recipient_ids +export async function handleMessage(opts: MessageOptions): Promise { + const channel = await Channel.findOneOrFail({ where: { id: opts.channel_id }, relations: ["recipients"] }); if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404); + + const message = new Message({ + ...opts, + guild_id: channel.guild_id, + channel_id: opts.channel_id, + attachments: opts.attachments || [], + embeds: opts.embeds || [], + reactions: /*opts.reactions ||*/ [], + type: opts.type ?? 0 + }); + // TODO: are tts messages allowed in dm channels? should permission be checked? + if (opts.author_id) { + message.author = await User.getPublicUser(opts.author_id); + } + if (opts.application_id) { + message.application = await Application.findOneOrFail({ id: opts.application_id }); + } + if (opts.webhook_id) { + message.webhook = await Webhook.findOneOrFail({ id: opts.webhook_id }); + } - // @ts-ignore - const permission = await getPermission(opts.author_id, channel.guild_id, opts.channel_id, { channel }); + const permission = await getPermission(opts.author_id, channel.guild_id, opts.channel_id); permission.hasThrow("SEND_MESSAGES"); + if (permission.cache.member) { + message.member = permission.cache.member; + } + if (opts.tts) permission.hasThrow("SEND_TTS_MESSAGES"); if (opts.message_reference) { permission.hasThrow("READ_MESSAGE_HISTORY"); @@ -52,10 +77,11 @@ export async function handleMessage(opts: Partial): Promise { if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel"); // TODO: should be checked if the referenced message exists? // @ts-ignore - opts.type = MessageType.REPLY; + message.type = MessageType.REPLY; } - if (!opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.stickers?.length && !opts.activity) { + // TODO: stickers/activity + if (!opts.content && !opts.embeds?.length && !opts.attachments?.length) { throw new HTTPError("Empty messages are not allowed", 50006); } @@ -64,10 +90,9 @@ export async function handleMessage(opts: Partial): Promise { var mention_role_ids = [] as string[]; var mention_user_ids = [] as string[]; var mention_everyone = false; - var mention_everyone = false; if (content) { - content = content.trim(); + message.content = content.trim(); for (const [_, mention] of content.matchAll(CHANNEL_MENTION)) { if (!mention_channel_ids.includes(mention)) mention_channel_ids.push(mention); } @@ -90,21 +115,14 @@ export async function handleMessage(opts: Partial): Promise { } } + message.mention_channels = mention_channel_ids.map((x) => new Channel({ id: x })); + message.mention_roles = mention_role_ids.map((x) => new Role({ id: x })); + message.mentions = mention_user_ids.map((x) => new User({ id: x })); + message.mention_everyone = mention_everyone; + // TODO: check and put it all in the body - return { - ...opts, - guild_id: channel.guild_id, - channel_id: opts.channel_id, - mention_channel_ids, - mention_role_ids, - mention_user_ids, - mention_everyone, - attachments: opts.attachments || [], - embeds: opts.embeds || [], - reactions: opts.reactions || [], - type: opts.type ?? 0 - } as Message; + return message; } // TODO: cache link result in db @@ -160,14 +178,29 @@ export async function postHandleMessage(message: Message) { ]); } -export async function sendMessage(opts: Partial) { - const message = await handleMessage({ ...opts, id: Snowflake.generate(), timestamp: new Date() }); +export async function sendMessage(opts: MessageOptions) { + const message = await handleMessage({ ...opts, timestamp: new Date() }); - const data = await new Message(message).save(); + await Promise.all([ + message.save(), + emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data: message.toJSON() } as MessageCreateEvent) + ]); - await emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data } as MessageCreateEvent); + postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error - postHandleMessage(data).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error + return message; +} - return data; +interface MessageOptions extends MessageCreateSchema { + id?: string; + type?: MessageType; + pinned?: boolean; + author_id?: string; + webhook_id?: string; + application_id?: string; + embeds?: Embed[]; + channel_id?: string; + attachments?: Attachment[]; + edited_timestamp?: Date; + timestamp?: Date; } -- cgit 1.5.1 From f68b3027e377eac0e8e495123f864fb4b1da0614 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:56:33 +0200 Subject: :zap: use insert instead of save --- api/src/routes/auth/register.ts | 9 ++++++--- api/src/routes/guilds/index.ts | 41 +++++++++++++++++++---------------------- 2 files changed, 25 insertions(+), 25 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index b41ef82c..8bcecda1 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -181,10 +181,11 @@ router.post( // appearently discord doesn't save the date of birth and just calculate if nsfw is allowed // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false - const user = await new User({ + const user = { created_at: new Date(), username: adjusted_username, discriminator, + id: Snowflake.generate(), bot: false, system: false, desktop: false, @@ -204,8 +205,10 @@ router.post( hash: adjusted_password, valid_tokens_since: new Date() }, - settings: defaultSettings - }).save(); + settings: defaultSettings, + fingerprints: [] + }; + await User.insert(user); return res.json({ token: await generateToken(user.id) }); } diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index 020aba6a..e4157384 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,9 +1,8 @@ import { Router, Request, Response } from "express"; -import { Role, Guild, Snowflake, Config, User, Member } from "@fosscord/util"; +import { Role, Guild, Snowflake, Config, User, Member, Channel } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { check } from "./../../util/instanceOf"; import { GuildCreateSchema } from "../../schema/Guild"; -import { createChannel } from "../../util/Channel"; const router: Router = Router(); @@ -13,14 +12,15 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = const body = req.body as GuildCreateSchema; const { maxGuilds } = Config.get().limits.user; - const guild_count = await Member.count({ where: { id: req.user_id } }); + const guild_count = await Member.count({ id: req.user_id }); if (guild_count >= maxGuilds) { throw new HTTPError(`Maximum number of guilds reached ${maxGuilds}`, 403); } const guild_id = Snowflake.generate(); - const guild = new Guild( - { + + const [guild, role] = await Promise.all([ + Guild.insert({ name: body.name, region: Config.get().regions.default, owner_id: req.user_id, @@ -38,7 +38,7 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = preferred_locale: "en-US", premium_subscription_count: 0, premium_tier: 0, - system_channel_flags: "0", + system_channel_flags: 0, unavailable: false, verification_level: 0, welcome_screen: { @@ -47,11 +47,9 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = welcome_channels: [] }, widget_enabled: false - }, - { id: guild_id } - ); - const role = new Role( - { + }), + Role.insert({ + id: guild_id, guild_id: guild_id, color: 0, hoist: false, @@ -59,15 +57,9 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = mentionable: false, name: "@everyone", permissions: String("2251804225"), - position: 0, - tags: null - }, - { - id: guild_id - } - ); - - await Promise.all([guild.save(), role.save()]); + position: 0 + }) + ]); if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general" }]; @@ -86,13 +78,18 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = // TODO: should we abort if parent_id is a category? (to disallow sub category channels) var parent_id = ids.get(x.parent_id); - return createChannel({ ...x, guild_id, id, parent_id }, req.user_id, { keepId: true, skipExistsCheck: true }); + return Channel.createChannel({ ...x, guild_id, id, parent_id }, req.user_id, { + keepId: true, + skipExistsCheck: true, + skipPermissionCheck: true, + skipEventEmit: true + }); }) ); await Member.addToGuild(req.user_id, guild_id); - res.status(201).json({ id: guild.id }); + res.status(201).json({ id: guild_id }); }); export default router; -- cgit 1.5.1 From 7fcb68f0825b99a830f7c1ee24bd58a456c1ed5b Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Tue, 31 Aug 2021 17:56:38 +0200 Subject: Created list of all possible api errors and made them throwable in routes code --- api/src/middlewares/ErrorHandler.ts | 6 + api/src/util/ApiError.ts | 23 +++ api/src/util/Constants.ts | 293 ++++++++++++++++++++++++++++-------- 3 files changed, 256 insertions(+), 66 deletions(-) create mode 100644 api/src/util/ApiError.ts (limited to 'api/src') diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index 0ed37bb4..5fc36f33 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -1,6 +1,7 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; import { FieldError } from "../util/instanceOf"; +import {ApiError} from "../util/ApiError"; // TODO: update with new body/typorm validation export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { @@ -13,6 +14,11 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne let errors = undefined; if (error instanceof HTTPError && error.code) code = httpcode = error.code; + else if (error instanceof ApiError) { + code = error.code; + message = error.message; + httpcode = error.httpStatus; + } else if (error instanceof FieldError) { code = Number(error.code); message = error.message; diff --git a/api/src/util/ApiError.ts b/api/src/util/ApiError.ts new file mode 100644 index 00000000..2316cd71 --- /dev/null +++ b/api/src/util/ApiError.ts @@ -0,0 +1,23 @@ +export class ApiError extends Error { + constructor(readonly message: string, public readonly code: number, public readonly httpStatus: number = 400, public readonly defaultParams?: string[]) { + super(message); + } + + withDefaultParams(): ApiError { + if(this.defaultParams) + return new ApiError(applyParamsToString(this.message, this.defaultParams), this.code, this.httpStatus) + return this + } + + withParams(...params: string[]): ApiError { + return new ApiError(applyParamsToString(this.message, params), this.code, this.httpStatus) + } +} + +export function applyParamsToString(s: string, params: string[]): string { + let newString = s + params.forEach(a => { + newString = newString.replace("{}", a) + }) + return newString +} diff --git a/api/src/util/Constants.ts b/api/src/util/Constants.ts index f3a8dd67..15fdc519 100644 --- a/api/src/util/Constants.ts +++ b/api/src/util/Constants.ts @@ -1,3 +1,5 @@ +import {ApiError} from "./ApiError"; + export const WSCodes = { 1000: "WS_CLOSE_REQUESTED", 4004: "TOKEN_INVALID", @@ -421,6 +423,7 @@ export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"] /** * An error encountered while performing an API request. Here are the potential errors: + * * GENERAL_ERROR * * UNKNOWN_ACCOUNT * * UNKNOWN_APPLICATION * * UNKNOWN_CHANNEL @@ -436,27 +439,70 @@ export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"] * * UNKNOWN_USER * * UNKNOWN_EMOJI * * UNKNOWN_WEBHOOK + * * UNKNOWN_WEBHOOK_SERVICE + * * UNKNOWN_SESSION * * UNKNOWN_BAN + * * UNKNOWN_SKU + * * UNKNOWN_STORE_LISTING + * * UNKNOWN_ENTITLEMENT + * * UNKNOWN_BUILD + * * UNKNOWN_LOBBY + * * UNKNOWN_BRANCH + * * UNKNOWN_STORE_DIRECTORY_LAYOUT + * * UNKNOWN_REDISTRIBUTABLE + * * UNKNOWN_GIFT_CODE + * * UNKNOWN_STREAM + * * UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN * * UNKNOWN_GUILD_TEMPLATE + * * UNKNOWN_DISCOVERABLE_SERVER_CATEGORY + * * UNKNOWN_STICKER + * * UNKNOWN_INTERACTION + * * UNKNOWN_APPLICATION_COMMAND + * * UNKNOWN_APPLICATION_COMMAND_PERMISSIONS + * * UNKNOWN_STAGE_INSTANCE + * * UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM + * * UNKNOWN_GUILD_WELCOME_SCREEN + * * UNKNOWN_GUILD_SCHEDULED_EVENT + * * UNKNOWN_GUILD_SCHEDULED_EVENT_USER * * BOT_PROHIBITED_ENDPOINT * * BOT_ONLY_ENDPOINT - * * CHANNEL_HIT_WRITE_RATELIMIT + * * EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT + * * ACTION_NOT_AUTHORIZED_ON_APPLICATION + * * SLOWMODE_RATE_LIMIT + * * ONLY_OWNER + * * ANNOUNCEMENT_RATE_LIMITS + * * CHANNEL_WRITE_RATELIMIT + * * WORDS_NOT_ALLOWED + * * GUILD_PREMIUM_LEVEL_TOO_LOW * * MAXIMUM_GUILDS * * MAXIMUM_FRIENDS * * MAXIMUM_PINS + * * MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED * * MAXIMUM_ROLES * * MAXIMUM_WEBHOOKS + * * MAXIMUM_NUMBER_OF_EMOJIS_REACHED * * MAXIMUM_REACTIONS * * MAXIMUM_CHANNELS * * MAXIMUM_ATTACHMENTS * * MAXIMUM_INVITES + * * MAXIMUM_ANIMATED_EMOJIS + * * MAXIMUM_SERVER_MEMBERS + * * MAXIMUM_SERVER_CATEGORIES * * GUILD_ALREADY_HAS_TEMPLATE + * * MAXIMUM_THREAD_PARTICIPANTS + * * MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS + * * MAXIMUM_BANS_FETCHES + * * MAXIMUM_STICKERS + * * MAXIMUM_PRUNE_REQUESTS * * UNAUTHORIZED * * ACCOUNT_VERIFICATION_REQUIRED + * * OPENING_DIRECT_MESSAGES_TOO_FAST * * REQUEST_ENTITY_TOO_LARGE * * FEATURE_TEMPORARILY_DISABLED * * USER_BANNED + * * TARGET_USER_IS_NOT_CONNECTED_TO_VOICE * * ALREADY_CROSSPOSTED + * * APPLICATION_COMMAND_ALREADY_EXISTS * * MISSING_ACCESS * * INVALID_ACCOUNT_TYPE * * CANNOT_EXECUTE_ON_DM @@ -476,81 +522,196 @@ export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"] * * CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL * * INVALID_OR_TAKEN_INVITE_CODE * * CANNOT_EXECUTE_ON_SYSTEM_MESSAGE + * * CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE * * INVALID_OAUTH_TOKEN + * * MISSING_REQUIRED_OAUTH2_SCOPE + * * INVALID_WEBHOOK_TOKEN_PROVIDED + * * INVALID_ROLE + * * INVALID_RECIPIENT * * BULK_DELETE_MESSAGE_TOO_OLD * * INVALID_FORM_BODY * * INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT * * INVALID_API_VERSION + * * FILE_EXCEEDS_MAXIMUM_SIZE + * * INVALID_FILE_UPLOADED + * * CANNOT_SELF_REDEEM_GIFT + * * PAYMENT_SOURCE_REQUIRED * * CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL + * * INVALID_STICKER_SENT + * * CANNOT_EDIT_ARCHIVED_THREAD + * * INVALID_THREAD_NOTIFICATION_SETTINGS + * * BEFORE_EARLIER_THAN_THREAD_CREATION_DATE + * * SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION + * * SERVER_NEEDS_MONETIZATION_ENABLED + * * TWO_FACTOR_REQUIRED + * * NO_USERS_WITH_DISCORDTAG_EXIST * * REACTION_BLOCKED * * RESOURCE_OVERLOADED + * * STAGE_ALREADY_OPEN + * * THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE + * * THREAD_IS_LOCKED + * * MAXIMUM_NUMBER_OF_ACTIVE_THREADS + * * MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS + * * INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE + * * LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES + * * STICKER_MAXIMUM_FRAMERATE + * * STICKER_MAXIMUM_FRAME_COUNT + * * LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS + * * STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE + * * STICKER_ANIMATION_DURATION_MAXIMUM + * * UNKNOWN_VOICE_STATE * @typedef {string} APIError */ -export const APIErrors = { - UNKNOWN_ACCOUNT: 10001, - UNKNOWN_APPLICATION: 10002, - UNKNOWN_CHANNEL: 10003, - UNKNOWN_GUILD: 10004, - UNKNOWN_INTEGRATION: 10005, - UNKNOWN_INVITE: 10006, - UNKNOWN_MEMBER: 10007, - UNKNOWN_MESSAGE: 10008, - UNKNOWN_OVERWRITE: 10009, - UNKNOWN_PROVIDER: 10010, - UNKNOWN_ROLE: 10011, - UNKNOWN_TOKEN: 10012, - UNKNOWN_USER: 10013, - UNKNOWN_EMOJI: 10014, - UNKNOWN_WEBHOOK: 10015, - UNKNOWN_BAN: 10026, - UNKNOWN_GUILD_TEMPLATE: 10057, - BOT_PROHIBITED_ENDPOINT: 20001, - BOT_ONLY_ENDPOINT: 20002, - CHANNEL_HIT_WRITE_RATELIMIT: 20028, - MAXIMUM_GUILDS: 30001, - MAXIMUM_FRIENDS: 30002, - MAXIMUM_PINS: 30003, - MAXIMUM_ROLES: 30005, - MAXIMUM_WEBHOOKS: 30007, - MAXIMUM_REACTIONS: 30010, - MAXIMUM_CHANNELS: 30013, - MAXIMUM_ATTACHMENTS: 30015, - MAXIMUM_INVITES: 30016, - GUILD_ALREADY_HAS_TEMPLATE: 30031, - UNAUTHORIZED: 40001, - ACCOUNT_VERIFICATION_REQUIRED: 40002, - REQUEST_ENTITY_TOO_LARGE: 40005, - FEATURE_TEMPORARILY_DISABLED: 40006, - USER_BANNED: 40007, - ALREADY_CROSSPOSTED: 40033, - MISSING_ACCESS: 50001, - INVALID_ACCOUNT_TYPE: 50002, - CANNOT_EXECUTE_ON_DM: 50003, - EMBED_DISABLED: 50004, - CANNOT_EDIT_MESSAGE_BY_OTHER: 50005, - CANNOT_SEND_EMPTY_MESSAGE: 50006, - CANNOT_MESSAGE_USER: 50007, - CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: 50008, - CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: 50009, - OAUTH2_APPLICATION_BOT_ABSENT: 50010, - MAXIMUM_OAUTH2_APPLICATIONS: 50011, - INVALID_OAUTH_STATE: 50012, - MISSING_PERMISSIONS: 50013, - INVALID_AUTHENTICATION_TOKEN: 50014, - NOTE_TOO_LONG: 50015, - INVALID_BULK_DELETE_QUANTITY: 50016, - CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: 50019, - INVALID_OR_TAKEN_INVITE_CODE: 50020, - CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: 50021, - INVALID_OAUTH_TOKEN: 50025, - BULK_DELETE_MESSAGE_TOO_OLD: 50034, - INVALID_FORM_BODY: 50035, - INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036, - INVALID_API_VERSION: 50041, - CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: 50074, - REACTION_BLOCKED: 90001, - RESOURCE_OVERLOADED: 130000, -}; +export const DiscordApiErrors = { + //https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes + GENERAL_ERROR: new ApiError("General error (such as a malformed request body, amongst other things)", 0), + UNKNOWN_ACCOUNT: new ApiError("Unknown account", 10001), + UNKNOWN_APPLICATION: new ApiError("Unknown application", 10002), + UNKNOWN_CHANNEL: new ApiError("Unknown channel", 10003), + UNKNOWN_GUILD: new ApiError("Unknown guild", 10004), + UNKNOWN_INTEGRATION: new ApiError("Unknown integration", 10005), + UNKNOWN_INVITE: new ApiError("Unknown invite", 10006), + UNKNOWN_MEMBER: new ApiError("Unknown member", 10007), + UNKNOWN_MESSAGE: new ApiError("Unknown message", 10008), + UNKNOWN_OVERWRITE: new ApiError("Unknown permission overwrite", 10009), + UNKNOWN_PROVIDER: new ApiError("Unknown provider", 10010), + UNKNOWN_ROLE: new ApiError("Unknown role", 10011), + UNKNOWN_TOKEN: new ApiError("Unknown token", 10012), + UNKNOWN_USER: new ApiError("Unknown user", 10013), + UNKNOWN_EMOJI: new ApiError("Unknown emoji", 10014), + UNKNOWN_WEBHOOK: new ApiError("Unknown webhook", 10015), + UNKNOWN_WEBHOOK_SERVICE: new ApiError("Unknown webhook service", 10016), + UNKNOWN_SESSION: new ApiError("Unknown session", 10020), + UNKNOWN_BAN: new ApiError("Unknown ban", 10026), + UNKNOWN_SKU: new ApiError("Unknown SKU", 10027), + UNKNOWN_STORE_LISTING: new ApiError("Unknown Store Listing", 10028), + UNKNOWN_ENTITLEMENT: new ApiError("Unknown entitlement", 10029), + UNKNOWN_BUILD: new ApiError("Unknown build", 10030), + UNKNOWN_LOBBY: new ApiError("Unknown lobby", 10031), + UNKNOWN_BRANCH: new ApiError("Unknown branch", 10032), + UNKNOWN_STORE_DIRECTORY_LAYOUT: new ApiError("Unknown store directory layout", 10033), + UNKNOWN_REDISTRIBUTABLE: new ApiError("Unknown redistributable", 10036), + UNKNOWN_GIFT_CODE: new ApiError("Unknown gift code", 10038), + UNKNOWN_STREAM: new ApiError("Unknown stream", 10049), + UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN: new ApiError("Unknown premium server subscribe cooldown", 10050), + UNKNOWN_GUILD_TEMPLATE: new ApiError("Unknown guild template", 10057), + UNKNOWN_DISCOVERABLE_SERVER_CATEGORY: new ApiError("Unknown discoverable server category", 10059), + UNKNOWN_STICKER: new ApiError("Unknown sticker", 10060), + UNKNOWN_INTERACTION: new ApiError("Unknown interaction", 10062), + UNKNOWN_APPLICATION_COMMAND: new ApiError("Unknown application command", 10063), + UNKNOWN_APPLICATION_COMMAND_PERMISSIONS: new ApiError("Unknown application command permissions", 10066), + UNKNOWN_STAGE_INSTANCE: new ApiError("Unknown Stage Instance", 10067), + UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM: new ApiError("Unknown Guild Member Verification Form", 10068), + UNKNOWN_GUILD_WELCOME_SCREEN: new ApiError("Unknown Guild Welcome Screen", 10069), + UNKNOWN_GUILD_SCHEDULED_EVENT: new ApiError("Unknown Guild Scheduled Event", 10070), + UNKNOWN_GUILD_SCHEDULED_EVENT_USER: new ApiError("Unknown Guild Scheduled Event User", 10071), + BOT_PROHIBITED_ENDPOINT: new ApiError("Bots cannot use this endpoint", 20001), + BOT_ONLY_ENDPOINT: new ApiError("Only bots can use this endpoint", 20002), + EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT: new ApiError("Explicit content cannot be sent to the desired recipient(s)", 20009), + ACTION_NOT_AUTHORIZED_ON_APPLICATION: new ApiError("You are not authorized to perform this action on this application", 20012), + SLOWMODE_RATE_LIMIT: new ApiError("This action cannot be performed due to slowmode rate limit", 20016), + ONLY_OWNER: new ApiError("Only the owner of this account can perform this action", 20018), + ANNOUNCEMENT_RATE_LIMITS: new ApiError("This message cannot be edited due to announcement rate limits", 20022), + CHANNEL_WRITE_RATELIMIT: new ApiError("The channel you are writing has hit the write rate limit", 20028), + WORDS_NOT_ALLOWED: new ApiError("Your Stage topic, server name, server description, or channel names contain words that are not allowed", 20031), + GUILD_PREMIUM_LEVEL_TOO_LOW: new ApiError("Guild premium subscription level too low", 20035), + MAXIMUM_GUILDS: new ApiError("Maximum number of guilds reached ({})", 30001, undefined, ["100"]), + MAXIMUM_FRIENDS: new ApiError("Maximum number of friends reached ({})", 30002, undefined, ["1000"]), + MAXIMUM_PINS: new ApiError("Maximum number of pins reached for the channel ({})", 30003, undefined, ["50"]), + MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED: new ApiError("Maximum number of recipients reached ({})", 30004, undefined, ["10"]), + MAXIMUM_ROLES: new ApiError("Maximum number of guild roles reached ({})", 30005, undefined, ["250"]), + MAXIMUM_WEBHOOKS: new ApiError("Maximum number of webhooks reached ({})", 30007, undefined, ["10"]), + MAXIMUM_NUMBER_OF_EMOJIS_REACHED: new ApiError("Maximum number of emojis reached", 30008), + MAXIMUM_REACTIONS: new ApiError("Maximum number of reactions reached ({})", 30010, undefined, ["20"]), + MAXIMUM_CHANNELS: new ApiError("Maximum number of guild channels reached ({})", 30013, undefined, ["500"]), + MAXIMUM_ATTACHMENTS: new ApiError("Maximum number of attachments in a message reached ({})", 30015, undefined, ["10"]), + MAXIMUM_INVITES: new ApiError("Maximum number of invites reached ({})", 30016, undefined, ["1000"]), + MAXIMUM_ANIMATED_EMOJIS: new ApiError("Maximum number of animated emojis reached", 30018), + MAXIMUM_SERVER_MEMBERS: new ApiError("Maximum number of server members reached", 30019), + MAXIMUM_SERVER_CATEGORIES: new ApiError("Maximum number of server categories has been reached ({})", 30030, undefined, ["5"]), + GUILD_ALREADY_HAS_TEMPLATE: new ApiError("Guild already has a template", 30031), + MAXIMUM_THREAD_PARTICIPANTS: new ApiError("Max number of thread participants has been reached", 30033), + MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS: new ApiError("Maximum number of bans for non-guild members have been exceeded", 30035), + MAXIMUM_BANS_FETCHES: new ApiError("Maximum number of bans fetches has been reached", 30037), + MAXIMUM_STICKERS: new ApiError("Maximum number of stickers reached", 30039), + MAXIMUM_PRUNE_REQUESTS: new ApiError("Maximum number of prune requests has been reached. Try again later", 30040), + UNAUTHORIZED: new ApiError("Unauthorized. Provide a valid token and try again", 40001), + ACCOUNT_VERIFICATION_REQUIRED: new ApiError("You need to verify your account in order to perform this action", 40002), + OPENING_DIRECT_MESSAGES_TOO_FAST: new ApiError("You are opening direct messages too fast", 40003), + REQUEST_ENTITY_TOO_LARGE: new ApiError("Request entity too large. Try sending something smaller in size", 40005), + FEATURE_TEMPORARILY_DISABLED: new ApiError("This feature has been temporarily disabled server-side", 40006), + USER_BANNED: new ApiError("The user is banned from this guild", 40007), + TARGET_USER_IS_NOT_CONNECTED_TO_VOICE: new ApiError("Target user is not connected to voice", 40032), + ALREADY_CROSSPOSTED: new ApiError("This message has already been crossposted", 40033), + APPLICATION_COMMAND_ALREADY_EXISTS: new ApiError("An application command with that name already exists", 40041), + MISSING_ACCESS: new ApiError("Missing access", 50001), + INVALID_ACCOUNT_TYPE: new ApiError("Invalid account type", 50002), + CANNOT_EXECUTE_ON_DM: new ApiError("Cannot execute action on a DM channel", 50003), + EMBED_DISABLED: new ApiError("Guild widget disabled", 50004), + CANNOT_EDIT_MESSAGE_BY_OTHER: new ApiError("Cannot edit a message authored by another user", 50005), + CANNOT_SEND_EMPTY_MESSAGE: new ApiError("Cannot send an empty message", 50006), + CANNOT_MESSAGE_USER: new ApiError("Cannot send messages to this user", 50007), + CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: new ApiError("Cannot send messages in a voice channel", 50008), + CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: new ApiError("Channel verification level is too high for you to gain access", 50009), + OAUTH2_APPLICATION_BOT_ABSENT: new ApiError("OAuth2 application does not have a bot", 50010), + MAXIMUM_OAUTH2_APPLICATIONS: new ApiError("OAuth2 application limit reached", 50011), + INVALID_OAUTH_STATE: new ApiError("Invalid OAuth2 state", 50012), + MISSING_PERMISSIONS: new ApiError("You lack permissions to perform that action", 50013), + INVALID_AUTHENTICATION_TOKEN: new ApiError("Invalid authentication token provided", 50014), + NOTE_TOO_LONG: new ApiError("Note was too long", 50015), + INVALID_BULK_DELETE_QUANTITY: new ApiError("Provided too few or too many messages to delete. Must provide at least {} and fewer than {} messages to delete", 50016, undefined, ["2","100"]), + CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: new ApiError("A message can only be pinned to the channel it was sent in", 50019), + INVALID_OR_TAKEN_INVITE_CODE: new ApiError("Invite code was either invalid or taken", 50020), + CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: new ApiError("Cannot execute action on a system message", 50021), + CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE: new ApiError("Cannot execute action on this channel type", 50024), + INVALID_OAUTH_TOKEN: new ApiError("Invalid OAuth2 access token provided", 50025), + MISSING_REQUIRED_OAUTH2_SCOPE: new ApiError("Missing required OAuth2 scope", 50026), + INVALID_WEBHOOK_TOKEN_PROVIDED: new ApiError("Invalid webhook token provided", 50027), + INVALID_ROLE: new ApiError("Invalid role", 50028), + INVALID_RECIPIENT: new ApiError("Invalid Recipient(s)", 50033), + BULK_DELETE_MESSAGE_TOO_OLD: new ApiError("A message provided was too old to bulk delete", 50034), + INVALID_FORM_BODY: new ApiError("Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided", 50035), + INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: new ApiError("An invite was accepted to a guild the application's bot is not in", 50036), + INVALID_API_VERSION: new ApiError("Invalid API version provided", 50041), + FILE_EXCEEDS_MAXIMUM_SIZE: new ApiError("File uploaded exceeds the maximum size", 50045), + INVALID_FILE_UPLOADED: new ApiError("Invalid file uploaded", 50046), + CANNOT_SELF_REDEEM_GIFT: new ApiError("Cannot self-redeem this gift", 50054), + PAYMENT_SOURCE_REQUIRED: new ApiError("Payment source required to redeem gift", 50070), + CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: new ApiError("Cannot delete a channel required for Community guilds", 50074), + INVALID_STICKER_SENT: new ApiError("Invalid sticker sent", 50081), + CANNOT_EDIT_ARCHIVED_THREAD: new ApiError("Tried to perform an operation on an archived thread, such as editing a message or adding a user to the thread", 50083), + INVALID_THREAD_NOTIFICATION_SETTINGS: new ApiError("Invalid thread notification settings", 50084), + BEFORE_EARLIER_THAN_THREAD_CREATION_DATE: new ApiError("before value is earlier than the thread creation date", 50085), + SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION: new ApiError("This server is not available in your location", 50095), + SERVER_NEEDS_MONETIZATION_ENABLED: new ApiError("This server needs monetization enabled in order to perform this action", 50097), + TWO_FACTOR_REQUIRED: new ApiError("Two factor is required for this operation", 60003), + NO_USERS_WITH_DISCORDTAG_EXIST: new ApiError("No users with DiscordTag exist", 80004), + REACTION_BLOCKED: new ApiError("Reaction was blocked", 90001), + RESOURCE_OVERLOADED: new ApiError("API resource is currently overloaded. Try again a little later", 130000), + STAGE_ALREADY_OPEN: new ApiError("The Stage is already open", 150006), + THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE: new ApiError("A thread has already been created for this message", 160004), + THREAD_IS_LOCKED: new ApiError("Thread is locked", 160005), + MAXIMUM_NUMBER_OF_ACTIVE_THREADS: new ApiError("Maximum number of active threads reached", 160006), + MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS: new ApiError("Maximum number of active announcement threads reached", 160007), + INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: new ApiError("Invalid JSON for uploaded Lottie file", 170001), + LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: new ApiError("Uploaded Lotties cannot contain rasterized images such as PNG or JPEG", 170002), + STICKER_MAXIMUM_FRAMERATE: new ApiError("Sticker maximum framerate exceeded", 170003), + STICKER_MAXIMUM_FRAME_COUNT: new ApiError("Sticker frame count exceeds maximum of {} frames", 170004, undefined, ["1000"]), + LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS: new ApiError("Lottie animation maximum dimensions exceeded", 170005), + STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE: new ApiError("Sticker frame rate is either too small or too large", 170006), + STICKER_ANIMATION_DURATION_MAXIMUM: new ApiError("Sticker animation duration exceeds maximum of {} seconds", 170007, undefined, ["5"]), + + + //Other errors + UNKNOWN_VOICE_STATE: new ApiError("Unknown Voice State", 10065, 404), +} + +/** + * An error encountered while performing an API request (Fosscord only). Here are the potential errors: + */ +export const FosscordApiErrors = { + +} /** * The value set for a guild's default message notifications, e.g. `ALL`. Here are the available types: -- cgit 1.5.1 From a58cc00f83e03965acc49b1d6aec134499318d95 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:57:19 +0200 Subject: fix member roles --- api/src/routes/guilds/#guild_id/index.ts | 4 ++-- api/src/routes/guilds/#guild_id/members/#member_id/index.ts | 2 +- api/src/routes/guilds/#guild_id/roles.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index d205b164..6f55be3b 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -14,8 +14,8 @@ router.get("/", async (req: Request, res: Response) => { const [guild, member_count, member] = await Promise.all([ Guild.findOneOrFail({ id: guild_id }), - Member.count({ guild_id: guild_id, id: req.user_id }), - Member.findOneOrFail(req.user_id) + Member.count({ guild: { id: guild_id }, id: req.user_id }), + Member.findOneOrFail({ id: req.user_id }) ]); if (!member_count) throw new HTTPError("You are not a member of the guild you are trying to access", 401); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts index db29cd08..d9ce91c0 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts @@ -43,7 +43,7 @@ router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) emitEvent({ event: "GUILD_MEMBER_UPDATE", guild_id, - data: { ...member, roles: member.role_ids } + data: { ...member, roles: member.roles.map((x) => x.id) } } as GuildMemberUpdateEvent) ]); diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index 796a8eb8..f6ac8caa 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -91,7 +91,7 @@ router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Res const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_ROLES"); - const role = new Role({ ...body, role_id, guild_id, permissions: perms.bitfield & (body.permissions || 0n) }); + const role = new Role({ ...body, id: role_id, guild_id, permissions: perms.bitfield & (body.permissions || 0n) }); await Promise.all([ role.save(), -- cgit 1.5.1 From e8c9bae897ba370ecdb0fcb1e0f16345f1938038 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:57:44 +0200 Subject: Channel utility methods in entity --- api/src/routes/guilds/#guild_id/channels.ts | 3 +- api/src/util/Channel.ts | 52 ----------------------------- 2 files changed, 1 insertion(+), 54 deletions(-) delete mode 100644 api/src/util/Channel.ts (limited to 'api/src') diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index 7b0e94b6..5aa1d33d 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -4,7 +4,6 @@ import { HTTPError } from "lambert-server"; import { ChannelModifySchema } from "../../../schema/Channel"; import { check } from "../../../util/instanceOf"; -import { createChannel } from "../../../util/Channel"; const router = Router(); router.get("/", async (req: Request, res: Response) => { @@ -22,7 +21,7 @@ router.post("/", check(ChannelModifySchema), async (req: Request, res: Response) const { guild_id } = req.params; const body = req.body as ChannelModifySchema; - const channel = await createChannel({ ...body, guild_id }, req.user_id); + const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id); res.status(201).json(channel); }); diff --git a/api/src/util/Channel.ts b/api/src/util/Channel.ts deleted file mode 100644 index bc9217ce..00000000 --- a/api/src/util/Channel.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { ChannelCreateEvent, Channel, ChannelType, emitEvent, getPermission, Snowflake } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; - -// TODO: DM channel -export async function createChannel( - channel: Partial, - user_id: string = "0", - opts?: { - keepId?: boolean; - skipExistsCheck?: boolean; - } -) { - // Always check if user has permission first - const permissions = await getPermission(user_id, channel.guild_id); - permissions.hasThrow("MANAGE_CHANNELS"); - - switch (channel.type) { - case ChannelType.GUILD_TEXT: - case ChannelType.GUILD_VOICE: - if (channel.parent_id && !opts?.skipExistsCheck) { - const exists = await Channel.findOneOrFail({ id: channel.parent_id }); - if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400); - if (exists.guild_id !== channel.guild_id) throw new HTTPError("The category channel needs to be in the guild"); - } - break; - case ChannelType.GUILD_CATEGORY: - break; - case ChannelType.DM: - case ChannelType.GROUP_DM: - throw new HTTPError("You can't create a dm channel in a guild"); - // TODO: check if guild is community server - case ChannelType.GUILD_STORE: - case ChannelType.GUILD_NEWS: - default: - throw new HTTPError("Not yet supported"); - } - - if (!channel.permission_overwrites) channel.permission_overwrites = []; - // TODO: auto generate position - - channel = await new Channel({ - ...channel, - ...(!opts?.keepId && { id: Snowflake.generate() }), - created_at: new Date(), - // @ts-ignore - recipient_ids: null - }).save(); - - await emitEvent({ event: "CHANNEL_CREATE", data: channel, guild_id: channel.guild_id } as ChannelCreateEvent); - - return channel; -} -- cgit 1.5.1 From adc75ca76b5561d6d4e8d01c5fdd574f8b98363a Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:58:28 +0200 Subject: :sparkles: channel recipients --- api/src/routes/users/@me/channels.ts | 7 +++--- gateway/package-lock.json | 4 ++-- gateway/src/listener/listener.ts | 10 ++++----- gateway/src/opcodes/Identify.ts | 42 ++++++++++++++++++++++++------------ 4 files changed, 39 insertions(+), 24 deletions(-) (limited to 'api/src') diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index ab203571..880e09c1 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -5,13 +5,14 @@ import { HTTPError } from "lambert-server"; import { DmChannelCreateSchema } from "../../../schema/Channel"; import { check } from "../../../util/instanceOf"; import { In } from "typeorm"; +import { Recipient } from "../../../../../util/dist/entities/Recipient"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - var channels = await Channel.find({ recipient_ids: req.user_id }); + const recipients = await Recipient.find({ where: { id: req.user_id }, relations: ["channel"] }); - res.json(channels); + res.json(recipients.map((x) => x.channel)); }); router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Response) => { @@ -34,7 +35,7 @@ router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Respons owner_id: req.user_id, created_at: new Date(), last_message_id: null, - recipient_ids: [...body.recipients, req.user_id] + recipients: [...body.recipients.map((x) => new Recipient({ id: x })), new Recipient({ id: req.user_id })] }).save(); await emitEvent({ event: "CHANNEL_CREATE", data: channel, user_id: req.user_id } as ChannelCreateEvent); diff --git a/gateway/package-lock.json b/gateway/package-lock.json index 5190b6b0..40606ecd 100644 --- a/gateway/package-lock.json +++ b/gateway/package-lock.json @@ -55,7 +55,7 @@ "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", "typeorm": "^0.2.37", - "typescript": "^4.3.5", + "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" }, "devDependencies": { @@ -1949,7 +1949,7 @@ "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", "typeorm": "^0.2.37", - "typescript": "^4.3.5", + "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" } }, diff --git a/gateway/src/listener/listener.ts b/gateway/src/listener/listener.ts index 8fa96555..75ca1680 100644 --- a/gateway/src/listener/listener.ts +++ b/gateway/src/listener/listener.ts @@ -14,7 +14,8 @@ import { Send } from "../util/Send"; import WebSocket from "../util/WebSocket"; import "missing-native-js-functions"; import { Channel as AMQChannel } from "amqplib"; -import { In } from "../../../util/node_modules/typeorm"; +import { In, Like } from "../../../util/node_modules/typeorm"; +import { Recipient } from "../../../util/dist/entities/Recipient"; // TODO: close connection on Invalidated Token // TODO: check intent @@ -28,10 +29,9 @@ export async function setupListener(this: WebSocket) { const members = await Member.find({ where: { id: this.user_id } }); const guild_ids = members.map((x) => x.guild_id); const user = await User.findOneOrFail({ id: this.user_id }); - const channels = await Channel.find({ - where: [{ recipient_ids: this.user_id }, { guild_id: In(guild_ids) }], - }); - const dm_channels = channels.filter((x) => !x.guild_id); + const recipients = await Recipient.find({ where: { id: this.user_id }, relations: ["channel"] }); + const channels = await Channel.find({ guild_id: In(guild_ids) }); + const dm_channels = recipients.map((x) => x.channel); const guild_channels = channels.filter((x) => x.guild_id); const opts: { acknowledge: boolean; channel?: AMQChannel } = { acknowledge: true }; diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 87008998..958f1b73 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -1,12 +1,25 @@ import { CLOSECODES, Payload, OPCODES } from "../util/Constants"; import WebSocket from "../util/WebSocket"; -import { Channel, checkToken, Guild, Intents, Member, ReadyEventData, User, EVENTEnum, Config } from "@fosscord/util"; +import { + Channel, + checkToken, + Guild, + Intents, + Member, + ReadyEventData, + User, + EVENTEnum, + Config, + dbConnection, +} from "@fosscord/util"; import { setupListener } from "../listener/listener"; import { IdentifySchema } from "../schema/Identify"; import { Send } from "../util/Send"; // import experiments from "./experiments.json"; const experiments: any = []; import { check } from "./instanceOf"; +import { Like } from "../../../util/node_modules/typeorm"; +import { Recipient } from "../../../util/dist/entities/Recipient"; // TODO: bot sharding // TODO: check priviliged intents @@ -42,17 +55,21 @@ export async function onIdentify(this: WebSocket, data: Payload) { } } - const members = await Member.find({ where: { id: this.user_id }, relations: ["guild"] }); + const members = await Member.find({ + where: { id: this.user_id }, + relations: ["guild", "guild.channels", "guild.emojis", "guild.roles", "guild.stickers", "user", "roles"], + }); const merged_members = members.map((x: any) => { - const y = { ...x, user_id: x.id }; - delete y.settings; - delete y.id; - return [y]; + return [x]; }) as Member[][]; - const guilds = members.map((x) => x.guild); + const guilds = members.map((x) => ({ ...x.guild, joined_at: x.joined_at })); const user_guild_settings_entries = members.map((x) => x.settings); - const channels = await Channel.find({ where: { recipient_ids: this.user_id } }); + const recipients = await Recipient.find({ + where: { id: this.user_id }, + relations: ["channel", "channel.recipients"], + }); + const channels = recipients.map((x) => x.channel); const user = await User.findOneOrFail({ id: this.user_id }); if (!user) return this.close(CLOSECODES.Authentication_failed); @@ -63,6 +80,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { public_flags: user.public_flags, avatar: user.avatar, bot: user.bot, + bio: user.bio, }; const privateUser = { @@ -116,11 +134,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { partial: false, // TODO partial version: 642, }, - // @ts-ignore - private_channels: channels.map((x): ChannelDocument => { - delete x.recipients; - return x; - }), + private_channels: channels, session_id: "", // TODO analytics_token: "", // TODO connected_accounts: [], // TODO @@ -134,7 +148,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { // @ts-ignore experiments: experiments, // TODO guild_join_requests: [], // TODO what is this? - users: [public_user, ...channels.map((x: any) => x.recipients).flat()].unique(), // TODO + users: [public_user].unique(), // TODO merged_members: merged_members, // shard // TODO: only for bots sharding // application // TODO for applications -- cgit 1.5.1 From 3f66c101789f9a6d065490d3e3265c764698113f Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:58:47 +0200 Subject: :bug: db query fixes --- Status.ts | 7 ----- api/package-lock.json | 18 +++++------ api/package.json | 2 +- api/src/middlewares/Authentication.ts | 8 ++--- .../routes/channels/#channel_id/messages/index.ts | 36 ++++++++++++---------- api/src/routes/channels/#channel_id/pins.ts | 2 +- api/src/routes/channels/#channel_id/typing.ts | 2 +- api/src/routes/users/@me/disable.ts | 2 +- api/src/routes/users/@me/index.ts | 2 +- api/src/routes/users/@me/relationships.ts | 12 ++++---- api/src/schema/Message.ts | 2 +- bundle/package-lock.json | 13 ++++++++ bundle/package.json | 1 + 13 files changed, 59 insertions(+), 48 deletions(-) delete mode 100644 Status.ts (limited to 'api/src') diff --git a/Status.ts b/Status.ts deleted file mode 100644 index c4dab586..00000000 --- a/Status.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type Status = "idle" | "dnd" | "online" | "offline"; - -export interface ClientStatus { - desktop?: string; // e.g. Windows/Linux/Mac - mobile?: string; // e.g. iOS/Android - web?: string; // e.g. browser, bot account -} diff --git a/api/package-lock.json b/api/package-lock.json index 758e51c8..63724688 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -60,7 +60,7 @@ "saslprep": "^1.0.3", "ts-node": "^9.1.1", "ts-node-dev": "^1.1.6", - "typescript": "^4.1.2" + "typescript": "^4.4.2" } }, "../util": { @@ -83,7 +83,7 @@ "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", "typeorm": "^0.2.37", - "typescript": "^4.3.5", + "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" }, "devDependencies": { @@ -11430,9 +11430,9 @@ } }, "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz", + "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -12637,7 +12637,7 @@ "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", "typeorm": "^0.2.37", - "typescript": "^4.3.5", + "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" } }, @@ -21387,9 +21387,9 @@ } }, "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz", + "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", "dev": true }, "umd": { diff --git a/api/package.json b/api/package.json index b8bb4d45..37db6ff3 100644 --- a/api/package.json +++ b/api/package.json @@ -52,7 +52,7 @@ "saslprep": "^1.0.3", "ts-node": "^9.1.1", "ts-node-dev": "^1.1.6", - "typescript": "^4.1.2" + "typescript": "^4.4.2" }, "dependencies": { "@fosscord/util": "file:../util", diff --git a/api/src/middlewares/Authentication.ts b/api/src/middlewares/Authentication.ts index 34a66a6b..a300c786 100644 --- a/api/src/middlewares/Authentication.ts +++ b/api/src/middlewares/Authentication.ts @@ -18,9 +18,9 @@ export const API_PREFIX_TRAILING_SLASH = /^\/api(\/v\d+)?\//; declare global { namespace Express { interface Request { - user_id: any; + user_id: string; user_bot: boolean; - token: any; + token: string; } } } @@ -47,7 +47,7 @@ export async function Authentication(req: Request, res: Response, next: NextFunc req.user_id = decoded.id; req.user_bot = user.bot; return next(); - } catch (error) { - return next(new HTTPError(error.toString(), 400)); + } catch (error: any) { + return next(new HTTPError(error?.toString(), 400)); } } diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index 17944548..86de6de8 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -31,10 +31,7 @@ export function isTextChannel(type: ChannelType): boolean { // get messages router.get("/", async (req: Request, res: Response) => { const channel_id = req.params.channel_id; - const channel = await Channel.findOneOrFail( - { id: channel_id }, - { select: ["guild_id", "type", "permission_overwrites", "recipient_ids", "owner_id"] } - ); // lean is needed, because we don't want to populate .recipients that also auto deletes .recipient_ids + const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel) throw new HTTPError("Channel not found", 404); isTextChannel(channel.type); @@ -56,7 +53,12 @@ router.get("/", async (req: Request, res: Response) => { permissions.hasThrow("VIEW_CHANNEL"); if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); - var query: FindManyOptions & { where: { id?: any } } = { order: { id: "DESC" }, take: limit, where: { channel_id } }; + var query: FindManyOptions & { where: { id?: any } } = { + order: { id: "DESC" }, + take: limit, + where: { channel_id }, + relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"] + }; if (after) query.where.id = MoreThan(after); else if (before) query.where.id = LessThan(before); @@ -69,18 +71,20 @@ router.get("/", async (req: Request, res: Response) => { const messages = await Message.find(query); - return res.json(messages).map((x) => { - (x.reactions || []).forEach((x: any) => { + return res.json( + messages.map((x) => { + (x.reactions || []).forEach((x: any) => { + // @ts-ignore + if ((x.user_ids || []).includes(req.user_id)) x.me = true; + // @ts-ignore + delete x.user_ids; + }); // @ts-ignore - if ((x.user_ids || []).includes(req.user_id)) x.me = true; - // @ts-ignore - delete x.user_ids; - }); - // @ts-ignore - if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: "0", avatar: null }; + if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: "0", avatar: null }; - return x; - }); + return x; + }) + ); }); // TODO: config max upload size @@ -136,5 +140,5 @@ router.post("/", messageUpload.single("file"), async (req: Request, res: Respons edited_timestamp: undefined }); - return res.send(data); + return res.json(data); }); diff --git a/api/src/routes/channels/#channel_id/pins.ts b/api/src/routes/channels/#channel_id/pins.ts index d83e36ed..96a3fdbf 100644 --- a/api/src/routes/channels/#channel_id/pins.ts +++ b/api/src/routes/channels/#channel_id/pins.ts @@ -14,7 +14,7 @@ router.put("/:message_id", async (req: Request, res: Response) => { // * in dm channels anyone can pin messages -> only check for guilds if (message.guild_id) permission.hasThrow("MANAGE_MESSAGES"); - const pinned_count = await Message.count({ channel_id, pinned: true }); + const pinned_count = await Message.count({ channel: { id: channel_id }, pinned: true }); const { maxPins } = Config.get().limits.channel; if (pinned_count >= maxPins) throw new HTTPError("Max pin count reached: " + maxPins); diff --git a/api/src/routes/channels/#channel_id/typing.ts b/api/src/routes/channels/#channel_id/typing.ts index 2305e8e8..f1fb3c86 100644 --- a/api/src/routes/channels/#channel_id/typing.ts +++ b/api/src/routes/channels/#channel_id/typing.ts @@ -17,7 +17,7 @@ router.post("/", async (req: Request, res: Response) => { channel_id: channel_id, data: { // this is the paylod - member: { ...member, roles: member.role_ids }, + member: { ...member, roles: member.roles.map((x) => x.id) }, channel_id, timestamp, user_id, diff --git a/api/src/routes/users/@me/disable.ts b/api/src/routes/users/@me/disable.ts index ed1dedcc..7b8a130c 100644 --- a/api/src/routes/users/@me/disable.ts +++ b/api/src/routes/users/@me/disable.ts @@ -5,7 +5,7 @@ import bcrypt from "bcrypt"; const router = Router(); router.post("/", async (req: Request, res: Response) => { - const user = await User.findOneOrFail(req.user_id); //User object + const user = await User.findOneOrFail({ id: req.user_id }); //User object let correctpass = true; if (user.data.hash) { diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 274cfb24..d5a5723c 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -37,7 +37,7 @@ router.patch("/", check(UserModifySchema), async (req: Request, res: Response) = if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); - const user = await new User({ ...body }, { id: req.user_id }).save(); + const user = await new User({ ...body, id: req.user_id }).save(); // TODO: dispatch user update event res.json(user); diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 1a89b110..0b864d88 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -26,7 +26,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ const id = friend.id; if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); - const user = await User.findOneOrFail(req.user_id, { relations: ["relationships"], select: userProjection }); + const user = await User.findOneOrFail({ id: req.user_id }, { relations: ["relationships"], select: userProjection }); var relationship = user.relationships.find((x) => x.id === id); const friendRequest = friend.relationships.find((x) => x.id === req.user_id); @@ -67,8 +67,8 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ return res.sendStatus(204); } - var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming }, { id: req.user_id }); - var outgoing_relationship = new Relationship({ nickname: undefined, type: RelationshipType.outgoing }, { id }); + var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming, id: req.user_id }); + var outgoing_relationship = new Relationship({ nickname: undefined, type: RelationshipType.outgoing, id }); if (friendRequest) { if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); @@ -113,7 +113,7 @@ router.put("/:id", check({ $type: new Length(Number, 1, 4) }), async (req: Reque return await updateRelationship( req, res, - await User.findOneOrFail(req.params.id, { relations: ["relationships"], select: userProjection }), + await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships"], select: userProjection }), req.body.type ); }); @@ -135,8 +135,8 @@ router.delete("/:id", 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(req.user_id, { select: userProjection, relations: ["relationships"] }); - const friend = await User.findOneOrFail(id, { select: userProjection, relations: ["relationships"] }); + const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] }); + const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] }); const relationship = user.relationships.find((x) => x.id === id); const friendRequest = friend.relationships.find((x) => x.id === req.user_id); diff --git a/api/src/schema/Message.ts b/api/src/schema/Message.ts index bf10c037..742542df 100644 --- a/api/src/schema/Message.ts +++ b/api/src/schema/Message.ts @@ -81,7 +81,7 @@ export interface MessageCreateSchema { message_id: string; channel_id: string; guild_id?: string; - fail_if_not_exists: boolean; + fail_if_not_exists?: boolean; }; payload_json?: string; file?: any; diff --git a/bundle/package-lock.json b/bundle/package-lock.json index 9fcb5490..59fd96ee 100644 --- a/bundle/package-lock.json +++ b/bundle/package-lock.json @@ -16,6 +16,7 @@ "@fosscord/util": "file:../util", "async-exit-hook": "^2.0.1", "express": "^4.17.1", + "missing-native-js-functions": "^1.2.13", "mongodb-memory-server": "^7.3.6", "node-os-utils": "^1.3.5" }, @@ -70,6 +71,7 @@ "mongoose-long": "^0.3.2", "multer": "^1.4.2", "node-fetch": "^2.6.1", + "supertest": "^6.1.6", "typeorm": "^0.2.37" }, "devDependencies": { @@ -1224,6 +1226,11 @@ "node": "*" } }, + "node_modules/missing-native-js-functions": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.13.tgz", + "integrity": "sha512-1RAArfUkrGkj5N3xJVW251F2PvfP2ozAcxsLLDR6uiiAixTP5Abh8zzGMadepbqgiHC0FGlTSAUNbh9abN4Osg==" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -1916,6 +1923,7 @@ "multer": "^1.4.2", "node-fetch": "^2.6.1", "saslprep": "^1.0.3", + "supertest": "^6.1.6", "ts-node": "^9.1.1", "ts-node-dev": "^1.1.6", "typeorm": "^0.2.37", @@ -2800,6 +2808,11 @@ "brace-expansion": "^1.1.7" } }, + "missing-native-js-functions": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.13.tgz", + "integrity": "sha512-1RAArfUkrGkj5N3xJVW251F2PvfP2ozAcxsLLDR6uiiAixTP5Abh8zzGMadepbqgiHC0FGlTSAUNbh9abN4Osg==" + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", diff --git a/bundle/package.json b/bundle/package.json index 71d7efea..63945135 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -51,6 +51,7 @@ "@fosscord/util": "file:../util", "async-exit-hook": "^2.0.1", "express": "^4.17.1", + "missing-native-js-functions": "^1.2.13", "mongodb-memory-server": "^7.3.6", "node-os-utils": "^1.3.5" } -- cgit 1.5.1