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/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts
index 5fc36f33..f061172a 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";
import {ApiError} from "../util/ApiError";
@@ -19,12 +20,18 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne
message = error.message;
httpcode = error.httpStatus;
}
- 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;
@@ -32,8 +39,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);
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/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/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<Message> & { where: { id?: any } } = { order: { id: "DESC" }, take: limit, where: { channel_id } };
+ var query: FindManyOptions<Message> & { 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/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/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(),
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;
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/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/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<Channel>,
- 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;
-}
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<Message>): Promise<Message> {
- 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<Message> {
+ 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<Message>): Promise<Message> {
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<Message>): Promise<Message> {
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<Message>): Promise<Message> {
}
}
+ 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<Message>) {
- 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;
}
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"
}
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
diff --git a/util/package-lock.json b/util/package-lock.json
index bb04d0bc..47aca2d1 100644
--- a/util/package-lock.json
+++ b/util/package-lock.json
@@ -24,7 +24,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": {
@@ -8235,9 +8235,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"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==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -15150,9 +15150,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=="
},
"typescript-json-schema": {
"version": "0.50.1",
diff --git a/util/package.json b/util/package.json
index af14a521..39af7526 100644
--- a/util/package.json
+++ b/util/package.json
@@ -51,7 +51,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"
},
"jest": {
diff --git a/util/src/entities/Application.ts b/util/src/entities/Application.ts
index b179d171..2092cd4e 100644
--- a/util/src/entities/Application.ts
+++ b/util/src/entities/Application.ts
@@ -2,6 +2,7 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { Team } from "./Team";
+import { User } from "./User";
@Entity("applications")
export class Application extends BaseClass {
@@ -29,8 +30,9 @@ export class Application extends BaseClass {
@Column({ nullable: true })
privacy_policy_url?: string;
- @Column()
- owner_id: string;
+ @JoinColumn({ name: "owner_id" })
+ @ManyToOne(() => User)
+ owner?: User;
@Column({ nullable: true })
summary?: string;
@@ -38,18 +40,12 @@ export class Application extends BaseClass {
@Column()
verify_key: string;
- @RelationId((application: Application) => application.team)
- team_id: string;
-
@JoinColumn({ name: "team_id" })
- @ManyToOne(() => Team, (team: Team) => team.id)
+ @ManyToOne(() => Team)
team?: Team;
- @RelationId((application: Application) => application.guild)
- guild_id: string;
-
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild)
guild: Guild; // if this application is a game sold, this field will be the guild to which it has been linked
@Column({ nullable: true })
diff --git a/util/src/entities/Attachment.ts b/util/src/entities/Attachment.ts
new file mode 100644
index 00000000..ca893400
--- /dev/null
+++ b/util/src/entities/Attachment.ts
@@ -0,0 +1,34 @@
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import { BaseClass } from "./BaseClass";
+
+@Entity("attachments")
+export class Attachment extends BaseClass {
+ @Column()
+ filename: string; // name of file attached
+
+ @Column()
+ size: number; // size of file in bytes
+
+ @Column()
+ url: string; // source url of file
+
+ @Column()
+ proxy_url: string; // a proxied url of file
+
+ @Column({ nullable: true })
+ height?: number; // height of file (if image)
+
+ @Column({ nullable: true })
+ width?: number; // width of file (if image)
+
+ @Column({ nullable: true })
+ content_type?: string;
+
+ @Column({ nullable: true })
+ @RelationId((attachment: Attachment) => attachment.message)
+ message_id: string;
+
+ @JoinColumn({ name: "message_id" })
+ @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments)
+ message: import("./Message").Message;
+}
diff --git a/util/src/entities/AuditLog.ts b/util/src/entities/AuditLog.ts
index ea8aa641..ceeb21fd 100644
--- a/util/src/entities/AuditLog.ts
+++ b/util/src/entities/AuditLog.ts
@@ -43,18 +43,16 @@ export enum AuditLogEvents {
@Entity("audit_logs")
export class AuditLogEntry extends BaseClass {
- @RelationId((auditlog: AuditLogEntry) => auditlog.target)
- target_id: string;
-
@JoinColumn({ name: "target_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
target?: User;
+ @Column({ nullable: true })
@RelationId((auditlog: AuditLogEntry) => auditlog.user)
user_id: string;
@JoinColumn({ name: "user_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
user: User;
@Column({
diff --git a/util/src/entities/Ban.ts b/util/src/entities/Ban.ts
index f1cbd849..e8a6d648 100644
--- a/util/src/entities/Ban.ts
+++ b/util/src/entities/Ban.ts
@@ -5,25 +5,28 @@ import { User } from "./User";
@Entity("bans")
export class Ban extends BaseClass {
+ @Column({ nullable: true })
@RelationId((ban: Ban) => ban.user)
user_id: string;
@JoinColumn({ name: "user_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
user: User;
+ @Column({ nullable: true })
@RelationId((ban: Ban) => ban.guild)
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild)
guild: Guild;
+ @Column({ nullable: true })
@RelationId((ban: Ban) => ban.executor)
executor_id: string;
@JoinColumn({ name: "executor_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
executor: User;
@Column()
diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts
index 63ce5836..0856ccd1 100644
--- a/util/src/entities/BaseClass.ts
+++ b/util/src/entities/BaseClass.ts
@@ -1,13 +1,5 @@
import "reflect-metadata";
-import {
- BaseEntity,
- BeforeInsert,
- BeforeUpdate,
- EntityMetadata,
- FindConditions,
- FindManyOptions,
- PrimaryColumn,
-} from "typeorm";
+import { BaseEntity, BeforeInsert, BeforeUpdate, EntityMetadata, FindConditions, PrimaryColumn } from "typeorm";
import { Snowflake } from "../util/Snowflake";
import "missing-native-js-functions";
@@ -16,13 +8,12 @@ import "missing-native-js-functions";
export class BaseClass extends BaseEntity {
@PrimaryColumn()
- id: string;
+ id: string = Snowflake.generate();
// @ts-ignore
- constructor(public props?: any, public opts: { id?: string } = {}) {
+ constructor(public props?: any) {
super();
-
- this.id = this.opts.id || Snowflake.generate();
+ this.assign(props);
}
get construct(): any {
@@ -35,13 +26,15 @@ export class BaseClass extends BaseEntity {
assign(props: any) {
if (!props || typeof props !== "object") return;
-
- delete props.id;
delete props.opts;
delete props.props;
- const properties = new Set(this.metadata.columns.map((x: any) => x.propertyName));
- // will not include relational properties (e.g. @RelationId @ManyToMany)
+ const properties = new Set(
+ this.metadata.columns
+ .map((x: any) => x.propertyName)
+ .concat(this.metadata.relations.map((x) => x.propertyName))
+ );
+ // will not include relational properties
for (const key in props) {
if (!properties.has(key)) continue;
@@ -65,8 +58,11 @@ export class BaseClass extends BaseEntity {
}
toJSON(): any {
- // @ts-ignore
- return Object.fromEntries(this.metadata.columns.map((x) => [x.propertyName, this[x.propertyName]]));
+ return Object.fromEntries(
+ this.metadata.columns // @ts-ignore
+ .map((x) => [x.propertyName, this[x.propertyName]]) // @ts-ignore
+ .concat(this.metadata.relations.map((x) => [x.propertyName, this[x.propertyName]]))
+ );
}
static increment<T extends BaseClass>(conditions: FindConditions<T>, propertyPath: string, value: number | string) {
diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index 4900f485..e3586dfc 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -1,8 +1,12 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { Message } from "./Message";
import { User } from "./User";
+import { HTTPError } from "lambert-server";
+import { emitEvent, getPermission, Snowflake } from "../util";
+import { ChannelCreateEvent } from "../interfaces";
+import { Recipient } from "./Recipient";
export enum ChannelType {
GUILD_TEXT = 0, // a text channel within a server
@@ -25,40 +29,39 @@ export class Channel extends BaseClass {
@Column({ type: "simple-enum", enum: ChannelType })
type: ChannelType;
- @Column("simple-array")
- @RelationId((channel: Channel) => channel.recipients)
- recipient_ids: string[];
-
- @JoinColumn({ name: "recipient_ids" })
- @ManyToMany(() => User, (user: User) => user.id)
- recipients?: User[];
+ @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { cascade: true })
+ recipients?: Recipient[];
+ @Column({ nullable: true })
@RelationId((channel: Channel) => channel.last_message)
last_message_id: string;
@JoinColumn({ name: "last_message_id" })
- @ManyToOne(() => Message, (message: Message) => message.id)
+ @ManyToOne(() => Message)
last_message?: Message;
+ @Column({ nullable: true })
@RelationId((channel: Channel) => channel.guild)
guild_id?: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild)
guild: Guild;
+ @Column({ nullable: true })
@RelationId((channel: Channel) => channel.parent)
parent_id: string;
@JoinColumn({ name: "parent_id" })
- @ManyToOne(() => Channel, (channel: Channel) => channel.id)
+ @ManyToOne(() => Channel)
parent?: Channel;
+ @Column({ nullable: true })
@RelationId((channel: Channel) => channel.owner)
owner_id: string;
@JoinColumn({ name: "owner_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
owner: User;
@Column({ nullable: true })
@@ -82,14 +85,77 @@ export class Channel extends BaseClass {
@Column({ nullable: true })
user_limit?: number;
- @Column()
- nsfw: boolean;
+ @Column({ nullable: true })
+ nsfw?: boolean;
- @Column()
- rate_limit_per_user: number;
+ @Column({ nullable: true })
+ rate_limit_per_user?: number;
@Column({ nullable: true })
topic?: string;
+
+ // TODO: DM channel
+ static async createChannel(
+ channel: Partial<Channel>,
+ user_id: string = "0",
+ opts?: {
+ keepId?: boolean;
+ skipExistsCheck?: boolean;
+ skipPermissionCheck?: boolean;
+ skipEventEmit?: boolean;
+ }
+ ) {
+ if (!opts?.skipPermissionCheck) {
+ // 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 = {
+ ...channel,
+ ...(!opts?.keepId && { id: Snowflake.generate() }),
+ created_at: new Date(),
+ position: channel.position || 0,
+ };
+
+ await Promise.all([
+ Channel.insert(channel),
+ !opts?.skipEventEmit
+ ? emitEvent({
+ event: "CHANNEL_CREATE",
+ data: channel,
+ guild_id: channel.guild_id,
+ } as ChannelCreateEvent)
+ : Promise.resolve(),
+ ]);
+
+ return channel;
+ }
}
export interface ChannelPermissionOverwrite {
diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts
index f030b167..5eb55933 100644
--- a/util/src/entities/Config.ts
+++ b/util/src/entities/Config.ts
@@ -204,12 +204,12 @@ export const DefaultConfigOptions: ConfigValue = {
window: 5,
},
webhook: {
- count: 5,
- window: 20,
+ count: 10,
+ window: 5,
},
channel: {
- count: 5,
- window: 20,
+ count: 10,
+ window: 5,
},
auth: {
login: {
diff --git a/util/src/entities/ConnectedAccount.ts b/util/src/entities/ConnectedAccount.ts
index e48bc322..75982d01 100644
--- a/util/src/entities/ConnectedAccount.ts
+++ b/util/src/entities/ConnectedAccount.ts
@@ -4,11 +4,12 @@ import { User } from "./User";
@Entity("connected_accounts")
export class ConnectedAccount extends BaseClass {
+ @Column({ nullable: true })
@RelationId((account: ConnectedAccount) => account.user)
user_id: string;
@JoinColumn({ name: "user_id" })
- @ManyToOne(() => User, (user: User) => user.connected_accounts)
+ @ManyToOne(() => User)
user: User;
@Column({ select: false })
diff --git a/util/src/entities/Emoji.ts b/util/src/entities/Emoji.ts
index 4c0fccd3..181aff2c 100644
--- a/util/src/entities/Emoji.ts
+++ b/util/src/entities/Emoji.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { Role } from "./Role";
@@ -15,7 +15,7 @@ export class Emoji extends BaseClass {
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.emojis)
+ @ManyToOne(() => Guild)
guild: Guild;
@Column()
@@ -26,11 +26,4 @@ export class Emoji extends BaseClass {
@Column()
require_colons: boolean;
-
- @RelationId((emoji: Emoji) => emoji.roles)
- role_ids: string[];
-
- @JoinColumn({ name: "role_ids" })
- @ManyToMany(() => Role, (role: Role) => role.id)
- roles: Role[]; // roles this emoji is whitelisted to (new discord feature?)
}
diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index 21b059a3..032a9415 100644
--- a/util/src/entities/Guild.ts
+++ b/util/src/entities/Guild.ts
@@ -1,20 +1,30 @@
import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm";
+import { Ban } from "./Ban";
import { BaseClass } from "./BaseClass";
import { Channel } from "./Channel";
import { Emoji } from "./Emoji";
import { Invite } from "./Invite";
import { Member } from "./Member";
import { Role } from "./Role";
+import { Sticker } from "./Sticker";
+import { Template } from "./Template";
import { User } from "./User";
import { VoiceState } from "./VoiceState";
+import { Webhook } from "./Webhook";
+
+// TODO: application_command_count, application_command_counts: {1: 0, 2: 0, 3: 0}
+// TODO: guild_scheduled_events
+// TODO: stage_instances
+// TODO: threads
@Entity("guilds")
export class Guild extends BaseClass {
+ @Column({ nullable: true })
@RelationId((guild: Guild) => guild.afk_channel)
afk_channel_id?: string;
@JoinColumn({ name: "afk_channel_id" })
- @ManyToOne(() => Channel, (channel: Channel) => channel.id)
+ @ManyToOne(() => Channel)
afk_channel?: Channel;
@Column({ nullable: true })
@@ -25,6 +35,10 @@ export class Guild extends BaseClass {
// @Column({ nullable: true })
// application?: string;
+ @JoinColumn({ name: "ban_ids" })
+ @OneToMany(() => Ban, (ban: Ban) => ban.guild)
+ bans: Ban[];
+
@Column({ nullable: true })
banner?: string;
@@ -64,52 +78,57 @@ export class Guild extends BaseClass {
@Column({ nullable: true })
presence_count?: number; // users online
- @RelationId((guild: Guild) => guild.members)
- member_ids: string[];
-
- @JoinColumn({ name: "member_ids" })
@OneToMany(() => Member, (member: Member) => member.guild)
members: Member[];
- @RelationId((guild: Guild) => guild.roles)
- role_ids: string[];
-
@JoinColumn({ name: "role_ids" })
@OneToMany(() => Role, (role: Role) => role.guild)
roles: Role[];
- @RelationId((guild: Guild) => guild.channels)
- channel_ids: string[];
-
@JoinColumn({ name: "channel_ids" })
@OneToMany(() => Channel, (channel: Channel) => channel.guild)
channels: Channel[];
- @RelationId((guild: Guild) => guild.emojis)
- emoji_ids: string[];
+ @Column({ nullable: true })
+ @RelationId((guild: Guild) => guild.template)
+ template_id: string;
+
+ @JoinColumn({ name: "template_id" })
+ @ManyToOne(() => Template)
+ template: Template;
@JoinColumn({ name: "emoji_ids" })
@OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild)
emojis: Emoji[];
- @RelationId((guild: Guild) => guild.voice_states)
- voice_state_ids: string[];
+ @JoinColumn({ name: "sticker_ids" })
+ @OneToMany(() => Sticker, (sticker: Sticker) => sticker.guild)
+ stickers: Sticker[];
+
+ @JoinColumn({ name: "invite_ids" })
+ @OneToMany(() => Invite, (invite: Invite) => invite.guild)
+ invites: Invite[];
@JoinColumn({ name: "voice_state_ids" })
@OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild)
voice_states: VoiceState[];
+ @JoinColumn({ name: "webhook_ids" })
+ @OneToMany(() => Webhook, (webhook: Webhook) => webhook.guild)
+ webhooks: Webhook[];
+
@Column({ nullable: true })
mfa_level?: number;
@Column()
name: string;
+ @Column({ nullable: true })
@RelationId((guild: Guild) => guild.owner)
owner_id: string;
- @JoinColumn({ name: "owner_id" })
- @OneToOne(() => User)
+ @JoinColumn([{ name: "owner_id", referencedColumnName: "id" }])
+ @ManyToOne(() => User)
owner: User;
@Column({ nullable: true })
@@ -121,18 +140,20 @@ export class Guild extends BaseClass {
@Column({ nullable: true })
premium_tier?: number; // nitro boost level
+ @Column({ nullable: true })
@RelationId((guild: Guild) => guild.public_updates_channel)
public_updates_channel_id: string;
@JoinColumn({ name: "public_updates_channel_id" })
- @OneToOne(() => Channel, (channel: Channel) => channel.id)
+ @ManyToOne(() => Channel)
public_updates_channel?: Channel;
+ @Column({ nullable: true })
@RelationId((guild: Guild) => guild.rules_channel)
rules_channel_id?: string;
@JoinColumn({ name: "rules_channel_id" })
- @OneToOne(() => Channel, (channel: Channel) => channel.id)
+ @ManyToOne(() => Channel)
rules_channel?: string;
@Column({ nullable: true })
@@ -141,11 +162,12 @@ export class Guild extends BaseClass {
@Column({ nullable: true })
splash?: string;
+ @Column({ nullable: true })
@RelationId((guild: Guild) => guild.system_channel)
system_channel_id?: string;
@JoinColumn({ name: "system_channel_id" })
- @OneToOne(() => Channel, (channel: Channel) => channel.id)
+ @ManyToOne(() => Channel)
system_channel?: Channel;
@Column({ nullable: true })
@@ -154,11 +176,12 @@ export class Guild extends BaseClass {
@Column({ nullable: true })
unavailable?: boolean;
+ @Column({ nullable: true })
@RelationId((guild: Guild) => guild.vanity_url)
vanity_url_code?: string;
@JoinColumn({ name: "vanity_url_code" })
- @OneToOne(() => Invite)
+ @ManyToOne(() => Invite)
vanity_url?: Invite;
@Column({ nullable: true })
@@ -176,11 +199,12 @@ export class Guild extends BaseClass {
}[];
};
+ @Column({ nullable: true })
@RelationId((guild: Guild) => guild.widget_channel)
widget_channel_id?: string;
@JoinColumn({ name: "widget_channel_id" })
- @OneToOne(() => Channel, (channel: Channel) => channel.id)
+ @ManyToOne(() => Channel)
widget_channel?: Channel;
@Column({ nullable: true })
diff --git a/util/src/entities/Invite.ts b/util/src/entities/Invite.ts
index d8c6d69c..01e22294 100644
--- a/util/src/entities/Invite.ts
+++ b/util/src/entities/Invite.ts
@@ -27,32 +27,36 @@ export class Invite extends BaseClass {
@Column()
expires_at: Date;
+ @Column({ nullable: true })
@RelationId((invite: Invite) => invite.guild)
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild)
guild: Guild;
+ @Column({ nullable: true })
@RelationId((invite: Invite) => invite.channel)
channel_id: string;
@JoinColumn({ name: "channel_id" })
- @ManyToOne(() => Channel, (channel: Channel) => channel.id)
+ @ManyToOne(() => Channel)
channel: Channel;
+ @Column({ nullable: true })
@RelationId((invite: Invite) => invite.inviter)
inviter_id: string;
@JoinColumn({ name: "inviter_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
inviter: User;
+ @Column({ nullable: true })
@RelationId((invite: Invite) => invite.target_user)
target_user_id: string;
@JoinColumn({ name: "target_user_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
target_user?: string; // could be used for "User specific invites" https://github.com/fosscord/fosscord/issues/62
@Column({ nullable: true })
diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
index c5d289ef..d2d78bb9 100644
--- a/util/src/entities/Member.ts
+++ b/util/src/entities/Member.ts
@@ -15,27 +15,22 @@ import { Role } from "./Role";
@Entity("members")
export class Member extends BaseClass {
- @RelationId((member: Member) => member.user)
- user_id: string;
-
- @JoinColumn({ name: "user_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @JoinColumn({ name: "id" })
+ @ManyToOne(() => User)
user: User;
+ @Column({ nullable: true })
@RelationId((member: Member) => member.guild)
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.members)
+ @ManyToOne(() => Guild)
guild: Guild;
@Column({ nullable: true })
nick?: string;
- @RelationId((member: Member) => member.roles)
- role_ids: string[];
-
- @JoinTable()
+ @JoinTable({ name: "member_roles" })
@ManyToMany(() => Role)
roles: Role[];
@@ -62,7 +57,7 @@ export class Member extends BaseClass {
read_state: Record<string, string | null>;
static async IsInGuildOrFail(user_id: string, guild_id: string) {
- if (await Member.count({ id: user_id, guild_id })) return true;
+ if (await Member.count({ id: user_id, guild: { id: guild_id } })) return true;
throw new HTTPError("You are not member of this guild", 403);
}
@@ -99,46 +94,54 @@ export class Member extends BaseClass {
static async addRole(user_id: string, guild_id: string, role_id: string) {
const [member] = await Promise.all([
+ // @ts-ignore
Member.findOneOrFail({
where: { id: user_id, guild_id: guild_id },
- relations: ["user"], // we don't want to load the role objects just the ids
+ relations: ["user", "roles"], // we don't want to load the role objects just the ids
+ select: ["roles.id"],
}),
await Role.findOneOrFail({ id: role_id, guild_id: guild_id }),
]);
- member.role_ids.push(role_id);
- member.save();
+ member.roles.push(new Role({ id: role_id }));
- await emitEvent({
- event: "GUILD_MEMBER_UPDATE",
- data: {
+ await Promise.all([
+ member.save(),
+ emitEvent({
+ event: "GUILD_MEMBER_UPDATE",
+ data: {
+ guild_id: guild_id,
+ user: member.user,
+ roles: member.roles.map((x) => x.id),
+ },
guild_id: guild_id,
- user: member.user,
- roles: member.role_ids,
- },
- guild_id: guild_id,
- } as GuildMemberUpdateEvent);
+ } as GuildMemberUpdateEvent),
+ ]);
}
static async removeRole(user_id: string, guild_id: string, role_id: string) {
const [member] = await Promise.all([
+ // @ts-ignore
Member.findOneOrFail({
where: { id: user_id, guild_id: guild_id },
- relations: ["user"], // we don't want to load the role objects just the ids
+ relations: ["user", "roles"], // we don't want to load the role objects just the ids
+ select: ["roles.id"],
}),
await Role.findOneOrFail({ id: role_id, guild_id: guild_id }),
]);
- member.role_ids.remove(role_id);
- member.save();
+ member.roles = member.roles.filter((x) => x.id == role_id);
- await emitEvent({
- event: "GUILD_MEMBER_UPDATE",
- data: {
+ await Promise.all([
+ member.save(),
+ emitEvent({
+ event: "GUILD_MEMBER_UPDATE",
+ data: {
+ guild_id: guild_id,
+ user: member.user,
+ roles: member.roles.map((x) => x.id),
+ },
guild_id: guild_id,
- user: member.user,
- roles: member.role_ids,
- },
- guild_id: guild_id,
- } as GuildMemberUpdateEvent);
+ } as GuildMemberUpdateEvent),
+ ]);
}
static async changeNickname(user_id: string, guild_id: string, nickname: string) {
@@ -175,11 +178,14 @@ export class Member extends BaseClass {
throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403);
}
- const guild = await Guild.findOneOrFail(guild_id, {
+ const guild = await Guild.findOneOrFail({
+ where: {
+ id: guild_id,
+ },
relations: ["channels", "emojis", "members", "roles", "stickers"],
});
- if (await Member.count({ id: user.id, guild_id }))
+ if (await Member.count({ id: user.id, guild: { id: guild_id } }))
throw new HTTPError("You are already a member of this guild", 400);
const member = {
@@ -194,23 +200,23 @@ export class Member extends BaseClass {
pending: false,
};
// @ts-ignore
- guild.joined_at = member.joined_at;
+ guild.joined_at = member.joined_at.toISOString();
await Promise.all([
- new Member({
+ Member.insert({
...member,
+ roles: undefined,
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(),
+ }),
Guild.increment({ id: guild_id }, "member_count", 1),
emitEvent({
event: "GUILD_MEMBER_ADD",
@@ -223,7 +229,7 @@ export class Member extends BaseClass {
} as GuildMemberAddEvent),
emitEvent({
event: "GUILD_CREATE",
- data: guild,
+ data: { ...guild, members: [...guild.members, member] },
user_id,
} as GuildCreateEvent),
]);
diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts
index 43d0f9d0..542b2b55 100644
--- a/util/src/entities/Message.ts
+++ b/util/src/entities/Message.ts
@@ -9,8 +9,10 @@ import {
CreateDateColumn,
Entity,
JoinColumn,
+ JoinTable,
ManyToMany,
ManyToOne,
+ OneToMany,
RelationId,
UpdateDateColumn,
} from "typeorm";
@@ -18,6 +20,7 @@ import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { Webhook } from "./Webhook";
import { Sticker } from "./Sticker";
+import { Attachment } from "./Attachment";
export enum MessageType {
DEFAULT = 0,
@@ -44,46 +47,52 @@ export class Message extends BaseClass {
@Column()
id: string;
+ @Column({ nullable: true })
@RelationId((message: Message) => message.channel)
channel_id: string;
@JoinColumn({ name: "channel_id" })
- @ManyToOne(() => Channel, (channel: Channel) => channel.id)
+ @ManyToOne(() => Channel)
channel: Channel;
+ @Column({ nullable: true })
@RelationId((message: Message) => message.guild)
guild_id?: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild)
guild?: Guild;
+ @Column({ nullable: true })
@RelationId((message: Message) => message.author)
author_id: string;
- @JoinColumn({ name: "author_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @JoinColumn({ name: "author_id", referencedColumnName: "id" })
+ @ManyToOne(() => User)
author?: User;
+ @Column({ nullable: true })
@RelationId((message: Message) => message.member)
member_id: string;
@JoinColumn({ name: "member_id" })
- @ManyToOne(() => Member, (member: Member) => member.id)
+ @ManyToOne(() => Member)
member?: Member;
+ @Column({ nullable: true })
@RelationId((message: Message) => message.webhook)
webhook_id: string;
@JoinColumn({ name: "webhook_id" })
- @ManyToOne(() => Webhook, (webhook: Webhook) => webhook.id)
+ @ManyToOne(() => Webhook)
webhook?: Webhook;
+ @Column({ nullable: true })
@RelationId((message: Message) => message.application)
application_id: string;
@JoinColumn({ name: "application_id" })
- @ManyToOne(() => Application, (application: Application) => application.id)
+ @ManyToOne(() => Application)
application?: Application;
@Column({ nullable: true })
@@ -103,29 +112,25 @@ export class Message extends BaseClass {
@Column({ nullable: true })
mention_everyone?: boolean;
- @RelationId((message: Message) => message.mentions)
- mention_user_ids: string[];
-
- @JoinColumn({ name: "mention_user_ids" })
- @ManyToMany(() => User, (user: User) => user.id)
+ @JoinTable({ name: "message_user_mentions" })
+ @ManyToMany(() => User)
mentions: User[];
- @RelationId((message: Message) => message.mention_roles)
- mention_role_ids: string[];
-
- @JoinColumn({ name: "mention_role_ids" })
- @ManyToMany(() => Role, (role: Role) => role.id)
+ @JoinTable({ name: "message_role_mentions" })
+ @ManyToMany(() => Role)
mention_roles: Role[];
- @RelationId((message: Message) => message.mention_channels)
- mention_channel_ids: string[];
-
- @JoinColumn({ name: "mention_channel_ids" })
- @ManyToMany(() => Channel, (channel: Channel) => channel.id)
+ @JoinTable({ name: "message_channel_mentions" })
+ @ManyToMany(() => Channel)
mention_channels: Channel[];
- @Column({ type: "simple-json" })
- attachments: Attachment[];
+ @JoinTable({ name: "message_stickers" })
+ @ManyToMany(() => Sticker)
+ sticker_items?: Sticker[];
+
+ @JoinColumn({ name: "attachment_ids" })
+ @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message)
+ attachments?: Attachment[];
@Column({ type: "simple-json" })
embeds: Embed[];
@@ -150,14 +155,6 @@ export class Message extends BaseClass {
@Column({ nullable: true })
flags?: string;
-
- @RelationId((message: Message) => message.stickers)
- sticker_ids: string[];
-
- @JoinColumn({ name: "sticker_ids" })
- @ManyToMany(() => Sticker, (sticker: Sticker) => sticker.id)
- stickers?: Sticker[];
-
@Column({ type: "simple-json", nullable: true })
message_reference?: {
message_id: string;
@@ -166,7 +163,7 @@ export class Message extends BaseClass {
};
@JoinColumn({ name: "message_reference_id" })
- @ManyToOne(() => Message, (message: Message) => message.id)
+ @ManyToOne(() => Message)
referenced_message?: Message;
@Column({ type: "simple-json", nullable: true })
@@ -178,8 +175,8 @@ export class Message extends BaseClass {
// user: User; // TODO: autopopulate user
};
- @Column({ type: "simple-json" })
- components: MessageComponent[];
+ @Column({ type: "simple-json", nullable: true })
+ components?: MessageComponent[];
}
export interface MessageComponent {
@@ -198,17 +195,6 @@ export enum MessageComponentType {
Button = 2,
}
-export interface Attachment {
- id: string; // attachment id
- filename: string; // name of file attached
- size: number; // size of file in bytes
- url: string; // source url of file
- proxy_url: string; // a proxied url of file
- height?: number; // height of file (if image)
- width?: number; // width of file (if image)
- content_type?: string;
-}
-
export interface Embed {
title?: string; //title of embed
type?: EmbedType; // type of embed (always "rich" for webhook embeds)
diff --git a/util/src/entities/ReadState.ts b/util/src/entities/ReadState.ts
index 650ee4c3..8dd05b21 100644
--- a/util/src/entities/ReadState.ts
+++ b/util/src/entities/ReadState.ts
@@ -10,25 +10,28 @@ import { User } from "./User";
@Entity("read_states")
export class ReadState extends BaseClass {
+ @Column({ nullable: true })
@RelationId((read_state: ReadState) => read_state.channel)
channel_id: string;
@JoinColumn({ name: "channel_id" })
- @ManyToOne(() => Channel, (channel: Channel) => channel.id)
+ @ManyToOne(() => Channel)
channel: Channel;
+ @Column({ nullable: true })
@RelationId((read_state: ReadState) => read_state.user)
user_id: string;
@JoinColumn({ name: "user_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
user: User;
+ @Column({ nullable: true })
@RelationId((read_state: ReadState) => read_state.last_message)
last_message_id: string;
@JoinColumn({ name: "last_message_id" })
- @ManyToOne(() => Message, (message: Message) => message.id)
+ @ManyToOne(() => Message)
last_message?: Message;
@Column({ nullable: true })
diff --git a/util/src/entities/Recipient.ts b/util/src/entities/Recipient.ts
new file mode 100644
index 00000000..75d5b94d
--- /dev/null
+++ b/util/src/entities/Recipient.ts
@@ -0,0 +1,19 @@
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import { BaseClass } from "./BaseClass";
+
+@Entity("recipients")
+export class Recipient extends BaseClass {
+ @Column()
+ @RelationId((recipient: Recipient) => recipient.channel)
+ channel_id: string;
+
+ @JoinColumn({ name: "channel_id" })
+ @ManyToOne(() => require("./Channel").Channel)
+ channel: import("./Channel").Channel;
+
+ @JoinColumn({ name: "id" })
+ @ManyToOne(() => require("./User").User)
+ user: import("./User").User;
+
+ // TODO: settings/mute/nick/added at/encryption keys/read_state
+}
diff --git a/util/src/entities/Relationship.ts b/util/src/entities/Relationship.ts
index cf3624bf..5935f5b6 100644
--- a/util/src/entities/Relationship.ts
+++ b/util/src/entities/Relationship.ts
@@ -11,11 +11,12 @@ export enum RelationshipType {
@Entity("relationships")
export class Relationship extends BaseClass {
+ @Column({ nullable: true })
@RelationId((relationship: Relationship) => relationship.user)
user_id: string;
@JoinColumn({ name: "user_id" })
- @ManyToOne(() => User, (user: User) => user.relationships)
+ @ManyToOne(() => User)
user: User;
@Column({ nullable: true })
diff --git a/util/src/entities/Role.ts b/util/src/entities/Role.ts
index be8c731d..33c8d272 100644
--- a/util/src/entities/Role.ts
+++ b/util/src/entities/Role.ts
@@ -5,11 +5,12 @@ import { Guild } from "./Guild";
@Entity("roles")
export class Role extends BaseClass {
+ @Column({ nullable: true })
@RelationId((role: Role) => role.guild)
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild)
guild: Guild;
@Column()
diff --git a/util/src/entities/Sticker.ts b/util/src/entities/Sticker.ts
index 12394207..7730a86a 100644
--- a/util/src/entities/Sticker.ts
+++ b/util/src/entities/Sticker.ts
@@ -31,7 +31,7 @@ export class Sticker extends BaseClass {
guild_id?: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild)
guild?: Guild;
@Column({ type: "simple-enum", enum: StickerType })
diff --git a/util/src/entities/Team.ts b/util/src/entities/Team.ts
index b37f368c..beb8bf68 100644
--- a/util/src/entities/Team.ts
+++ b/util/src/entities/Team.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { TeamMember } from "./TeamMember";
import { User } from "./User";
@@ -8,20 +8,18 @@ export class Team extends BaseClass {
@Column({ nullable: true })
icon?: string;
- @RelationId((team: Team) => team.members)
- member_ids: string[];
-
@JoinColumn({ name: "member_ids" })
- @ManyToMany(() => TeamMember, (member: TeamMember) => member.id)
+ @OneToMany(() => TeamMember, (member: TeamMember) => member.team)
members: TeamMember[];
@Column()
name: string;
+ @Column({ nullable: true })
@RelationId((team: Team) => team.owner_user)
owner_user_id: string;
@JoinColumn({ name: "owner_user_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
owner_user: User;
}
diff --git a/util/src/entities/TeamMember.ts b/util/src/entities/TeamMember.ts
index d4c95397..6b184d08 100644
--- a/util/src/entities/TeamMember.ts
+++ b/util/src/entities/TeamMember.ts
@@ -15,17 +15,19 @@ export class TeamMember extends BaseClass {
@Column({ type: "simple-array" })
permissions: string[];
+ @Column({ nullable: true })
@RelationId((member: TeamMember) => member.team)
team_id: string;
@JoinColumn({ name: "team_id" })
- @ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.id)
+ @ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members)
team: import("./Team").Team;
+ @Column({ nullable: true })
@RelationId((member: TeamMember) => member.user)
user_id: string;
@JoinColumn({ name: "user_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
user: User;
}
diff --git a/util/src/entities/Template.ts b/util/src/entities/Template.ts
index 2d8c9b6c..76f77ba6 100644
--- a/util/src/entities/Template.ts
+++ b/util/src/entities/Template.ts
@@ -17,11 +17,12 @@ export class Template extends BaseClass {
@Column({ nullable: true })
usage_count?: number;
+ @Column({ nullable: true })
@RelationId((template: Template) => template.creator)
creator_id: string;
@JoinColumn({ name: "creator_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
creator: User;
@Column()
@@ -30,11 +31,12 @@ export class Template extends BaseClass {
@Column()
updated_at: Date;
+ @Column({ nullable: true })
@RelationId((template: Template) => template.source_guild)
source_guild_id: string;
@JoinColumn({ name: "source_guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild)
source_guild: Guild;
@Column({ type: "simple-json" })
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index 73afba67..39f654be 100644
--- a/util/src/entities/User.ts
+++ b/util/src/entities/User.ts
@@ -1,9 +1,10 @@
-import { Column, Entity, FindOneOptions, JoinColumn, OneToMany, RelationId } from "typeorm";
+import { Column, Entity, FindOneOptions, JoinColumn, ManyToMany, OneToMany, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { BitField } from "../util/BitField";
import { Relationship } from "./Relationship";
import { ConnectedAccount } from "./ConnectedAccount";
import { HTTPError } from "lambert-server";
+import { Channel } from "./Channel";
type PublicUserKeys =
| "username"
@@ -105,16 +106,10 @@ export class User extends BaseClass {
@Column()
public_flags: string;
- @RelationId((user: User) => user.relationships)
- relationship_ids: string[]; // array of guild ids the user is part of
-
@JoinColumn({ name: "relationship_ids" })
@OneToMany(() => Relationship, (relationship: Relationship) => relationship.user, { cascade: true })
relationships: Relationship[];
- @RelationId((user: User) => user.connected_accounts)
- connected_account_ids: string[]; // array of guild ids the user is part of
-
@JoinColumn({ name: "connected_account_ids" })
@OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user)
connected_accounts: ConnectedAccount[];
@@ -126,16 +121,19 @@ export class User extends BaseClass {
};
@Column({ type: "simple-array" })
- fingerprints: string[] = []; // array of fingerprints -> used to prevent multiple accounts
+ fingerprints: string[]; // array of fingerprints -> used to prevent multiple accounts
@Column({ type: "simple-json" })
settings: UserSettings;
static async getPublicUser(user_id: string, opts?: FindOneOptions<User>) {
- const user = await User.findOne(user_id, {
- ...opts,
- select: [...PublicUserProjection, ...(opts?.select || [])],
- });
+ const user = await User.findOne(
+ { id: user_id },
+ {
+ ...opts,
+ select: [...PublicUserProjection, ...(opts?.select || [])],
+ }
+ );
if (!user) throw new HTTPError("User not found", 404);
return user;
}
diff --git a/util/src/entities/VoiceState.ts b/util/src/entities/VoiceState.ts
index e9d3dfa2..c5040cf1 100644
--- a/util/src/entities/VoiceState.ts
+++ b/util/src/entities/VoiceState.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, JoinColumn, ManyToOne, OneToOne, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { Channel } from "./Channel";
import { Guild } from "./Guild";
@@ -6,25 +6,28 @@ import { User } from "./User";
@Entity("voice_states")
export class VoiceState extends BaseClass {
+ @Column({ nullable: true })
@RelationId((voice_state: VoiceState) => voice_state.guild)
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild)
guild?: Guild;
+ @Column({ nullable: true })
@RelationId((voice_state: VoiceState) => voice_state.channel)
channel_id: string;
@JoinColumn({ name: "channel_id" })
- @ManyToOne(() => Channel, (channel: Channel) => channel.id)
+ @ManyToOne(() => Channel)
channel: Channel;
+ @Column({ nullable: true })
@RelationId((voice_state: VoiceState) => voice_state.user)
user_id: string;
@JoinColumn({ name: "user_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
user: User;
@Column()
diff --git a/util/src/entities/Webhook.ts b/util/src/entities/Webhook.ts
index a75cb959..12ba0d08 100644
--- a/util/src/entities/Webhook.ts
+++ b/util/src/entities/Webhook.ts
@@ -27,38 +27,43 @@ export class Webhook extends BaseClass {
@Column({ nullable: true })
token?: string;
+ @Column({ nullable: true })
@RelationId((webhook: Webhook) => webhook.guild)
guild_id: string;
@JoinColumn({ name: "guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild)
guild: Guild;
+ @Column({ nullable: true })
@RelationId((webhook: Webhook) => webhook.channel)
channel_id: string;
@JoinColumn({ name: "channel_id" })
- @ManyToOne(() => Channel, (channel: Channel) => channel.id)
+ @ManyToOne(() => Channel)
channel: Channel;
+ @Column({ nullable: true })
@RelationId((webhook: Webhook) => webhook.application)
application_id: string;
@JoinColumn({ name: "application_id" })
- @ManyToOne(() => Application, (application: Application) => application.id)
+ @ManyToOne(() => Application)
application: Application;
+ @Column({ nullable: true })
@RelationId((webhook: Webhook) => webhook.user)
user_id: string;
@JoinColumn({ name: "user_id" })
- @ManyToOne(() => User, (user: User) => user.id)
+ @ManyToOne(() => User)
user: User;
+ @Column({ nullable: true })
@RelationId((webhook: Webhook) => webhook.guild)
source_guild_id: string;
@JoinColumn({ name: "source_guild_id" })
- @ManyToOne(() => Guild, (guild: Guild) => guild.id)
+ @ManyToOne(() => Guild)
source_guild: Guild;
}
diff --git a/util/src/entities/index.ts b/util/src/entities/index.ts
index e0246a10..aa37ae2e 100644
--- a/util/src/entities/index.ts
+++ b/util/src/entities/index.ts
@@ -1,4 +1,5 @@
export * from "./Application";
+export * from "./Attachment";
export * from "./AuditLog";
export * from "./Ban";
export * from "./BaseClass";
@@ -12,6 +13,7 @@ export * from "./Member";
export * from "./Message";
export * from "./RateLimit";
export * from "./ReadState";
+export * from "./Recipient";
export * from "./Relationship";
export * from "./Role";
export * from "./Sticker";
diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts
index 814a8beb..7ea1bd49 100644
--- a/util/src/interfaces/Event.ts
+++ b/util/src/interfaces/Event.ts
@@ -89,14 +89,7 @@ export interface ReadyEventData {
};
merged_members?: Omit<Member, "settings" | "user">[][];
// probably all users who the user is in contact with
- users?: {
- avatar: string | null;
- discriminator: string;
- id: string;
- username: string;
- bot: boolean;
- public_flags: string;
- }[];
+ users?: PublicUser[];
}
export interface ReadyEvent extends Event {
@@ -130,7 +123,9 @@ export interface ChannelPinsUpdateEvent extends Event {
export interface GuildCreateEvent extends Event {
event: "GUILD_CREATE";
- data: Guild;
+ data: Guild & {
+ joined_at: Date;
+ };
}
export interface GuildUpdateEvent extends Event {
diff --git a/util/src/tes.ts b/util/src/tes.ts
index b469f1d8..e326dee1 100644
--- a/util/src/tes.ts
+++ b/util/src/tes.ts
@@ -5,10 +5,14 @@ import { initDatabase } from "./util";
initDatabase().then(async (x) => {
try {
- const user = await new User(
- { guilds: [], discriminator: "1", username: "test", flags: "0", public_flags: "0" },
- { id: "0" }
- ).save();
+ const user = await new User({
+ guilds: [],
+ discriminator: "1",
+ username: "test",
+ flags: "0",
+ public_flags: "0",
+ id: "0",
+ }).save();
user.relationships = [new Relationship({ type: RelationshipType.friends })];
diff --git a/util/src/util/Config.ts b/util/src/util/Config.ts
index 508a8901..1ec71ad0 100644
--- a/util/src/util/Config.ts
+++ b/util/src/util/Config.ts
@@ -7,7 +7,7 @@ var config: ConfigEntity;
export const Config = {
init: async function init() {
if (config) return config;
- config = (await ConfigEntity.findOne({ id: "0" })) || new ConfigEntity({}, { id: "0" });
+ config = (await ConfigEntity.findOne({ id: "0" })) || new ConfigEntity({ id: "0" });
return this.set((config.value || {}).merge(DefaultConfigOptions));
},
diff --git a/util/src/util/Permissions.ts b/util/src/util/Permissions.ts
index ab6aa795..89b316ee 100644
--- a/util/src/util/Permissions.ts
+++ b/util/src/util/Permissions.ts
@@ -220,13 +220,14 @@ export async function getPermission(user_id?: string, guild_id?: string, channel
guild = await Guild.findOneOrFail({ id: guild_id });
if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR);
- member = await Member.findOneOrFail({ where: { guild_id, id: user_id }, relations: ["roles"] });
+ member = await Member.findOneOrFail({ where: { guild: guild_id, id: user_id }, relations: ["roles"] });
}
+ // TODO: remove guild.roles and convert recipient_ids to recipients
var permission = Permissions.finalPermission({
user: {
id: user_id,
- roles: member?.role_ids || [],
+ roles: member?.roles.map((x) => x.id) || [],
},
guild: {
roles: member?.roles || [],
@@ -234,7 +235,7 @@ export async function getPermission(user_id?: string, guild_id?: string, channel
channel: {
overwrites: channel?.permission_overwrites,
owner_id: channel?.owner_id,
- recipient_ids: channel?.recipient_ids,
+ recipient_ids: channel?.recipients?.map((x) => x.id),
},
});
|