1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-11-10 12:42:44 +01:00

🚧 typeorm

This commit is contained in:
Flam3rboy 2021-08-27 11:11:16 +02:00
parent 0ecc5d8c0e
commit c21c342821
18 changed files with 1465 additions and 732 deletions

View File

@ -106,7 +106,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
}),
guild_experiments: [], // TODO
geo_ordered_rtc_regions: [], // TODO
relationships: user.user_data.relationships,
relationships: user.data.relationships,
read_state: {
// TODO
entries: [],

845
util/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -44,6 +44,7 @@
"dot-prop": "^6.0.1",
"env-paths": "^2.2.1",
"jsonwebtoken": "^8.5.1",
"lambert-server": "^1.2.8",
"missing-native-js-functions": "^1.2.10",
"node-fetch": "^2.6.1",
"patch-package": "^6.4.7",

View File

@ -1,381 +0,0 @@
diff --git a/node_modules/test-performance/cjs/index.js b/node_modules/test-performance/cjs/index.js
index 65d5904..04638a1 100644
--- a/node_modules/test-performance/cjs/index.js
+++ b/node_modules/test-performance/cjs/index.js
@@ -1,4 +1,4 @@
-'use strict';
+"use strict";
/*! *****************************************************************************
Copyright (c) Microsoft Corporation.
@@ -15,50 +15,139 @@ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
+const { performance } = require("perf_hooks");
+
function __awaiter(thisArg, _arguments, P, generator) {
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
+ function adopt(value) {
+ return value instanceof P
+ ? value
+ : new P(function (resolve) {
+ resolve(value);
+ });
+ }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) {
+ try {
+ step(generator.next(value));
+ } catch (e) {
+ reject(e);
+ }
+ }
+ function rejected(value) {
+ try {
+ step(generator["throw"](value));
+ } catch (e) {
+ reject(e);
+ }
+ }
+ function step(result) {
+ result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
+ }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
}
function __generator(thisArg, body) {
- var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
- return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
- function verb(n) { return function (v) { return step([n, v]); }; }
- function step(op) {
- if (f) throw new TypeError("Generator is already executing.");
- while (_) try {
- if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
- if (y = 0, t) op = [op[0] & 2, t.value];
- switch (op[0]) {
- case 0: case 1: t = op; break;
- case 4: _.label++; return { value: op[1], done: false };
- case 5: _.label++; y = op[1]; op = [0]; continue;
- case 7: op = _.ops.pop(); _.trys.pop(); continue;
- default:
- if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
- if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
- if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
- if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
- if (t[2]) _.ops.pop();
- _.trys.pop(); continue;
- }
- op = body.call(thisArg, _);
- } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
- if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
- }
+ var _ = {
+ label: 0,
+ sent: function () {
+ if (t[0] & 1) throw t[1];
+ return t[1];
+ },
+ trys: [],
+ ops: [],
+ },
+ f,
+ y,
+ t,
+ g;
+ return (
+ (g = { next: verb(0), throw: verb(1), return: verb(2) }),
+ typeof Symbol === "function" &&
+ (g[Symbol.iterator] = function () {
+ return this;
+ }),
+ g
+ );
+ function verb(n) {
+ return function (v) {
+ return step([n, v]);
+ };
+ }
+ function step(op) {
+ if (f) throw new TypeError("Generator is already executing.");
+ while (_)
+ try {
+ if (
+ ((f = 1),
+ y &&
+ (t =
+ op[0] & 2
+ ? y["return"]
+ : op[0]
+ ? y["throw"] || ((t = y["return"]) && t.call(y), 0)
+ : y.next) &&
+ !(t = t.call(y, op[1])).done)
+ )
+ return t;
+ if (((y = 0), t)) op = [op[0] & 2, t.value];
+ switch (op[0]) {
+ case 0:
+ case 1:
+ t = op;
+ break;
+ case 4:
+ _.label++;
+ return { value: op[1], done: false };
+ case 5:
+ _.label++;
+ y = op[1];
+ op = [0];
+ continue;
+ case 7:
+ op = _.ops.pop();
+ _.trys.pop();
+ continue;
+ default:
+ if (!((t = _.trys), (t = t.length > 0 && t[t.length - 1])) && (op[0] === 6 || op[0] === 2)) {
+ _ = 0;
+ continue;
+ }
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
+ _.label = op[1];
+ break;
+ }
+ if (op[0] === 6 && _.label < t[1]) {
+ _.label = t[1];
+ t = op;
+ break;
+ }
+ if (t && _.label < t[2]) {
+ _.label = t[2];
+ _.ops.push(op);
+ break;
+ }
+ if (t[2]) _.ops.pop();
+ _.trys.pop();
+ continue;
+ }
+ op = body.call(thisArg, _);
+ } catch (e) {
+ op = [6, e];
+ y = 0;
+ } finally {
+ f = t = 0;
+ }
+ if (op[0] & 5) throw op[1];
+ return { value: op[0] ? op[1] : void 0, done: true };
+ }
}
var baselineFunctions = {
- standard: {
- expectedMsRunTime: 12,
- func: function (dummyParam) {
- },
- },
+ standard: {
+ expectedMsRunTime: 12,
+ func: function (dummyParam) {},
+ },
};
/**
@@ -66,28 +155,28 @@ var baselineFunctions = {
* to a baseline.
*/
function calculateExpectedPerformance(baselineExpected, baselineActual, target) {
- var performanceRatio = baselineActual / baselineExpected;
- return target / performanceRatio;
+ var performanceRatio = baselineActual / baselineExpected;
+ return target / performanceRatio;
}
/**
* This runs a single performance test of a function
*/
function perfTest(func) {
- return __awaiter(this, void 0, void 0, function () {
- var start, end;
- return __generator(this, function (_a) {
- switch (_a.label) {
- case 0:
- start = performance.now();
- return [4 /*yield*/, func()];
- case 1:
- _a.sent();
- end = performance.now();
- return [2 /*return*/, end - start];
- }
- });
- });
+ return __awaiter(this, void 0, void 0, function () {
+ var start, end;
+ return __generator(this, function (_a) {
+ switch (_a.label) {
+ case 0:
+ start = performance.now();
+ return [4 /*yield*/, func()];
+ case 1:
+ _a.sent();
+ end = performance.now();
+ return [2 /*return*/, end - start];
+ }
+ });
+ });
}
var NUMBER_OF_TESTS = 10000;
@@ -96,22 +185,31 @@ var NUMBER_OF_TESTS = 10000;
* the average in milliseconds.
*/
function getScore(func) {
- return __awaiter(this, void 0, void 0, function () {
- var tests, results;
- var _this = this;
- return __generator(this, function (_a) {
- switch (_a.label) {
- case 0:
- tests = new Array(NUMBER_OF_TESTS).fill(undefined).map(function () { return __awaiter(_this, void 0, void 0, function () { return __generator(this, function (_a) {
- return [2 /*return*/, perfTest(func)];
- }); }); });
- return [4 /*yield*/, Promise.all(tests)];
- case 1:
- results = _a.sent();
- return [2 /*return*/, results.reduce(function (acc, val) { return acc + val; }, 0) / NUMBER_OF_TESTS];
- }
- });
- });
+ return __awaiter(this, void 0, void 0, function () {
+ var tests, results;
+ var _this = this;
+ return __generator(this, function (_a) {
+ switch (_a.label) {
+ case 0:
+ tests = new Array(NUMBER_OF_TESTS).fill(undefined).map(function () {
+ return __awaiter(_this, void 0, void 0, function () {
+ return __generator(this, function (_a) {
+ return [2 /*return*/, perfTest(func)];
+ });
+ });
+ });
+ return [4 /*yield*/, Promise.all(tests)];
+ case 1:
+ results = _a.sent();
+ return [
+ 2 /*return*/,
+ results.reduce(function (acc, val) {
+ return acc + val;
+ }, 0) / NUMBER_OF_TESTS,
+ ];
+ }
+ });
+ });
}
/**
@@ -119,31 +217,38 @@ function getScore(func) {
* known baseline function.
*/
function runTests(baselineFunc, targetFunc) {
- return __awaiter(this, void 0, void 0, function () {
- var initialBaselineScore, initialTargetScore, midwayBaselineScore, endTargetScore, endBaselineScore, totalBaselineScore, totalTargetScore;
- return __generator(this, function (_a) {
- switch (_a.label) {
- case 0: return [4 /*yield*/, getScore(baselineFunc)];
- case 1:
- initialBaselineScore = _a.sent();
- return [4 /*yield*/, getScore(targetFunc)];
- case 2:
- initialTargetScore = _a.sent();
- return [4 /*yield*/, getScore(baselineFunc)];
- case 3:
- midwayBaselineScore = _a.sent();
- return [4 /*yield*/, getScore(targetFunc)];
- case 4:
- endTargetScore = _a.sent();
- return [4 /*yield*/, getScore(baselineFunc)];
- case 5:
- endBaselineScore = _a.sent();
- totalBaselineScore = (initialBaselineScore + midwayBaselineScore + endBaselineScore) / 3;
- totalTargetScore = (initialTargetScore + endTargetScore) / 2;
- return [2 /*return*/, [totalBaselineScore, totalTargetScore]];
- }
- });
- });
+ return __awaiter(this, void 0, void 0, function () {
+ var initialBaselineScore,
+ initialTargetScore,
+ midwayBaselineScore,
+ endTargetScore,
+ endBaselineScore,
+ totalBaselineScore,
+ totalTargetScore;
+ return __generator(this, function (_a) {
+ switch (_a.label) {
+ case 0:
+ return [4 /*yield*/, getScore(baselineFunc)];
+ case 1:
+ initialBaselineScore = _a.sent();
+ return [4 /*yield*/, getScore(targetFunc)];
+ case 2:
+ initialTargetScore = _a.sent();
+ return [4 /*yield*/, getScore(baselineFunc)];
+ case 3:
+ midwayBaselineScore = _a.sent();
+ return [4 /*yield*/, getScore(targetFunc)];
+ case 4:
+ endTargetScore = _a.sent();
+ return [4 /*yield*/, getScore(baselineFunc)];
+ case 5:
+ endBaselineScore = _a.sent();
+ totalBaselineScore = (initialBaselineScore + midwayBaselineScore + endBaselineScore) / 3;
+ totalTargetScore = (initialTargetScore + endTargetScore) / 2;
+ return [2 /*return*/, [totalBaselineScore, totalTargetScore]];
+ }
+ });
+ });
}
/**
@@ -152,20 +257,22 @@ function runTests(baselineFunc, targetFunc) {
*/
var BASELINE_FUNCTION = baselineFunctions.standard;
function getPerformanceScore(func) {
- return __awaiter(this, void 0, void 0, function () {
- var _a, baseline, target;
- return __generator(this, function (_b) {
- switch (_b.label) {
- case 0:
- if (typeof func !== 'function')
- throw new Error(func + " is not a function");
- return [4 /*yield*/, runTests(BASELINE_FUNCTION.func, func)];
- case 1:
- _a = _b.sent(), baseline = _a[0], target = _a[1];
- return [2 /*return*/, calculateExpectedPerformance(BASELINE_FUNCTION.expectedMsRunTime, baseline, target)];
- }
- });
- });
+ return __awaiter(this, void 0, void 0, function () {
+ var _a, baseline, target;
+ return __generator(this, function (_b) {
+ switch (_b.label) {
+ case 0:
+ if (typeof func !== "function") throw new Error(func + " is not a function");
+ return [4 /*yield*/, runTests(BASELINE_FUNCTION.func, func)];
+ case 1:
+ (_a = _b.sent()), (baseline = _a[0]), (target = _a[1]);
+ return [
+ 2 /*return*/,
+ calculateExpectedPerformance(BASELINE_FUNCTION.expectedMsRunTime, baseline, target),
+ ];
+ }
+ });
+ });
}
module.exports = getPerformanceScore;

View File

@ -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] });
}
}
}

