diff --git a/util/src/entities/BaseClass.ts b/util/src/entities/BaseClass.ts
index 2d2457de..bb6ccea1 100644
--- a/util/src/entities/BaseClass.ts
+++ b/util/src/entities/BaseClass.ts
@@ -3,6 +3,10 @@ import { BaseEntity, BeforeInsert, BeforeUpdate, PrimaryColumn } from "typeorm";
import { Snowflake } from "../util/Snowflake";
import Ajv, { ValidateFunction } from "ajv";
import schema from "./schema.json";
+import "missing-native-js-functions";
+
+// TODO use class-validator https://typeorm.io/#/validation with class annotators (isPhone/isEmail) combined with types from typescript-json-schema
+// btw. we don't use class-validator for everything, because we need to explicitly set the type instead of deriving it from typescript also it doesn't easily support nested objects
const ajv = new Ajv({
removeAdditional: "all",
@@ -12,7 +16,6 @@ const ajv = new Ajv({
validateFormats: false,
allowUnionTypes: true,
});
-// const validator = ajv.compile<BaseClass>(schema);
export class BaseClass extends BaseEntity {
@PrimaryColumn()
@@ -43,10 +46,20 @@ export class BaseClass extends BaseEntity {
delete props.opts;
+ const properties = new Set(this.metadata.columns.map((x: any) => x.propertyName));
+ // will not include relational properties (e.g. @RelationId @ManyToMany)
+
for (const key in props) {
if (this.hasOwnProperty(key)) continue;
+ if (!properties.has(key)) continue;
+ // @ts-ignore
+ const setter = this[`set${key.capitalize()}`];
- Object.defineProperty(this, key, { value: props[key] });
+ if (setter) {
+ setter.call(this, props[key]);
+ } else {
+ Object.defineProperty(this, key, { value: props[key] });
+ }
}
}
diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts
index 60d84958..c9326be2 100644
--- a/util/src/entities/Config.ts
+++ b/util/src/entities/Config.ts
@@ -1,7 +1,7 @@
import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
-import { Snowflake } from "../util";
import { BaseClass } from "./BaseClass";
import crypto from "crypto";
+import { Snowflake } from "../util/Snowflake";
@Entity("config")
export class ConfigEntity extends BaseClass {
diff --git a/util/src/entities/ReadState.ts b/util/src/entities/ReadState.ts
index 0310cb5f..70a38b84 100644
--- a/util/src/entities/ReadState.ts
+++ b/util/src/entities/ReadState.ts
@@ -4,6 +4,10 @@ import { Channel } from "./Channel";
import { Message } from "./Message";
import { User } from "./User";
+// for read receipts
+// notification cursor and public read receipt need to be forwards-only (the former to prevent re-pinging when marked as unread, and the latter to be acceptable as a legal acknowledgement in criminal proceedings), and private read marker needs to be advance-rewind capable
+// public read receipt ≥ notification cursor ≥ private fully read marker
+
@Entity("read_states")
export class ReadState extends BaseClass {
@RelationId((read_state: ReadState) => read_state.channel)
diff --git a/util/src/entities/Template.ts b/util/src/entities/Template.ts
index c8d2034c..1dfc3b1b 100644
--- a/util/src/entities/Template.ts
+++ b/util/src/entities/Template.ts
@@ -1,11 +1,11 @@
-import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
+import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { User } from "./User";
@Entity("templates")
export class Template extends BaseClass {
- @Column()
+ @PrimaryColumn()
code: string;
@Column()
@@ -36,4 +36,7 @@ export class Template extends BaseClass {
@JoinColumn({ name: "source_guild_id" })
@ManyToOne(() => Guild, (guild: Guild) => guild.id)
source_guild: Guild;
+
+ @Column("simple-json")
+ serialized_source_guild: Guild;
}
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index bdf0d35f..03cb3af4 100644
--- a/util/src/entities/User.ts
+++ b/util/src/entities/User.ts
@@ -29,8 +29,8 @@ export class User extends BaseClass {
setDiscriminator(val: string) {
const number = Number(val);
if (isNaN(number)) throw new Error("invalid discriminator");
- if (number > 0 && number < 10000) throw new Error("discriminator must be between 1 and 9999");
- this.discriminator = val;
+ if (number <= 0 || number > 10000) throw new Error("discriminator must be between 1 and 9999");
+ this.discriminator = val.toString();
}
@Column()
diff --git a/util/src/entities/schema.json b/util/src/entities/schema.json
index ebf66e88..7f192690 100644
--- a/util/src/entities/schema.json
+++ b/util/src/entities/schema.json
@@ -1198,6 +1198,380 @@
},
"type": "object"
},
+ "ConfigEntity": {
+ "properties": {
+ "construct": {
+ },
+ "id": {
+ "type": "string"
+ },
+ "metadata": {
+ },
+ "opts": {
+ "properties": {
+ "id": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "value": {
+ "$ref": "#/definitions/ConfigValue"
+ }
+ },
+ "type": "object"
+ },
+ "ConfigValue": {
+ "properties": {
+ "cdn": {
+ "properties": {
+ "endpoint": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "endpointClient": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "gateway": {
+ "properties": {
+ "endpoint": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "endpointClient": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "general": {
+ "properties": {
+ "instance_id": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "kafka": {
+ "properties": {
+ "brokers": {
+ "anyOf": [
+ {
+ "items": {
+ "$ref": "#/definitions/KafkaBroker"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "limits": {
+ "properties": {
+ "channel": {
+ "properties": {
+ "maxPins": {
+ "type": "number"
+ },
+ "maxTopic": {
+ "type": "number"
+ }
+ },
+ "type": "object"
+ },
+ "guild": {
+ "properties": {
+ "hideOfflineMember": {
+ "type": "number"
+ },
+ "maxChannels": {
+ "type": "number"
+ },
+ "maxChannelsInCategory": {
+ "type": "number"
+ },
+ "maxMembers": {
+ "type": "number"
+ },
+ "maxRoles": {
+ "type": "number"
+ }
+ },
+ "type": "object"
+ },
+ "message": {
+ "properties": {
+ "maxAttachmentSize": {
+ "type": "number"
+ },
+ "maxBulkDelete": {
+ "type": "number"
+ },
+ "maxCharacters": {
+ "type": "number"
+ },
+ "maxReactions": {
+ "type": "number"
+ },
+ "maxTTSCharacters": {
+ "type": "number"
+ }
+ },
+ "type": "object"
+ },
+ "rate": {
+ "properties": {
+ "error": {
+ "$ref": "#/definitions/RateLimitOptions"
+ },
+ "global": {
+ "$ref": "#/definitions/RateLimitOptions"
+ },
+ "ip": {
+ "$ref": "#/definitions/Omit<RateLimitOptions,\"bot_count\">"
+ },
+ "routes": {
+ "properties": {
+ "auth": {
+ "properties": {
+ "login": {
+ "$ref": "#/definitions/RateLimitOptions"
+ },
+ "register": {
+ "$ref": "#/definitions/RateLimitOptions"
+ }
+ },
+ "type": "object"
+ },
+ "channel": {
+ "$ref": "#/definitions/RateLimitOptions"
+ },
+ "guild": {
+ "$ref": "#/definitions/RateLimitOptions"
+ },
+ "webhook": {
+ "$ref": "#/definitions/RateLimitOptions"
+ }
+ },
+ "type": "object"
+ }
+ },
+ "type": "object"
+ },
+ "user": {
+ "properties": {
+ "maxFriends": {
+ "type": "number"
+ },
+ "maxGuilds": {
+ "type": "number"
+ },
+ "maxUsername": {
+ "type": "number"
+ }
+ },
+ "type": "object"
+ }
+ },
+ "type": "object"
+ },
+ "login": {
+ "properties": {
+ "requireCaptcha": {
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ },
+ "permissions": {
+ "properties": {
+ "user": {
+ "properties": {
+ "createGuilds": {
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ }
+ },
+ "type": "object"
+ },
+ "rabbitmq": {
+ "properties": {
+ "host": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "regions": {
+ "properties": {
+ "available": {
+ "items": {
+ "$ref": "#/definitions/Region"
+ },
+ "type": "array"
+ },
+ "default": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
+ "register": {
+ "properties": {
+ "allowMultipleAccounts": {
+ "type": "boolean"
+ },
+ "allowNewRegistration": {
+ "type": "boolean"
+ },
+ "blockProxies": {
+ "type": "boolean"
+ },
+ "dateOfBirth": {
+ "properties": {
+ "minimum": {
+ "type": "number"
+ },
+ "necessary": {
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ },
+ "email": {
+ "properties": {
+ "allowlist": {
+ "type": "boolean"
+ },
+ "blocklist": {
+ "type": "boolean"
+ },
+ "domains": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ "necessary": {
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ },
+ "password": {
+ "properties": {
+ "minLength": {
+ "type": "number"
+ },
+ "minNumbers": {
+ "type": "number"
+ },
+ "minSymbols": {
+ "type": "number"
+ },
+ "minUpperCase": {
+ "type": "number"
+ }
+ },
+ "type": "object"
+ },
+ "requireCaptcha": {
+ "type": "boolean"
+ },
+ "requireInvite": {
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ },
+ "security": {
+ "properties": {
+ "autoUpdate": {
+ "type": [
+ "number",
+ "boolean"
+ ]
+ },
+ "captcha": {
+ "properties": {
+ "enabled": {
+ "type": "boolean"
+ },
+ "secret": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "service": {
+ "anyOf": [
+ {
+ "enum": [
+ "hcaptcha",
+ "recaptcha"
+ ],
+ "type": "string"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "sitekey": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "type": "object"
+ },
+ "forwadedFor": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "ipdataApiKey": {
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "jwtSecret": {
+ "type": "string"
+ },
+ "requestSignature": {
+ "type": "string"
+ }
+ },
+ "type": "object"
+ }
+ },
+ "type": "object"
+ },
"ConnectedAccount": {
"properties": {
"access_token": {
@@ -1721,6 +2095,9 @@
"public_updates_channel": {
"$ref": "#/definitions/Channel"
},
+ "public_updates_channel_id": {
+ "type": "string"
+ },
"region": {
"type": "string"
},
@@ -1760,6 +2137,9 @@
"vanity_url": {
"$ref": "#/definitions/Invite"
},
+ "vanity_url_code": {
+ "type": "string"
+ },
"verification_level": {
"type": "number"
},
@@ -1809,6 +2189,9 @@
"widget_channel": {
"$ref": "#/definitions/Channel"
},
+ "widget_channel_id": {
+ "type": "string"
+ },
"widget_enabled": {
"type": "boolean"
}
@@ -2520,12 +2903,12 @@
"target_user": {
"type": "string"
},
+ "target_user_id": {
+ "type": "string"
+ },
"target_user_type": {
"type": "number"
},
- "target_usser_id": {
- "type": "string"
- },
"temporary": {
"type": "boolean"
},
@@ -2615,6 +2998,17 @@
},
"type": "object"
},
+ "KafkaBroker": {
+ "properties": {
+ "ip": {
+ "type": "string"
+ },
+ "port": {
+ "type": "number"
+ }
+ },
+ "type": "object"
+ },
"ListenEventOpts": {
"properties": {
"acknowledge": {
@@ -3507,12 +3901,12 @@
"target_user": {
"type": "string"
},
+ "target_user_id": {
+ "type": "string"
+ },
"target_user_type": {
"type": "number"
},
- "target_usser_id": {
- "type": "string"
- },
"temporary": {
"type": "boolean"
},
@@ -3843,6 +4237,23 @@
},
"type": "object"
},
+ "Omit<RateLimitOptions,\"bot_count\">": {
+ "properties": {
+ "bot": {
+ "type": "number"
+ },
+ "count": {
+ "type": "number"
+ },
+ "onyIp": {
+ "type": "boolean"
+ },
+ "window": {
+ "type": "number"
+ }
+ },
+ "type": "object"
+ },
"Omit<Relationship,\"nickname\">": {
"properties": {
"assign": {
@@ -4322,6 +4733,23 @@
},
"type": "object"
},
+ "RateLimitOptions": {
+ "properties": {
+ "bot": {
+ "type": "number"
+ },
+ "count": {
+ "type": "number"
+ },
+ "onyIp": {
+ "type": "boolean"
+ },
+ "window": {
+ "type": "number"
+ }
+ },
+ "type": "object"
+ },
"Reaction": {
"properties": {
"count": {
@@ -4355,6 +4783,9 @@
"last_message": {
"$ref": "#/definitions/Message"
},
+ "last_message_id": {
+ "type": "string"
+ },
"last_pin_timestamp": {
"format": "date-time",
"type": "string"
@@ -4763,6 +5194,29 @@
"Record<string,string|null>": {
"type": "object"
},
+ "Region": {
+ "properties": {
+ "custom": {
+ "type": "boolean"
+ },
+ "deprecated": {
+ "type": "boolean"
+ },
+ "id": {
+ "type": "string"
+ },
+ "name": {
+ "type": "string"
+ },
+ "optimal": {
+ "type": "boolean"
+ },
+ "vip": {
+ "type": "boolean"
+ }
+ },
+ "type": "object"
+ },
"Relationship": {
"properties": {
"construct": {
@@ -5053,6 +5507,9 @@
"creator": {
"$ref": "#/definitions/User"
},
+ "creator_id": {
+ "type": "string"
+ },
"description": {
"type": "string"
},
@@ -5072,9 +5529,15 @@
},
"type": "object"
},
+ "serialized_source_guild": {
+ "$ref": "#/definitions/Guild"
+ },
"source_guild": {
"$ref": "#/definitions/Guild"
},
+ "source_guild_id": {
+ "type": "string"
+ },
"updated_at": {
"format": "date-time",
"type": "string"
@@ -5164,6 +5627,18 @@
"format": "date-time",
"type": "string"
},
+ "data": {
+ "properties": {
+ "hash": {
+ "type": "string"
+ },
+ "valid_tokens_since": {
+ "format": "date-time",
+ "type": "string"
+ }
+ },
+ "type": "object"
+ },
"deleted": {
"type": "boolean"
},
@@ -5179,15 +5654,27 @@
"email": {
"type": "string"
},
+ "fingerprints": {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
"flags": {
"type": "bigint"
},
- "guilds": {
+ "guild_ids": {
"items": {
"type": "string"
},
"type": "array"
},
+ "guilds": {
+ "items": {
+ "$ref": "#/definitions/Guild"
+ },
+ "type": "array"
+ },
"id": {
"type": "string"
},
@@ -5240,24 +5727,6 @@
"system": {
"type": "boolean"
},
- "user_data": {
- "properties": {
- "fingerprints": {
- "items": {
- "type": "string"
- },
- "type": "array"
- },
- "hash": {
- "type": "string"
- },
- "valid_tokens_since": {
- "format": "date-time",
- "type": "string"
- }
- },
- "type": "object"
- },
"username": {
"type": "string"
},
@@ -5544,6 +6013,9 @@
"channel": {
"$ref": "#/definitions/Channel"
},
+ "channel_id": {
+ "type": "string"
+ },
"construct": {
},
"deaf": {
@@ -5552,6 +6024,9 @@
"guild": {
"$ref": "#/definitions/Guild"
},
+ "guild_id": {
+ "type": "string"
+ },
"id": {
"type": "string"
},
@@ -5588,6 +6063,9 @@
},
"user": {
"$ref": "#/definitions/User"
+ },
+ "user_id": {
+ "type": "string"
}
},
"type": "object"
@@ -5634,17 +6112,26 @@
"Webhook": {
"properties": {
"application": {
+ "$ref": "#/definitions/Application"
+ },
+ "application_id": {
"type": "string"
},
"avatar": {
"type": "string"
},
"channel": {
+ "$ref": "#/definitions/Channel"
+ },
+ "channel_id": {
"type": "string"
},
"construct": {
},
"guild": {
+ "$ref": "#/definitions/Guild"
+ },
+ "guild_id": {
"type": "string"
},
"id": {
@@ -5664,6 +6151,9 @@
"type": "object"
},
"source_guild": {
+ "$ref": "#/definitions/Guild"
+ },
+ "source_guild_id": {
"type": "string"
},
"token": {
@@ -5673,6 +6163,9 @@
"$ref": "#/definitions/WebhookType"
},
"user": {
+ "$ref": "#/definitions/User"
+ },
+ "user_id": {
"type": "string"
}
},
diff --git a/util/src/util/Config.ts b/util/src/util/Config.ts
new file mode 100644
index 00000000..f8574f38
--- /dev/null
+++ b/util/src/util/Config.ts
@@ -0,0 +1,19 @@
+import "missing-native-js-functions";
+import { ConfigValue, ConfigEntity, DefaultConfigOptions } from "../entities/Config";
+
+var config: ConfigEntity;
+// TODO: use events to inform about config updates
+
+export const Config = {
+ init: async function init() {
+ config = new ConfigEntity({}, { id: "0" });
+ return this.set((config.value || {}).merge(DefaultConfigOptions));
+ },
+ get: function get() {
+ return config.value as ConfigValue;
+ },
+ set: function set(val: any) {
+ config.value = val.merge(config.value);
+ return config.save();
+ },
+};
diff --git a/util/src/util/Database.ts b/util/src/util/Database.ts
index 90ba9079..8bb8078e 100644
--- a/util/src/util/Database.ts
+++ b/util/src/util/Database.ts
@@ -1,11 +1,12 @@
import "reflect-metadata";
-import { createConnection } from "typeorm";
+import { Connection, createConnection } from "typeorm";
import * as Models from "../entities";
// 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 promise: Promise<any>;
+var dbConnection: Connection | undefined;
export function initDatabase() {
if (promise) return promise; // prevent initalizing multiple times
@@ -20,7 +21,16 @@ export function initDatabase() {
logging: false,
});
- promise.then(() => console.log("[Database] connected"));
+ promise.then((connection) => {
+ dbConnection = connection;
+ console.log("[Database] connected");
+ });
return promise;
}
+
+export { dbConnection };
+
+export function closeDatabase() {
+ dbConnection?.close();
+}
diff --git a/util/src/util/checkToken.ts b/util/src/util/checkToken.ts
index 58e537ea..1e203006 100644
--- a/util/src/util/checkToken.ts
+++ b/util/src/util/checkToken.ts
@@ -9,13 +9,10 @@ export function checkToken(token: string, jwtSecret: string): Promise<any> {
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: ["user_data", "bot", "disabled", "deleted"] }
- );
+ const user = await User.findOne({ id: decoded.id }, { select: ["data", "bot", "disabled", "deleted"] });
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 < user.user_data.valid_tokens_since.setSeconds(0, 0)) return rej("Invalid Token");
+ if (decoded.iat * 1000 < user.data.valid_tokens_since.setSeconds(0, 0)) return rej("Invalid Token");
if (user.disabled) return rej("User disabled");
if (user.deleted) return rej("User not found");
diff --git a/util/src/util/index.ts b/util/src/util/index.ts
index 700ecfa5..16b98ca3 100644
--- a/util/src/util/index.ts
+++ b/util/src/util/index.ts
@@ -1,13 +1,13 @@
export * from "./Database";
-export * from "./Regex";
-export * from "./String";
export * from "./BitField";
+export * from "./Config";
+export * from "./checkToken";
+export * from "./Event";
export * from "./Intents";
export * from "./MessageFlags";
export * from "./Permissions";
export * from "./Snowflake";
-export * from "./toBigInt";
export * from "./RabbitMQ";
-export * from "./Event";
-export * from "./checkToken";
+export * from "./Regex";
+export * from "./String";
diff --git a/util/src/util/toBigInt.ts b/util/src/util/toBigInt.ts
deleted file mode 100644
index b7985928..00000000
--- a/util/src/util/toBigInt.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export default function toBigInt(string: string): bigint {
- return BigInt(string);
-}
-
|