diff --git a/util/src/dtos/DmChannelDTO.ts b/src/util/dtos/DmChannelDTO.ts
index 226b2f9d..226b2f9d 100644
--- a/util/src/dtos/DmChannelDTO.ts
+++ b/src/util/dtos/DmChannelDTO.ts
diff --git a/util/src/dtos/UserDTO.ts b/src/util/dtos/UserDTO.ts
index ee2752a4..ee2752a4 100644
--- a/util/src/dtos/UserDTO.ts
+++ b/src/util/dtos/UserDTO.ts
diff --git a/util/src/dtos/index.ts b/src/util/dtos/index.ts
index 0e8f8459..0e8f8459 100644
--- a/util/src/dtos/index.ts
+++ b/src/util/dtos/index.ts
diff --git a/util/src/entities/Application.ts b/src/util/entities/Application.ts
index fab3d93f..fab3d93f 100644
--- a/util/src/entities/Application.ts
+++ b/src/util/entities/Application.ts
diff --git a/util/src/entities/Attachment.ts b/src/util/entities/Attachment.ts
index 7b4b17eb..7b4b17eb 100644
--- a/util/src/entities/Attachment.ts
+++ b/src/util/entities/Attachment.ts
diff --git a/util/src/entities/AuditLog.ts b/src/util/entities/AuditLog.ts
index b003e7ba..b003e7ba 100644
--- a/util/src/entities/AuditLog.ts
+++ b/src/util/entities/AuditLog.ts
diff --git a/util/src/entities/BackupCodes.ts b/src/util/entities/BackupCodes.ts
index d532a39a..d532a39a 100644
--- a/util/src/entities/BackupCodes.ts
+++ b/src/util/entities/BackupCodes.ts
diff --git a/util/src/entities/Ban.ts b/src/util/entities/Ban.ts
index 9504bd8e..9504bd8e 100644
--- a/util/src/entities/Ban.ts
+++ b/src/util/entities/Ban.ts
diff --git a/src/util/entities/BaseClass.ts b/src/util/entities/BaseClass.ts
new file mode 100644
index 00000000..d5a7c2bf
--- /dev/null
+++ b/src/util/entities/BaseClass.ts
@@ -0,0 +1,52 @@
+import "reflect-metadata";
+import { BaseEntity, BeforeInsert, BeforeUpdate, FindOptionsWhere, ObjectIdColumn, PrimaryColumn } from "typeorm";
+import { Snowflake } from "../util/Snowflake";
+import "missing-native-js-functions";
+import { getDatabase } from "..";
+import { OrmUtils } from "@fosscord/util";
+
+export class BaseClassWithoutId extends BaseEntity {
+ private get construct(): any {
+ return this.constructor;
+ }
+
+ private get metadata() {
+ return getDatabase()?.getMetadata(this.construct);
+ }
+
+ assign(props: any) {
+ OrmUtils.mergeDeep(this, props);
+ return this;
+ }
+
+ toJSON(): any {
+ 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: FindOptionsWhere<T>, propertyPath: string, value: number | string) {
+ const repository = this.getRepository();
+ return repository.increment(conditions, propertyPath, value);
+ }
+
+ static decrement<T extends BaseClass>(conditions: FindOptionsWhere<T>, propertyPath: string, value: number | string) {
+ const repository = this.getRepository();
+ return repository.decrement(conditions, propertyPath, value);
+ }
+}
+
+export const PrimaryIdColumn = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryColumn;
+
+export class BaseClass extends BaseClassWithoutId {
+ @PrimaryIdColumn()
+ id: string;
+
+ @BeforeUpdate()
+ @BeforeInsert()
+ do_validate() {
+ if (!this.id) this.id = Snowflake.generate();
+ }
+}
diff --git a/util/src/entities/Categories.ts b/src/util/entities/Categories.ts
index 81fbc303..81fbc303 100644
--- a/util/src/entities/Categories.ts
+++ b/src/util/entities/Categories.ts
diff --git a/util/src/entities/Channel.ts b/src/util/entities/Channel.ts
index 10fa03ff..577b627e 100644
--- a/util/src/entities/Channel.ts
+++ b/src/util/entities/Channel.ts
@@ -58,7 +58,7 @@ export class Channel extends BaseClass {
recipients?: Recipient[];
@Column({ nullable: true })
- last_message_id: string;
+ last_message_id?: string;
@Column({ nullable: true })
@RelationId((channel: Channel) => channel.guild)
@@ -81,7 +81,7 @@ export class Channel extends BaseClass {
// for group DMs and owned custom channel types
@Column({ nullable: true })
@RelationId((channel: Channel) => channel.owner)
- owner_id: string;
+ owner_id?: string;
@JoinColumn({ name: "owner_id" })
@ManyToOne(() => User)
@@ -169,7 +169,7 @@ export class Channel extends BaseClass {
}
if (!opts?.skipNameChecks) {
- const guild = await Guild.findOneOrFail({ id: channel.guild_id });
+ const guild = await Guild.findOneOrFail({ where: { id: channel.guild_id } });
if (!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && channel.name) {
for (var character of InvisibleCharacters)
if (channel.name.includes(character))
@@ -202,7 +202,7 @@ export class Channel extends BaseClass {
case ChannelType.GUILD_NEWS:
case ChannelType.GUILD_VOICE:
if (channel.parent_id && !opts?.skipExistsCheck) {
- const exists = await Channel.findOneOrFail({ id: channel.parent_id });
+ const exists = await Channel.findOneOrFail({ where: { 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");
@@ -230,7 +230,7 @@ export class Channel extends BaseClass {
};
await Promise.all([
- new Channel(channel).save(),
+ Channel.create(channel).save(),
!opts?.skipEventEmit
? emitEvent({
event: "CHANNEL_CREATE",
@@ -281,15 +281,15 @@ export class Channel extends BaseClass {
if (channel == null) {
name = trimSpecial(name);
- channel = await new Channel({
+ channel = await Channel.create({
name,
type,
- owner_id: type === ChannelType.DM ? undefined : null, // 1:1 DMs are ownerless in fosscord-server
+ owner_id: undefined,
created_at: new Date(),
- last_message_id: null,
+ last_message_id: undefined,
recipients: channelRecipients.map(
(x) =>
- new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })
+ Recipient.create({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) })
),
nsfw: false,
}).save();
diff --git a/util/src/entities/ClientRelease.ts b/src/util/entities/ClientRelease.ts
index c5afd307..c5afd307 100644
--- a/util/src/entities/ClientRelease.ts
+++ b/src/util/entities/ClientRelease.ts
diff --git a/util/src/entities/Config.ts b/src/util/entities/Config.ts
index 9aabc1a8..9aabc1a8 100644
--- a/util/src/entities/Config.ts
+++ b/src/util/entities/Config.ts
diff --git a/util/src/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts
index 09ae30ab..09ae30ab 100644
--- a/util/src/entities/ConnectedAccount.ts
+++ b/src/util/entities/ConnectedAccount.ts
diff --git a/util/src/entities/Emoji.ts b/src/util/entities/Emoji.ts
index a3615b7d..a3615b7d 100644
--- a/util/src/entities/Emoji.ts
+++ b/src/util/entities/Emoji.ts
diff --git a/util/src/entities/Encryption.ts b/src/util/entities/Encryption.ts
index 3b82ff84..b597b90a 100644
--- a/util/src/entities/Encryption.ts
+++ b/src/util/entities/Encryption.ts
@@ -14,22 +14,22 @@ import { DmChannelDTO } from "../dtos";
@Entity("security_settings")
export class SecuritySettings extends BaseClass {
- @Column({nullable: true})
- guild_id: Snowflake;
+ @Column({ nullable: true })
+ guild_id: string;
- @Column({nullable: true})
- channel_id: Snowflake;
+ @Column({ nullable: true })
+ channel_id: string;
- @Column()
- encryption_permission_mask: BitField;
+ @Column()
+ encryption_permission_mask: number;
- @Column()
- allowed_algorithms: string[];
+ @Column({ type: "simple-array" })
+ allowed_algorithms: string[];
- @Column()
- current_algorithm: string;
+ @Column()
+ current_algorithm: string;
- @Column({nullable: true})
- used_since_message: Snowflake;
+ @Column({ nullable: true })
+ used_since_message: string;
}
diff --git a/util/src/entities/Guild.ts b/src/util/entities/Guild.ts
index 143cb542..2ce7c213 100644
--- a/util/src/entities/Guild.ts
+++ b/src/util/entities/Guild.ts
@@ -86,7 +86,7 @@ export class Guild extends BaseClass {
//TODO: https://discord.com/developers/docs/resources/guild#guild-object-guild-features
@Column({ nullable: true })
- primary_category_id: number;
+ primary_category_id?: string; // TODO: this was number?
@Column({ nullable: true })
icon?: string;
@@ -285,7 +285,7 @@ export class Guild extends BaseClass {
}) {
const guild_id = Snowflake.generate();
- const guild = await new Guild({
+ const guild = await Guild.create({
name: body.name || "Fosscord",
icon: await handleFile(`/icons/${guild_id}`, body.icon as string),
region: Config.get().regions.default,
@@ -294,7 +294,7 @@ export class Guild extends BaseClass {
default_message_notifications: 1, // defaults effect: setting the push default at mentions-only will save a lot
explicit_content_filter: 0,
features: Config.get().guild.defaultFeatures,
- primary_category_id: null,
+ primary_category_id: undefined,
id: guild_id,
max_members: 250000,
max_presences: 250000,
@@ -320,7 +320,7 @@ export class Guild extends BaseClass {
// we have to create the role _after_ the guild because else we would get a "SQLITE_CONSTRAINT: FOREIGN KEY constraint failed" error
// TODO: make the @everyone a pseudorole that is dynamically generated at runtime so we can save storage
- await new Role({
+ await Role.create({
id: guild_id,
guild_id: guild_id,
color: 0,
@@ -331,8 +331,8 @@ export class Guild extends BaseClass {
name: "@everyone",
permissions: String("2251804225"),
position: 0,
- icon: null,
- unicode_emoji: null
+ icon: undefined,
+ unicode_emoji: undefined
}).save();
if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general", nsfw: false }];
diff --git a/util/src/entities/Invite.ts b/src/util/entities/Invite.ts
index 6ac64ddc..4f36f247 100644
--- a/util/src/entities/Invite.ts
+++ b/src/util/entities/Invite.ts
@@ -52,7 +52,7 @@ export class Invite extends BaseClassWithoutId {
@Column({ nullable: true })
@RelationId((invite: Invite) => invite.inviter)
- inviter_id: string;
+ inviter_id?: string;
@JoinColumn({ name: "inviter_id" })
@ManyToOne(() => User)
@@ -75,7 +75,7 @@ export class Invite extends BaseClassWithoutId {
vanity_url?: boolean;
static async joinGuild(user_id: string, code: string) {
- const invite = await Invite.findOneOrFail({ code });
+ const invite = await Invite.findOneOrFail({ where: { code } });
if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) await Invite.delete({ code });
else await invite.save();
diff --git a/util/src/entities/Member.ts b/src/util/entities/Member.ts
index 03698453..d7bcefea 100644
--- a/util/src/entities/Member.ts
+++ b/src/util/entities/Member.ts
@@ -1,6 +1,8 @@
import { PublicUser, User } from "./User";
import { Message } from "./Message";
import {
+ BeforeInsert,
+ BeforeUpdate,
Column,
Entity,
Index,
@@ -73,17 +75,6 @@ export class Member extends BaseClassWithoutId {
@Column({ nullable: true })
nick?: string;
- setNick(val: string) {
-
- if (val) {
- val = val.split("\n").join("");
- val = val.split("\t").join("");
- if (BannedWords.find(val)) throw FieldErrors({ nick: { message: "Bad nickname", code: "INVALID_NICKNAME" } });
- }
-
- this.nick = val;
- }
-
@JoinTable({
name: "member_roles",
joinColumn: { name: "index", referencedColumnName: "index" },
@@ -129,8 +120,18 @@ export class Member extends BaseClassWithoutId {
// @Column({ type: "simple-json" })
// read_state: ReadState;
+ @BeforeUpdate()
+ @BeforeInsert()
+ validate() {
+ if (this.nick) {
+ this.nick = this.nick.split("\n").join("");
+ this.nick = this.nick.split("\t").join("");
+ if (BannedWords.find(this.nick)) throw FieldErrors({ nick: { message: "Bad nickname", code: "INVALID_NICKNAME" } });
+ }
+ }
+
static async IsInGuildOrFail(user_id: string, guild_id: string) {
- if (await Member.count({ id: user_id, guild: { id: guild_id } })) return true;
+ if (await Member.count({ where: { id: user_id, guild: { id: guild_id } } })) return true;
throw new HTTPError("You are not member of this guild", 403);
}
@@ -168,11 +169,12 @@ export class Member extends BaseClassWithoutId {
Member.findOneOrFail({
where: { id: user_id, guild_id },
relations: ["user", "roles"], // we don't want to load the role objects just the ids
- select: ["index", "roles.id"],
+ //@ts-ignore
+ select: ["index", "roles.id"], // TODO fix type
}),
Role.findOneOrFail({ where: { id: role_id, guild_id }, select: ["id"] }),
]);
- member.roles.push(new Role({ id: role_id }));
+ member.roles.push(Role.create({ id: role_id }));
await Promise.all([
member.save(),
@@ -194,9 +196,10 @@ export class Member extends BaseClassWithoutId {
Member.findOneOrFail({
where: { id: user_id, guild_id },
relations: ["user", "roles"], // we don't want to load the role objects just the ids
- select: ["roles.id", "index"],
+ //@ts-ignore
+ select: ["roles.id", "index"], // TODO: fix type
}),
- await Role.findOneOrFail({ id: role_id, guild_id }),
+ await Role.findOneOrFail({ where: { id: role_id, guild_id } }),
]);
member.roles = member.roles.filter((x) => x.id == role_id);
@@ -246,7 +249,7 @@ export class Member extends BaseClassWithoutId {
throw DiscordApiErrors.USER_BANNED;
}
const { maxGuilds } = Config.get().limits.user;
- const guild_count = await Member.count({ id: user_id });
+ const guild_count = await Member.count({ where: { id: user_id } });
if (guild_count >= maxGuilds) {
throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403);
}
@@ -258,7 +261,7 @@ export class Member extends BaseClassWithoutId {
relations: [...PublicGuildRelations, "system_channel"],
});
- if (await Member.count({ id: user.id, guild: { id: guild_id } }))
+ if (await Member.count({ where: { id: user.id, guild: { id: guild_id } } }))
throw new HTTPError("You are already a member of this guild", 400);
const member = {
@@ -274,12 +277,18 @@ export class Member extends BaseClassWithoutId {
};
await Promise.all([
- new Member({
+ Member.create({
...member,
- roles: [new Role({ id: guild_id })],
+ roles: [Role.create({ id: guild_id })],
// read_state: {},
settings: {
- channel_overrides: [],
+ guild_id: null,
+ mute_config: null,
+ mute_scheduled_events: false,
+ flags: 0,
+ hide_muted_channels: false,
+ notify_highlights: 0,
+ channel_overrides: {},
message_notifications: 0,
mobile_push: true,
muted: false,
@@ -318,13 +327,12 @@ export class Member extends BaseClassWithoutId {
if (guild.system_channel_id) {
// send welcome message
- const message = new Message({
+ const message = Message.create({
type: 7,
guild_id: guild.id,
channel_id: guild.system_channel_id,
author: user,
timestamp: new Date(),
-
reactions: [],
attachments: [],
embeds: [],
@@ -385,7 +393,7 @@ export const DefaultUserGuildSettings: UserGuildSettings = {
suppress_roles: false,
version: 453, // ?
guild_id: null,
-}
+};
export interface MuteConfig {
end_time: number;
diff --git a/util/src/entities/Message.ts b/src/util/entities/Message.ts
index 6ea5c4aa..3a3dd5e4 100644
--- a/util/src/entities/Message.ts
+++ b/src/util/entities/Message.ts
@@ -5,6 +5,8 @@ import { Channel } from "./Channel";
import { InteractionType } from "../interfaces/Interaction";
import { Application } from "./Application";
import {
+ BeforeInsert,
+ BeforeUpdate,
Column,
CreateDateColumn,
Entity,
@@ -23,6 +25,7 @@ import { Sticker } from "./Sticker";
import { Attachment } from "./Attachment";
import { BannedWords } from "../util";
import { HTTPError } from "lambert-server";
+import { ValidatorConstraint } from "class-validator";
export enum MessageType {
DEFAULT = 0,
@@ -58,7 +61,7 @@ export class Message extends BaseClass {
@Column({ nullable: true })
@RelationId((message: Message) => message.channel)
@Index()
- channel_id: string;
+ channel_id?: string;
@JoinColumn({ name: "channel_id" })
@ManyToOne(() => Channel, {
@@ -79,7 +82,7 @@ export class Message extends BaseClass {
@Column({ nullable: true })
@RelationId((message: Message) => message.author)
@Index()
- author_id: string;
+ author_id?: string;
@JoinColumn({ name: "author_id", referencedColumnName: "id" })
@ManyToOne(() => User, {
@@ -89,7 +92,7 @@ export class Message extends BaseClass {
@Column({ nullable: true })
@RelationId((message: Message) => message.member)
- member_id: string;
+ member_id?: string;
@JoinColumn({ name: "member_id", referencedColumnName: "id" })
@ManyToOne(() => User, {
@@ -99,7 +102,7 @@ export class Message extends BaseClass {
@Column({ nullable: true })
@RelationId((message: Message) => message.webhook)
- webhook_id: string;
+ webhook_id?: string;
@JoinColumn({ name: "webhook_id" })
@ManyToOne(() => Webhook)
@@ -107,7 +110,7 @@ export class Message extends BaseClass {
@Column({ nullable: true })
@RelationId((message: Message) => message.application)
- application_id: string;
+ application_id?: string;
@JoinColumn({ name: "application_id" })
@ManyToOne(() => Application)
@@ -116,17 +119,12 @@ export class Message extends BaseClass {
@Column({ nullable: true, type: process.env.PRODUCTION ? "longtext" : undefined })
content?: string;
- setContent(val: string) {
- if (val && BannedWords.find(val)) throw new HTTPError("Message was blocked by automatic moderation", 200000);
- this.content = val;
- }
-
@Column()
@CreateDateColumn()
timestamp: Date;
@Column({ nullable: true })
- edited_timestamp: Date;
+ edited_timestamp?: Date;
@Column({ nullable: true })
tts?: boolean;
@@ -179,6 +177,7 @@ export class Message extends BaseClass {
@Column({ nullable: true })
flags?: string;
+
@Column({ type: "simple-json", nullable: true })
message_reference?: {
message_id: string;
@@ -201,6 +200,14 @@ export class Message extends BaseClass {
@Column({ type: "simple-json", nullable: true })
components?: MessageComponent[];
+
+ @BeforeUpdate()
+ @BeforeInsert()
+ validate() {
+ if (this.content) {
+ if (BannedWords.find(this.content)) throw new HTTPError("Message was blocked by automatic moderation", 200000);
+ }
+ }
}
export interface MessageComponent {
diff --git a/util/src/entities/Migration.ts b/src/util/entities/Migration.ts
index 3f39ae72..3f39ae72 100644
--- a/util/src/entities/Migration.ts
+++ b/src/util/entities/Migration.ts
diff --git a/util/src/entities/Note.ts b/src/util/entities/Note.ts
index 36017c5e..36017c5e 100644
--- a/util/src/entities/Note.ts
+++ b/src/util/entities/Note.ts
diff --git a/util/src/entities/RateLimit.ts b/src/util/entities/RateLimit.ts
index f5916f6b..f5916f6b 100644
--- a/util/src/entities/RateLimit.ts
+++ b/src/util/entities/RateLimit.ts
diff --git a/util/src/entities/ReadState.ts b/src/util/entities/ReadState.ts
index b915573b..b915573b 100644
--- a/util/src/entities/ReadState.ts
+++ b/src/util/entities/ReadState.ts
diff --git a/util/src/entities/Recipient.ts b/src/util/entities/Recipient.ts
index a945f938..a945f938 100644
--- a/util/src/entities/Recipient.ts
+++ b/src/util/entities/Recipient.ts
diff --git a/util/src/entities/Relationship.ts b/src/util/entities/Relationship.ts
index c3592c76..c3592c76 100644
--- a/util/src/entities/Relationship.ts
+++ b/src/util/entities/Relationship.ts
diff --git a/util/src/entities/Role.ts b/src/util/entities/Role.ts
index 4b721b5b..d87b835f 100644
--- a/util/src/entities/Role.ts
+++ b/src/util/entities/Role.ts
@@ -37,10 +37,10 @@ export class Role extends BaseClass {
position: number;
@Column({ nullable: true })
- icon: string;
+ icon?: string;
@Column({ nullable: true })
- unicode_emoji: string;
+ unicode_emoji?: string;
@Column({ type: "simple-json", nullable: true })
tags?: {
diff --git a/util/src/entities/Session.ts b/src/util/entities/Session.ts
index 969efa89..969efa89 100644
--- a/util/src/entities/Session.ts
+++ b/src/util/entities/Session.ts
diff --git a/util/src/entities/Sticker.ts b/src/util/entities/Sticker.ts
index 37bc6fbe..37bc6fbe 100644
--- a/util/src/entities/Sticker.ts
+++ b/src/util/entities/Sticker.ts
diff --git a/util/src/entities/StickerPack.ts b/src/util/entities/StickerPack.ts
index ec8c69a2..ec8c69a2 100644
--- a/util/src/entities/StickerPack.ts
+++ b/src/util/entities/StickerPack.ts
diff --git a/util/src/entities/Team.ts b/src/util/entities/Team.ts
index 22140b7f..22140b7f 100644
--- a/util/src/entities/Team.ts
+++ b/src/util/entities/Team.ts
diff --git a/util/src/entities/TeamMember.ts b/src/util/entities/TeamMember.ts
index b726e1e8..b726e1e8 100644
--- a/util/src/entities/TeamMember.ts
+++ b/src/util/entities/TeamMember.ts
diff --git a/util/src/entities/Template.ts b/src/util/entities/Template.ts
index 1d952283..1d952283 100644
--- a/util/src/entities/Template.ts
+++ b/src/util/entities/Template.ts
diff --git a/util/src/entities/User.ts b/src/util/entities/User.ts
index 35aeea52..84a8a674 100644
--- a/util/src/entities/User.ts
+++ b/src/util/entities/User.ts
@@ -1,4 +1,4 @@
-import { Column, Entity, FindOneOptions, JoinColumn, OneToMany } from "typeorm";
+import { BeforeInsert, BeforeUpdate, Column, Entity, FindOneOptions, JoinColumn, OneToMany } from "typeorm";
import { BaseClass } from "./BaseClass";
import { BitField } from "../util/BitField";
import { Relationship } from "./Relationship";
@@ -59,23 +59,9 @@ export class User extends BaseClass {
@Column()
username: string; // username max length 32, min 2 (should be configurable)
- setUsername(val: string) {
- if (BannedWords.find(val)) throw FieldErrors({ username: { message: "Bad username", code: "INVALID_USERNAME" } });
- this.username = val;
- }
-
@Column()
discriminator: string; // opaque string: 4 digits on discord.com
- setDiscriminator(val: string) {
- const number = Number(val);
- if (val.length > 4) throw new Error("invalid discriminator");
- if (isNaN(number)) throw new Error("invalid discriminator");
- if (number <= 0 || number >= 10000) throw new Error("discriminator must be between 1 and 9999");
- val = Number(val).toString();
- this.discriminator = val.toString().padStart(4, "0");
- }
-
@Column({ nullable: true })
avatar?: string; // hash of the user avatar
@@ -139,13 +125,6 @@ export class User extends BaseClass {
@Column({ nullable: true, select: false })
email?: string; // email of the user
- setEmail(val?: string) {
- val = adjustEmail(val);
- if (!val) throw FieldErrors({ email: { message: "Invalid email", code: "EMAIL_INVALID" } });
- if (!val.match(/([a-z\d.-]{3,})@([a-z\d.-]+).([a-z]{2,})/g)) throw FieldErrors({ email: { message: "Invalid email", code: "EMAIL_INVALID" } });
- this.email = val;
- }
-
@Column()
flags: string; // UserFlags
@@ -194,6 +173,22 @@ export class User extends BaseClass {
@Column({ type: "simple-json", select: false })
extended_settings: string;
+ @BeforeUpdate()
+ @BeforeInsert()
+ validate() {
+ this.email = adjustEmail(this.email);
+ if (!this.email) throw FieldErrors({ email: { message: "Invalid email", code: "EMAIL_INVALID" } });
+ if (!this.email.match(/([a-z\d.-]{3,})@([a-z\d.-]+).([a-z]{2,})/g)) throw FieldErrors({ email: { message: "Invalid email", code: "EMAIL_INVALID" } });
+
+ const discrim = Number(this.discriminator);
+ if (this.discriminator.length > 4) throw FieldErrors({ email: { message: "Discriminator cannot be more than 4 digits.", code: "DISCRIMINATOR_INVALID" } });
+ if (isNaN(discrim)) throw FieldErrors({ email: { message: "Discriminator must be a number.", code: "DISCRIMINATOR_INVALID" } });
+ if (discrim <= 0 || discrim >= 10000) throw FieldErrors({ email: { message: "Discriminator must be a number.", code: "DISCRIMINATOR_INVALID" } });
+ this.discriminator = discrim.toString().padStart(4, "0");
+
+ if (BannedWords.find(this.username)) throw FieldErrors({ username: { message: "Bad username", code: "INVALID_USERNAME" } });
+ }
+
toPublicUser() {
const user: any = {};
PublicUserProjection.forEach((x) => {
@@ -203,13 +198,12 @@ export class User extends BaseClass {
}
static async getPublicUser(user_id: string, opts?: FindOneOptions<User>) {
- return await User.findOneOrFail(
- { id: user_id },
- {
- ...opts,
- select: [...PublicUserProjection, ...(opts?.select || [])],
- }
- );
+ return await User.findOneOrFail({
+ where: { id: user_id },
+ ...opts,
+ //@ts-ignore
+ select: [...PublicUserProjection, ...(opts?.select || [])], // TODO: fix
+ });
}
private static async generateDiscriminator(username: string): Promise<string | undefined> {
@@ -273,7 +267,7 @@ export class User extends BaseClass {
// if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false
const language = req.language === "en" ? "en-US" : req.language || "en-US";
- const user = new User({
+ const user = User.create({
created_at: new Date(),
username: username,
discriminator,
@@ -293,7 +287,7 @@ export class User extends BaseClass {
email: email,
rights: Config.get().security.defaultRights,
nsfw_allowed: true, // TODO: depending on age
- public_flags: "0",
+ public_flags: 0,
flags: "0", // TODO: generate
data: {
hash: password,
@@ -302,9 +296,8 @@ export class User extends BaseClass {
settings: { ...defaultSettings, locale: language },
purchased_flags: 5, // TODO: idk what the values for this are
premium_usage_flags: 2, // TODO: idk what the values for this are
- extended_settings: {},
+ extended_settings: "", // TODO: was {}
fingerprints: [],
- notes: {},
});
await user.save();
diff --git a/util/src/entities/VoiceState.ts b/src/util/entities/VoiceState.ts
index 75748a01..75748a01 100644
--- a/util/src/entities/VoiceState.ts
+++ b/src/util/entities/VoiceState.ts
diff --git a/util/src/entities/Webhook.ts b/src/util/entities/Webhook.ts
index 89538417..89538417 100644
--- a/util/src/entities/Webhook.ts
+++ b/src/util/entities/Webhook.ts
diff --git a/util/src/entities/index.ts b/src/util/entities/index.ts
index 49793810..49793810 100644
--- a/util/src/entities/index.ts
+++ b/src/util/entities/index.ts
diff --git a/src/util/imports/OrmUtils.ts b/src/util/imports/OrmUtils.ts
new file mode 100644
index 00000000..68a1932c
--- /dev/null
+++ b/src/util/imports/OrmUtils.ts
@@ -0,0 +1,96 @@
+//source: https://github.com/typeorm/typeorm/blob/master/src/util/OrmUtils.ts
+export class OrmUtils {
+ // Checks if it's an object made by Object.create(null), {} or new Object()
+ private static isPlainObject(item: any) {
+ if (item === null || item === undefined) {
+ return false;
+ }
+
+ return !item.constructor || item.constructor === Object;
+ }
+
+ private static mergeArrayKey(target: any, key: number, value: any, memo: Map<any, any>) {
+ // Have we seen this before? Prevent infinite recursion.
+ if (memo.has(value)) {
+ target[key] = memo.get(value);
+ return;
+ }
+
+ if (value instanceof Promise) {
+ // Skip promises entirely.
+ // This is a hold-over from the old code & is because we don't want to pull in
+ // the lazy fields. Ideally we'd remove these promises via another function first
+ // but for now we have to do it here.
+ return;
+ }
+
+ if (!this.isPlainObject(value) && !Array.isArray(value)) {
+ target[key] = value;
+ return;
+ }
+
+ if (!target[key]) {
+ target[key] = Array.isArray(value) ? [] : {};
+ }
+
+ memo.set(value, target[key]);
+ this.merge(target[key], value, memo);
+ memo.delete(value);
+ }
+
+ private static mergeObjectKey(target: any, key: string, value: any, memo: Map<any, any>) {
+ // Have we seen this before? Prevent infinite recursion.
+ if (memo.has(value)) {
+ Object.assign(target, { [key]: memo.get(value) });
+ return;
+ }
+
+ if (value instanceof Promise) {
+ // Skip promises entirely.
+ // This is a hold-over from the old code & is because we don't want to pull in
+ // the lazy fields. Ideally we'd remove these promises via another function first
+ // but for now we have to do it here.
+ return;
+ }
+
+ if (!this.isPlainObject(value) && !Array.isArray(value)) {
+ Object.assign(target, { [key]: value });
+ return;
+ }
+
+ if (!target[key]) {
+ Object.assign(target, { [key]: value });
+ }
+
+ memo.set(value, target[key]);
+ this.merge(target[key], value, memo);
+ memo.delete(value);
+ }
+
+ private static merge(target: any, source: any, memo: Map<any, any> = new Map()): any {
+ if (Array.isArray(target) && Array.isArray(source)) {
+ for (let key = 0; key < source.length; key++) {
+ this.mergeArrayKey(target, key, source[key], memo);
+ }
+ } else {
+ for (const key of Object.keys(source)) {
+ this.mergeObjectKey(target, key, source[key], memo);
+ }
+ }
+ }
+
+ /**
+ * Deep Object.assign.
+ */
+ static mergeDeep(target: any, ...sources: any[]): any {
+ if (!sources.length) {
+ return target;
+ }
+
+ for (const source of sources) {
+ OrmUtils.merge(target, source);
+ }
+
+ return target;
+ }
+}
\ No newline at end of file
diff --git a/src/util/imports/index.ts b/src/util/imports/index.ts
new file mode 100644
index 00000000..5d9bfb5f
--- /dev/null
+++ b/src/util/imports/index.ts
@@ -0,0 +1 @@
+export * from "./OrmUtils";
\ No newline at end of file
diff --git a/util/src/index.ts b/src/util/index.ts
index 52117302..385070a3 100644
--- a/util/src/index.ts
+++ b/src/util/index.ts
@@ -4,4 +4,5 @@ export * from "./util/index";
export * from "./interfaces/index";
export * from "./entities/index";
export * from "./dtos/index";
-export * from "./schemas";
\ No newline at end of file
+export * from "./schemas";
+export * from "./imports";
\ No newline at end of file
diff --git a/util/src/interfaces/Activity.ts b/src/util/interfaces/Activity.ts
index 9912e197..9912e197 100644
--- a/util/src/interfaces/Activity.ts
+++ b/src/util/interfaces/Activity.ts
diff --git a/util/src/interfaces/Event.ts b/src/util/interfaces/Event.ts
index 59f995db..59f995db 100644
--- a/util/src/interfaces/Event.ts
+++ b/src/util/interfaces/Event.ts
diff --git a/util/src/interfaces/Interaction.ts b/src/util/interfaces/Interaction.ts
index 5d3aae24..5d3aae24 100644
--- a/util/src/interfaces/Interaction.ts
+++ b/src/util/interfaces/Interaction.ts
diff --git a/util/src/interfaces/Presence.ts b/src/util/interfaces/Presence.ts
index 7663891a..7663891a 100644
--- a/util/src/interfaces/Presence.ts
+++ b/src/util/interfaces/Presence.ts
diff --git a/util/src/interfaces/Status.ts b/src/util/interfaces/Status.ts
index 5d2e1bba..5d2e1bba 100644
--- a/util/src/interfaces/Status.ts
+++ b/src/util/interfaces/Status.ts
diff --git a/util/src/interfaces/index.ts b/src/util/interfaces/index.ts
index ab7fa429..ab7fa429 100644
--- a/util/src/interfaces/index.ts
+++ b/src/util/interfaces/index.ts
diff --git a/util/src/migrations/1633864260873-EmojiRoles.ts b/src/util/migrations/1633864260873-EmojiRoles.ts
index f0d709f2..f0d709f2 100644
--- a/util/src/migrations/1633864260873-EmojiRoles.ts
+++ b/src/util/migrations/1633864260873-EmojiRoles.ts
diff --git a/util/src/migrations/1633864669243-EmojiUser.ts b/src/util/migrations/1633864669243-EmojiUser.ts
index 982405d7..982405d7 100644
--- a/util/src/migrations/1633864669243-EmojiUser.ts
+++ b/src/util/migrations/1633864669243-EmojiUser.ts
diff --git a/util/src/migrations/1633881705509-VanityInvite.ts b/src/util/migrations/1633881705509-VanityInvite.ts
index 45485310..45485310 100644
--- a/util/src/migrations/1633881705509-VanityInvite.ts
+++ b/src/util/migrations/1633881705509-VanityInvite.ts
diff --git a/util/src/migrations/1634308884591-Stickers.ts b/src/util/migrations/1634308884591-Stickers.ts
index fbc4649f..fbc4649f 100644
--- a/util/src/migrations/1634308884591-Stickers.ts
+++ b/src/util/migrations/1634308884591-Stickers.ts
diff --git a/util/src/migrations/1634424361103-Presence.ts b/src/util/migrations/1634424361103-Presence.ts
index 729955b8..729955b8 100644
--- a/util/src/migrations/1634424361103-Presence.ts
+++ b/src/util/migrations/1634424361103-Presence.ts
diff --git a/util/src/migrations/1634426540271-MigrationTimestamp.ts b/src/util/migrations/1634426540271-MigrationTimestamp.ts
index 3208b25b..3208b25b 100644
--- a/util/src/migrations/1634426540271-MigrationTimestamp.ts
+++ b/src/util/migrations/1634426540271-MigrationTimestamp.ts
diff --git a/util/src/migrations/1648643945733-ReleaseTypo.ts b/src/util/migrations/1648643945733-ReleaseTypo.ts
index 944b9dd9..944b9dd9 100644
--- a/util/src/migrations/1648643945733-ReleaseTypo.ts
+++ b/src/util/migrations/1648643945733-ReleaseTypo.ts
diff --git a/util/src/migrations/1660678870706-opencordFixes.ts b/src/util/migrations/1660678870706-opencordFixes.ts
index 1f10c212..1f10c212 100644
--- a/util/src/migrations/1660678870706-opencordFixes.ts
+++ b/src/util/migrations/1660678870706-opencordFixes.ts
diff --git a/util/src/migrations/1660689892073-mobileFixes2.ts b/src/util/migrations/1660689892073-mobileFixes2.ts
index bd28694e..bd28694e 100644
--- a/util/src/migrations/1660689892073-mobileFixes2.ts
+++ b/src/util/migrations/1660689892073-mobileFixes2.ts
diff --git a/util/src/schemas/Validator.ts b/src/util/schemas/Validator.ts
index e5f12ac5..b71bf6a1 100644
--- a/util/src/schemas/Validator.ts
+++ b/src/util/schemas/Validator.ts
@@ -3,7 +3,7 @@ import addFormats from "ajv-formats";
import fs from "fs";
import path from "path";
-const SchemaPath = path.join(__dirname, "..", "..", "..", "api", "assets", "schemas.json");
+const SchemaPath = path.join(__dirname, "..", "..", "..", "assets", "schemas.json");
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
export const ajv = new Ajv({
diff --git a/util/src/schemas/index.ts b/src/util/schemas/index.ts
index 662152dc..662152dc 100644
--- a/util/src/schemas/index.ts
+++ b/src/util/schemas/index.ts
diff --git a/util/src/schemas/voice.ts b/src/util/schemas/voice.ts
index 61c12f92..61c12f92 100644
--- a/util/src/schemas/voice.ts
+++ b/src/util/schemas/voice.ts
diff --git a/util/src/util/ApiError.ts b/src/util/util/ApiError.ts
index f1a9b4f6..f1a9b4f6 100644
--- a/util/src/util/ApiError.ts
+++ b/src/util/util/ApiError.ts
diff --git a/util/src/util/Array.ts b/src/util/util/Array.ts
index 5a45d1b5..5a45d1b5 100644
--- a/util/src/util/Array.ts
+++ b/src/util/util/Array.ts
diff --git a/util/src/util/AutoUpdate.ts b/src/util/util/AutoUpdate.ts
index 531bd8b7..fd65ecf5 100644
--- a/util/src/util/AutoUpdate.ts
+++ b/src/util/util/AutoUpdate.ts
@@ -76,7 +76,7 @@ async function getLatestVersion(url: string) {
try {
const agent = new ProxyAgent();
const response = await fetch(url, { agent });
- const content = await response.json();
+ const content = await response.json() as any; // TODO: types
return content.version;
} catch (error) {
throw new Error("[Auto update] check failed for " + url);
diff --git a/util/src/util/BannedWords.ts b/src/util/util/BannedWords.ts
index 891a5980..891a5980 100644
--- a/util/src/util/BannedWords.ts
+++ b/src/util/util/BannedWords.ts
diff --git a/util/src/util/BitField.ts b/src/util/util/BitField.ts
index fb887e05..fb887e05 100644
--- a/util/src/util/BitField.ts
+++ b/src/util/util/BitField.ts
diff --git a/util/src/util/Categories.ts b/src/util/util/Categories.ts
index a3c69da7..a3c69da7 100644
--- a/util/src/util/Categories.ts
+++ b/src/util/util/Categories.ts
diff --git a/util/src/util/Config.ts b/src/util/util/Config.ts
index 31b8d97c..31b8d97c 100644
--- a/util/src/util/Config.ts
+++ b/src/util/util/Config.ts
diff --git a/util/src/util/Constants.ts b/src/util/util/Constants.ts
index 81a7165d..81a7165d 100644
--- a/util/src/util/Constants.ts
+++ b/src/util/util/Constants.ts
diff --git a/src/util/util/Database.ts b/src/util/util/Database.ts
new file mode 100644
index 00000000..ddbea57d
--- /dev/null
+++ b/src/util/util/Database.ts
@@ -0,0 +1,96 @@
+import path from "path";
+import "reflect-metadata";
+import { DataSource } from "typeorm";
+import * as Models from "../entities";
+import { Migration } from "../entities/Migration";
+import { yellow, green, red } from "picocolors";
+
+// UUID extension option is only supported with postgres
+// We want to generate all id's with Snowflakes that's why we have our own BaseEntity class
+
+var dbConnection: DataSource | undefined;
+let dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db");
+
+export function getDatabase(): DataSource | null {
+ // if (!dbConnection) throw new Error("Tried to get database before it was initialised");
+ if (!dbConnection) return null;
+ return dbConnection;
+}
+
+export async function initDatabase(): Promise<DataSource> {
+ if (dbConnection) return dbConnection;
+
+ const type = dbConnectionString.includes("://") ? dbConnectionString.split(":")[0]?.replace("+srv", "") : "sqlite";
+ const isSqlite = type.includes("sqlite");
+
+ console.log(`[Database] ${yellow(`connecting to ${type} db`)}`);
+ if (isSqlite) {
+ console.log(`[Database] ${red(`You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`)}`);
+ }
+
+ const dataSource = new DataSource({
+ //@ts-ignore
+ type,
+ charset: 'utf8mb4',
+ url: isSqlite ? undefined : dbConnectionString,
+ database: isSqlite ? dbConnectionString : undefined,
+ entities: ["dist/util/entities/*.js"],
+ synchronize: type !== "mongodb",
+ logging: false,
+ bigNumberStrings: false,
+ supportBigNumbers: true,
+ name: "default",
+ // migrations: [path.join(__dirname, "..", "migrations", "*.js")],
+ });
+
+ dbConnection = await dataSource.initialize();
+
+ // // @ts-ignore
+ // promise = createConnection({
+ // type,
+ // charset: 'utf8mb4',
+ // url: isSqlite ? undefined : dbConnectionString,
+ // database: isSqlite ? dbConnectionString : undefined,
+ // // @ts-ignore
+ // entities: Object.values(Models).filter((x) => x?.constructor?.name !== "Object" && x?.name),
+ // synchronize: type !== "mongodb",
+ // logging: false,
+ // // cache: { // cache is used only by query builder and entity manager
+ // // duration: 1000 * 30,
+ // // type: "redis",
+ // // options: {
+ // // host: "localhost",
+ // // port: 6379,
+ // // },
+ // // },
+ // bigNumberStrings: false,
+ // supportBigNumbers: true,
+ // name: "default",
+ // migrations: [path.join(__dirname, "..", "migrations", "*.js")],
+ // });
+
+ // // run migrations, and if it is a new fresh database, set it to the last migration
+ // if (dbConnection.migrations.length) {
+ // if (!(await Migration.findOne({ }))) {
+ // let i = 0;
+
+ // await Migration.insert(
+ // dbConnection.migrations.map((x) => ({
+ // id: i++,
+ // name: x.name,
+ // timestamp: Date.now(),
+ // }))
+ // );
+ // }
+ // }
+ await dbConnection.runMigrations();
+ console.log(`[Database] ${green("connected")}`);
+
+ return dbConnection;
+}
+
+export { dbConnection };
+
+export function closeDatabase() {
+ dbConnection?.destroy();
+}
diff --git a/util/src/util/Email.ts b/src/util/util/Email.ts
index 6885da33..6885da33 100644
--- a/util/src/util/Email.ts
+++ b/src/util/util/Email.ts
diff --git a/util/src/util/Event.ts b/src/util/util/Event.ts
index 20a638a0..20a638a0 100644
--- a/util/src/util/Event.ts
+++ b/src/util/util/Event.ts
diff --git a/util/src/util/FieldError.ts b/src/util/util/FieldError.ts
index 406b33e8..406b33e8 100644
--- a/util/src/util/FieldError.ts
+++ b/src/util/util/FieldError.ts
diff --git a/util/src/util/Intents.ts b/src/util/util/Intents.ts
index 1e840b76..1e840b76 100644
--- a/util/src/util/Intents.ts
+++ b/src/util/util/Intents.ts
diff --git a/util/src/util/InvisibleCharacters.ts b/src/util/util/InvisibleCharacters.ts
index a48cfab0..a48cfab0 100644
--- a/util/src/util/InvisibleCharacters.ts
+++ b/src/util/util/InvisibleCharacters.ts
diff --git a/util/src/util/MessageFlags.ts b/src/util/util/MessageFlags.ts
index b59295c4..b59295c4 100644
--- a/util/src/util/MessageFlags.ts
+++ b/src/util/util/MessageFlags.ts
diff --git a/util/src/util/Permissions.ts b/src/util/util/Permissions.ts
index e5459ab5..e5459ab5 100644
--- a/util/src/util/Permissions.ts
+++ b/src/util/util/Permissions.ts
diff --git a/util/src/util/RabbitMQ.ts b/src/util/util/RabbitMQ.ts
index 0f5eb6aa..0f5eb6aa 100644
--- a/util/src/util/RabbitMQ.ts
+++ b/src/util/util/RabbitMQ.ts
diff --git a/util/src/util/Regex.ts b/src/util/util/Regex.ts
index 83fc9fe8..83fc9fe8 100644
--- a/util/src/util/Regex.ts
+++ b/src/util/util/Regex.ts
diff --git a/util/src/util/Rights.ts b/src/util/util/Rights.ts
index b28c75b7..b28c75b7 100644
--- a/util/src/util/Rights.ts
+++ b/src/util/util/Rights.ts
diff --git a/util/src/util/Snowflake.ts b/src/util/util/Snowflake.ts
index 134d526e..134d526e 100644
--- a/util/src/util/Snowflake.ts
+++ b/src/util/util/Snowflake.ts
diff --git a/util/src/util/String.ts b/src/util/util/String.ts
index 55f11e8d..55f11e8d 100644
--- a/util/src/util/String.ts
+++ b/src/util/util/String.ts
diff --git a/util/src/util/Token.ts b/src/util/util/Token.ts
index 500ace45..5ba3e1ec 100644
--- a/util/src/util/Token.ts
+++ b/src/util/util/Token.ts
@@ -11,14 +11,14 @@ export function checkToken(token: string, jwtSecret: string): Promise<any> {
in fosscord, even with instances that have bot distinction; we won't enforce "Bot" prefix,
as we don't really have separate pathways for bots
**/
-
+
jwt.verify(token, jwtSecret, JWTOptions, async (err, decoded: any) => {
if (err || !decoded) return rej("Invalid Token");
- const user = await User.findOne(
- { id: decoded.id },
- { select: ["data", "bot", "disabled", "deleted", "rights"] }
- );
+ const user = await User.findOne({
+ where: { id: decoded.id },
+ select: ["data", "bot", "disabled", "deleted", "rights"]
+ });
if (!user) return rej("Invalid Token");
// we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds
if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0))
diff --git a/util/src/util/TraverseDirectory.ts b/src/util/util/TraverseDirectory.ts
index 3d0d6279..3d0d6279 100644
--- a/util/src/util/TraverseDirectory.ts
+++ b/src/util/util/TraverseDirectory.ts
diff --git a/util/src/util/cdn.ts b/src/util/util/cdn.ts
index ea950cd1..812a4e1d 100644
--- a/util/src/util/cdn.ts
+++ b/src/util/util/cdn.ts
@@ -1,10 +1,10 @@
import FormData from "form-data";
import { HTTPError } from "lambert-server";
import fetch from "node-fetch";
+import { Attachment } from "../entities";
import { Config } from "./Config";
-import multer from "multer";
-export async function uploadFile(path: string, file?: Express.Multer.File) {
+export async function uploadFile(path: string, file?: Express.Multer.File): Promise<Attachment> {
if (!file?.buffer) throw new HTTPError("Missing file in body");
const form = new FormData();
@@ -21,7 +21,7 @@ export async function uploadFile(path: string, file?: Express.Multer.File) {
method: "POST",
body: form,
});
- const result = await response.json();
+ const result = await response.json() as Attachment;
if (response.status !== 200) throw result;
return result;
diff --git a/util/src/util/index.ts b/src/util/util/index.ts
index b2bd6489..b2bd6489 100644
--- a/util/src/util/index.ts
+++ b/src/util/util/index.ts
|