View File

@ -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 {

View File

@ -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)

View File

@ -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;
}

View File

@ -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()

View File

@ -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"
}
},

19
util/src/util/Config.ts Normal file
View File

@ -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();
},
};

View File

@ -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();
}

View File

@ -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");

View File

@ -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";

View File

@ -1,4 +0,0 @@
export default function toBigInt(string: string): bigint {
return BigInt(string);
}

31
util/tests/User.test.js Normal file
View File

@ -0,0 +1,31 @@
const { initDatabase, closeDatabase } = require("../dist/util/Database");
const { User } = require("../dist/entities/User");
jest.setTimeout(10000);
beforeAll((done) => {
initDatabase().then(() => {
new User().validate(); // warm up schema/model
done();
});
});
afterAll(() => {
closeDatabase();
});
describe("User", () => {
test("valid discriminator: 1", async () => {
new User({ discriminator: "1" }).validate();
});
test("invalid discriminator: test", async () => {
expect(() => {
new User({ discriminator: "test" }).validate();
}).toThrow();
});
test("invalid discriminator: 0", async () => {
expect(() => {
new User({ discriminator: "0" }).validate();
}).toThrow();
});
});

View File

@ -1,4 +1,4 @@
const { initDatabase } = require("../dist/util/Database");
const { initDatabase, closeDatabase } = require("../dist/util/Database");
const { User } = require("../dist/entities/User");
jest.setTimeout(10000);
@ -9,13 +9,15 @@ beforeAll((done) => {
});
});
afterAll(() => {
closeDatabase();
});
describe("Validate model class properties", () => {
describe("User", () => {
test("object instead of string", async () => {
expect(() => {
new User({ username: {} }).validate();
}).toThrow();
});
test("object instead of string", async () => {
expect(() => {
new User({}, { id: {} }).validate();
}).toThrow();
});
test("validation should be faster than 20ms", () => {

View File

@ -1,290 +0,0 @@
import { Schema, model, Types, Document } from "mongoose";
import "missing-native-js-functions";
import { Snowflake } from "./Snowflake";
import crypto from "crypto";
var config: any;
export default {
init: async function init(defaultOpts: any = DefaultOptions) {
config = await db.collection("config").findOneOrFail({});
return this.set((config || {}).merge(defaultOpts));
},
get: function get() {
return config as DefaultOptions;
},
set: function set(val: any) {
config = val.merge(config);
return db.collection("config").update({}, { $set: val }, { upsert: true });
},
};
export interface RateLimitOptions {
bot?: number;
count: number;
window: number;
onyIp?: boolean;
}
export interface Region {
id: string;
name: string;
vip: boolean;
custom: boolean;
deprecated: boolean;
optimal: boolean;
}
export interface KafkaBroker {
ip: string;
port: number;
}
export interface DefaultOptions {
gateway: {
endpointClient: string | null;
endpoint: string | null;
};
cdn: {
endpointClient: string | null;
endpoint: string | null;
};
general: {
instance_id: string;
};
permissions: {
user: {
createGuilds: boolean;
};
};
limits: {
user: {
maxGuilds: number;
maxUsername: number;
maxFriends: number;
};
guild: {
maxRoles: number;
maxMembers: number;
maxChannels: number;
maxChannelsInCategory: number;
hideOfflineMember: number;
};
message: {
maxCharacters: number;
maxTTSCharacters: number;
maxReactions: number;
maxAttachmentSize: number;
maxBulkDelete: number;
};
channel: {
maxPins: number;
maxTopic: number;
};
rate: {
ip: Omit<RateLimitOptions, "bot_count">;
global: RateLimitOptions;
error: RateLimitOptions;
routes: {
guild: RateLimitOptions;
webhook: RateLimitOptions;
channel: RateLimitOptions;
auth: {
login: RateLimitOptions;
register: RateLimitOptions;
};
// TODO: rate limit configuration for all routes
};
};
};
security: {
autoUpdate: boolean | number;
requestSignature: string;
jwtSecret: string;
forwadedFor: string | null; // header to get the real user ip address
captcha: {
enabled: boolean;
service: "recaptcha" | "hcaptcha" | null; // TODO: hcaptcha, custom
sitekey: string | null;
secret: string | null;
};
ipdataApiKey: string | null;
};
login: {
requireCaptcha: boolean;
};
register: {
email: {
necessary: boolean; // we have to use necessary instead of required as the cli tool uses json schema and can't use required
allowlist: boolean;
blocklist: boolean;
domains: string[];
};
dateOfBirth: {
necessary: boolean;
minimum: number; // in years
};
requireCaptcha: boolean;
requireInvite: boolean;
allowNewRegistration: boolean;
allowMultipleAccounts: boolean;
blockProxies: boolean;
password: {
minLength: number;
minNumbers: number;
minUpperCase: number;
minSymbols: number;
};
};
regions: {
default: string;
available: Region[];
};
rabbitmq: {
host: string | null;
};
kafka: {
brokers: KafkaBroker[] | null;
};
}
export const DefaultOptions: DefaultOptions = {
gateway: {
endpointClient: null,
endpoint: null,
},
cdn: {
endpointClient: null,
endpoint: null,
},
general: {
instance_id: Snowflake.generate(),
},
permissions: {
user: {
createGuilds: true,
},
},
limits: {
user: {
maxGuilds: 100,
maxUsername: 32,
maxFriends: 1000,
},
guild: {
maxRoles: 250,
maxMembers: 250000,
maxChannels: 500,
maxChannelsInCategory: 50,
hideOfflineMember: 1000,
},
message: {
maxCharacters: 2000,
maxTTSCharacters: 200,
maxReactions: 20,
maxAttachmentSize: 8388608,
maxBulkDelete: 100,
},
channel: {
maxPins: 50,
maxTopic: 1024,
},
rate: {
ip: {
count: 500,
window: 5,
},
global: {
count: 20,
window: 5,
bot: 250,
},
error: {
count: 10,
window: 5,
},
routes: {
guild: {
count: 5,
window: 5,
},
webhook: {
count: 5,
window: 20,
},
channel: {
count: 5,
window: 20,
},
auth: {
login: {
count: 5,
window: 60,
},
register: {
count: 2,
window: 60 * 60 * 12,
},
},
},
},
},
security: {
autoUpdate: true,
requestSignature: crypto.randomBytes(32).toString("base64"),
jwtSecret: crypto.randomBytes(256).toString("base64"),
forwadedFor: null,
// forwadedFor: "X-Forwarded-For" // nginx/reverse proxy
// forwadedFor: "CF-Connecting-IP" // cloudflare:
captcha: {
enabled: false,
service: null,
sitekey: null,
secret: null,
},
ipdataApiKey: "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9",
},
login: {
requireCaptcha: false,
},
register: {
email: {
necessary: true,
allowlist: false,
blocklist: true,
domains: [], // TODO: efficiently save domain blocklist in database
// domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"),
},
dateOfBirth: {
necessary: true,
minimum: 13,
},
requireInvite: false,
requireCaptcha: true,
allowNewRegistration: true,
allowMultipleAccounts: true,
blockProxies: true,
password: {
minLength: 8,
minNumbers: 2,
minUpperCase: 2,
minSymbols: 0,
},
},
regions: {
default: "fosscord",
available: [{ id: "fosscord", name: "Fosscord", vip: false, custom: false, deprecated: false, optimal: false }],
},
rabbitmq: {
host: null,
},
kafka: {
brokers: null,
},
};
export const ConfigSchema = new Schema({}, { strict: false });
export interface DefaultOptionsDocument extends DefaultOptions, Document {}
export const ConfigModel = model<DefaultOptionsDocument>("Config", ConfigSchema, "config");