From ef4d4a318176c3e572adc17427a8b8c728a618ab Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 24 Aug 2021 16:35:04 +0200 Subject: :construction: api --- api/src/middlewares/RateLimit.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'api/src/middlewares') diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index acf92606..9601dad3 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -72,7 +72,7 @@ export default function RateLimit(opts: { offender.expires_at = new Date(Date.now() + opts.window * 1000); offender.blocked = false; // mongodb ttl didn't update yet -> manually update/delete - db.collection("ratelimits").updateOne({ id: bucket_id, user_id }, { $set: offender }); + db.collection("ratelimits").update({ id: bucket_id, user_id }, { $set: offender }); Cache.delete(user_id + bucket_id); } } @@ -132,7 +132,7 @@ export async function initRateLimits(app: Router) { async function hitRoute(opts: { user_id: string; bucket_id: string; max_hits: number; window: number }) { const filter = { id: opts.bucket_id, user_id: opts.user_id }; - const { value } = await db.collection("ratelimits").findOneAndUpdate( + const { value } = await db.collection("ratelimits").findOneOrFailAndUpdate( filter, { $setOnInsert: { @@ -158,7 +158,7 @@ async function hitRoute(opts: { user_id: string; bucket_id: string; max_hits: nu event: EventRateLimit, data: value }); - await db.collection("ratelimits").updateOne(filter, { $set: { blocked: true } }); + await db.collection("ratelimits").update(filter, { $set: { blocked: true } }); } else { Cache.delete(opts.user_id); } -- cgit 1.5.1 From 2dbbd21f734b6c0785d5a02d8aed71f7f32d4cff Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Fri, 27 Aug 2021 11:10:42 +0200 Subject: :construction: api --- api/__tests__/routes/auth/login.ts | 1 - api/jest.config.ts | 4 +- api/package-lock.json | 1131 ++++++++++++++++++++++++-- api/package.json | 6 +- api/scripts/setup_test.js | 1 + api/src/middlewares/ErrorHandler.ts | 1 + api/src/routes/auth/login.ts | 12 +- api/src/routes/auth/register.ts | 59 +- api/src/routes/guilds/#guild_id/channels.ts | 12 +- api/src/routes/guilds/#guild_id/templates.ts | 60 +- api/src/routes/guilds/templates/index.ts | 7 +- api/src/routes/invites/index.ts | 4 +- api/src/routes/users/#id/profile.ts | 2 +- api/src/routes/users/@me/relationships.ts | 36 +- api/src/util/Channel.ts | 2 +- api/src/util/User.ts | 16 - api/tests/routes/auth/login.ts | 1 + 17 files changed, 1160 insertions(+), 195 deletions(-) delete mode 100644 api/__tests__/routes/auth/login.ts create mode 100644 api/scripts/setup_test.js delete mode 100644 api/src/util/User.ts create mode 100644 api/tests/routes/auth/login.ts (limited to 'api/src/middlewares') diff --git a/api/__tests__/routes/auth/login.ts b/api/__tests__/routes/auth/login.ts deleted file mode 100644 index 536e03ee..00000000 --- a/api/__tests__/routes/auth/login.ts +++ /dev/null @@ -1 +0,0 @@ -it("works", () => {}); diff --git a/api/jest.config.ts b/api/jest.config.ts index 153a9b2b..50550d9d 100644 --- a/api/jest.config.ts +++ b/api/jest.config.ts @@ -145,9 +145,9 @@ export default { // The glob patterns Jest uses to detect test files testMatch: [ - "**/__tests__/**/*.[jt]s?(x)", + "**/tests/**/*.[jt]s?(x)" // "**/?(*.)+(spec|test).[tj]s?(x)" - ], + ] // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped // testPathIgnorePatterns: [ diff --git a/api/package-lock.json b/api/package-lock.json index 3221969a..bff4e940 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -34,13 +34,15 @@ "mongoose-autopopulate": "^0.12.3", "mongoose-long": "^0.3.2", "multer": "^1.4.2", - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "typeorm": "^0.2.37" }, "devDependencies": { "@types/amqplib": "^0.8.1", "@types/bcrypt": "^5.0.0", "@types/express": "^4.17.9", "@types/i18next-node-fs-backend": "^2.1.0", + "@types/jest": "^27.0.1", "@types/jsonwebtoken": "^8.5.0", "@types/mongodb": "^3.6.9", "@types/mongoose": "^5.10.5", @@ -74,9 +76,9 @@ "jsonwebtoken": "^8.5.1", "missing-native-js-functions": "^1.2.10", "node-fetch": "^2.6.1", + "patch-package": "^6.4.7", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", - "ts-transform-json-schema": "^2.0.3", "typeorm": "^0.2.37", "typescript": "^4.3.5", "typescript-json-schema": "^0.50.1" @@ -1115,6 +1117,11 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@sqltools/formatter": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz", + "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==" + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -1282,6 +1289,101 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.1.tgz", + "integrity": "sha512-HTLpVXHrY69556ozYkcq47TtQJXpcWAWfkoqz+ZGz2JnmZhzlRjprCIyFnetSy8gpDWwTTGBcRVv1J1I1vBrHw==", + "dev": true, + "dependencies": { + "jest-diff": "^27.0.0", + "pretty-format": "^27.0.0" + } + }, + "node_modules/@types/jest/node_modules/@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@types/jest/node_modules/@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/jest/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@types/jest/node_modules/diff-sequences": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", + "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-diff": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.6.tgz", + "integrity": "sha512-Z1mqgkTCSYaFgwTlP/NUiRzdqgxmmhzHY1Tq17zL94morOHfHu3K4bgSgl+CR4GLhpV8VxkuOYuIWnQ9LnFqmg==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.0.6" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@types/jest/node_modules/jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/@types/jest/node_modules/pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "dependencies": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, "node_modules/@types/jsonwebtoken": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz", @@ -1425,6 +1527,11 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, + "node_modules/@types/zen-observable": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", + "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" + }, "node_modules/@zerollup/ts-helpers": { "version": "1.7.18", "resolved": "https://registry.npmjs.org/@zerollup/ts-helpers/-/ts-helpers-1.7.18.tgz", @@ -1667,7 +1774,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, "engines": { "node": ">=8" } @@ -1692,6 +1798,11 @@ "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=", "dev": true }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "node_modules/anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -1705,6 +1816,14 @@ "node": ">= 8" } }, + "node_modules/app-root-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", + "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==", + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -2116,7 +2235,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, "funding": [ { "type": "github", @@ -2927,6 +3045,111 @@ "node": ">=0.10.0" } }, + "node_modules/cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "dependencies": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "bin": { + "highlight": "bin/highlight" + }, + "engines": { + "node": ">=8.0.0", + "npm": ">=5.0.0" + } + }, + "node_modules/cli-highlight/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cli-highlight/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "node_modules/cli-highlight/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-highlight/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/cli-highlight/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cli-highlight/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, "node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -4114,8 +4337,7 @@ "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "node_modules/encodeurl": { "version": "1.0.2", @@ -4169,7 +4391,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, "engines": { "node": ">=6" } @@ -4183,7 +4404,6 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, "engines": { "node": ">=0.8.0" } @@ -4693,6 +4913,14 @@ "bser": "2.1.1" } }, + "node_modules/figlet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz", + "integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -4908,7 +5136,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, "engines": { "node": "6.* || 8.* || >= 10.*" } @@ -5035,7 +5262,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, "dependencies": { "ansi-regex": "^2.0.0" }, @@ -5047,7 +5273,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -5174,6 +5399,14 @@ "node": ">=10.0.0" } }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "engines": { + "node": "*" + } + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -5356,7 +5589,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, "funding": [ { "type": "github", @@ -7394,6 +7626,16 @@ "integrity": "sha512-nU7mOEuaXiQIB/EgTIjYZJ7g8KqMm2D8l4qp+DqA4jxWOb/tnb1KEoqp+tlbdQIDIAiC1i7j7X/3yHDFXLxr9g==", "dev": true }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/nanoassert": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", @@ -8004,6 +8246,14 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "node_modules/parent-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", + "integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/parents": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", @@ -8669,6 +8919,11 @@ "node": ">=8.10.0" } }, + "node_modules/reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "node_modules/regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", @@ -8720,7 +8975,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9190,6 +9444,11 @@ "node": ">=6" } }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "node_modules/saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -9315,7 +9574,6 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "dependencies": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -10119,7 +10377,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, "dependencies": { "ansi-regex": "^5.0.0" }, @@ -10339,6 +10596,25 @@ "node": ">=8" } }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", @@ -10776,45 +11052,269 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", - "dev": true, + "node_modules/typeorm": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.37.tgz", + "integrity": "sha512-7rkW0yCgFC24I5T0f3S/twmLSuccPh1SQmxET/oDWn2sSDVzbyWdnItSdKy27CdJGTlKHYtUVeOcMYw5LRsXVw==", + "dependencies": { + "@sqltools/formatter": "^1.2.2", + "app-root-path": "^3.0.0", + "buffer": "^6.0.3", + "chalk": "^4.1.0", + "cli-highlight": "^2.1.11", + "debug": "^4.3.1", + "dotenv": "^8.2.0", + "glob": "^7.1.6", + "js-yaml": "^4.0.0", + "mkdirp": "^1.0.4", + "reflect-metadata": "^0.1.13", + "sha.js": "^2.4.11", + "tslib": "^2.1.0", + "xml2js": "^0.4.23", + "yargonaut": "^1.1.4", + "yargs": "^17.0.1", + "zen-observable-ts": "^1.0.0" + }, "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "typeorm": "cli.js" }, - "engines": { - "node": ">=4.2.0" + "funding": { + "url": "https://opencollective.com/typeorm" + }, + "peerDependencies": { + "@sap/hana-client": "*", + "better-sqlite3": "*", + "hdb-pool": "*", + "ioredis": "*", + "mongodb": "^3.6.0", + "mssql": "*", + "mysql2": "*", + "oracledb": "*", + "pg": "*", + "pg-native": "*", + "pg-query-stream": "*", + "redis": "*", + "sql.js": "*", + "sqlite3": "*", + "typeorm-aurora-data-api-driver": "*" + }, + "peerDependenciesMeta": { + "@sap/hana-client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "hdb-pool": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "mongodb": { + "optional": true + }, + "mssql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "oracledb": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "pg-query-stream": { + "optional": true + }, + "redis": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "typeorm-aurora-data-api-driver": { + "optional": true + } } }, - "node_modules/umd": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", - "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", - "dev": true, - "bin": { - "umd": "bin/cli.js" - } + "node_modules/typeorm/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, - "node_modules/undeclared-identifiers": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", - "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", - "dev": true, + "node_modules/typeorm/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "acorn-node": "^1.3.0", - "dash-ast": "^1.0.0", - "get-assigned-identifiers": "^1.2.0", - "simple-concat": "^1.0.0", - "xtend": "^4.0.1" - }, - "bin": { - "undeclared-identifiers": "bin.js" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/union-value": { + "node_modules/typeorm/node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/typeorm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/typeorm/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/typeorm/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/typeorm/node_modules/string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/typeorm/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/typeorm/node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/typeorm/node_modules/yargs": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.1.1.tgz", + "integrity": "sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/typeorm/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "engines": { + "node": ">=10" + } + }, + "node_modules/typescript": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", + "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/umd": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz", + "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==", + "dev": true, + "bin": { + "umd": "bin/cli.js" + } + }, + "node_modules/undeclared-identifiers": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/undeclared-identifiers/-/undeclared-identifiers-1.1.3.tgz", + "integrity": "sha512-pJOW4nxjlmfwKApE4zvxLScM/njmwj/DiUBv7EabwE4O8kRUy+HIwxQtZLBPll/jx1LJyBcqNfB3/cpv9EZwOw==", + "dev": true, + "dependencies": { + "acorn-node": "^1.3.0", + "dash-ast": "^1.0.0", + "get-assigned-identifiers": "^1.2.0", + "simple-concat": "^1.0.0", + "xtend": "^4.0.1" + }, + "bin": { + "undeclared-identifiers": "bin.js" + } + }, + "node_modules/union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", @@ -11274,6 +11774,26 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -11299,6 +11819,66 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yargonaut": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz", + "integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==", + "dependencies": { + "chalk": "^1.1.1", + "figlet": "^1.1.1", + "parent-require": "^1.0.0" + } + }, + "node_modules/yargonaut/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargonaut/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargonaut/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargonaut/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yargonaut/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -11366,6 +11946,20 @@ "node": ">=6" } }, + "node_modules/zen-observable": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", + "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" + }, + "node_modules/zen-observable-ts": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", + "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", + "dependencies": { + "@types/zen-observable": "0.8.3", + "zen-observable": "0.8.15" + } + }, "node_modules/zip-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", @@ -11884,9 +12478,9 @@ "jsonwebtoken": "^8.5.1", "missing-native-js-functions": "^1.2.10", "node-fetch": "^2.6.1", + "patch-package": "^6.4.7", "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", - "ts-transform-json-schema": "^2.0.3", "typeorm": "^0.2.37", "typescript": "^4.3.5", "typescript-json-schema": "^0.50.1" @@ -12227,6 +12821,11 @@ "@sinonjs/commons": "^1.7.0" } }, + "@sqltools/formatter": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz", + "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==" + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -12391,6 +12990,82 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-27.0.1.tgz", + "integrity": "sha512-HTLpVXHrY69556ozYkcq47TtQJXpcWAWfkoqz+ZGz2JnmZhzlRjprCIyFnetSy8gpDWwTTGBcRVv1J1I1vBrHw==", + "dev": true, + "requires": { + "jest-diff": "^27.0.0", + "pretty-format": "^27.0.0" + }, + "dependencies": { + "@jest/types": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.6.tgz", + "integrity": "sha512-aSquT1qa9Pik26JK5/3rvnYb4bGtm1VFNesHKmNTwmPIgOrixvhL2ghIvFRNEpzy3gU+rUgjIF/KodbkFAl++g==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^16.0.0", + "chalk": "^4.0.0" + } + }, + "@types/yargs": { + "version": "16.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", + "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + }, + "diff-sequences": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.6.tgz", + "integrity": "sha512-ag6wfpBFyNXZ0p8pcuIDS//D8H062ZQJ3fzYxjpmeKjnz8W4pekL3AI8VohmyZmsWW2PWaHgjsmqR6L13101VQ==", + "dev": true + }, + "jest-diff": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.6.tgz", + "integrity": "sha512-Z1mqgkTCSYaFgwTlP/NUiRzdqgxmmhzHY1Tq17zL94morOHfHu3K4bgSgl+CR4GLhpV8VxkuOYuIWnQ9LnFqmg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^27.0.6", + "jest-get-type": "^27.0.6", + "pretty-format": "^27.0.6" + } + }, + "jest-get-type": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.6.tgz", + "integrity": "sha512-XTkK5exIeUbbveehcSR8w0bhH+c0yloW/Wpl+9vZrjzztCPWrxhHwkIFpZzCt71oRBsgxmuUfxEqOYoZI2macg==", + "dev": true + }, + "pretty-format": { + "version": "27.0.6", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.6.tgz", + "integrity": "sha512-8tGD7gBIENgzqA+UBzObyWqQ5B778VIFZA/S66cclyd5YkFLYs2Js7gxDKf0MXtTc9zcS7t1xhdfcElJ3YIvkQ==", + "dev": true, + "requires": { + "@jest/types": "^27.0.6", + "ansi-regex": "^5.0.0", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + } + } + } + }, "@types/jsonwebtoken": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.4.tgz", @@ -12534,6 +13209,11 @@ "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, + "@types/zen-observable": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz", + "integrity": "sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==" + }, "@zerollup/ts-helpers": { "version": "1.7.18", "resolved": "https://registry.npmjs.org/@zerollup/ts-helpers/-/ts-helpers-1.7.18.tgz", @@ -12721,8 +13401,7 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" }, "ansi-styles": { "version": "4.3.0", @@ -12738,6 +13417,11 @@ "integrity": "sha1-vgiVmQl7dKXJxKhKDNvNtivYeu8=", "dev": true }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, "anymatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", @@ -12748,6 +13432,11 @@ "picomatch": "^2.0.4" } }, + "app-root-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.0.0.tgz", + "integrity": "sha512-qMcx+Gy2UZynHjOHOIXPNvpf+9cjvk3cWrBBK7zg4gH9+clobJRb9NGzcT7mQTcV/6Gm/1WelUtqxVXnNlrwcw==" + }, "append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -13102,8 +13791,7 @@ "base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bcrypt": { "version": "5.0.1", @@ -13797,6 +14485,85 @@ } } }, + "cli-highlight": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/cli-highlight/-/cli-highlight-2.1.11.tgz", + "integrity": "sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==", + "requires": { + "chalk": "^4.0.0", + "highlight.js": "^10.7.1", + "mz": "^2.4.0", + "parse5": "^5.1.1", + "parse5-htmlparser2-tree-adapter": "^6.0.0", + "yargs": "^16.0.0" + }, + "dependencies": { + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "parse5": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", + "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + } + } + }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -14824,8 +15591,7 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, "encodeurl": { "version": "1.0.2", @@ -14869,8 +15635,7 @@ "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==" }, "escape-html": { "version": "1.0.3", @@ -14880,8 +15645,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "2.0.0", @@ -15298,6 +16062,11 @@ "bser": "2.1.1" } }, + "figlet": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.2.tgz", + "integrity": "sha512-WOn21V8AhyE1QqVfPIVxe3tupJacq1xGkPTB4iagT6o+P2cAgEOOwIxMftr4+ZCTI6d551ij9j61DFr0nsP2uQ==" + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -15470,8 +16239,7 @@ "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-package-type": { "version": "0.1.0", @@ -15562,7 +16330,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" }, @@ -15570,8 +16337,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" } } }, @@ -15676,6 +16442,11 @@ "resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz", "integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==" }, + "highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==" + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -15830,8 +16601,7 @@ "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "ignore": { "version": "5.1.8", @@ -17453,6 +18223,16 @@ "integrity": "sha512-nU7mOEuaXiQIB/EgTIjYZJ7g8KqMm2D8l4qp+DqA4jxWOb/tnb1KEoqp+tlbdQIDIAiC1i7j7X/3yHDFXLxr9g==", "dev": true }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "nanoassert": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/nanoassert/-/nanoassert-1.1.0.tgz", @@ -17936,6 +18716,11 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "dev": true }, + "parent-require": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz", + "integrity": "sha1-dGoWdjgIOoYLDu9nMssn7UbDKXc=" + }, "parents": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", @@ -18491,6 +19276,11 @@ "picomatch": "^2.2.1" } }, + "reflect-metadata": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" + }, "regenerator-runtime": { "version": "0.13.9", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", @@ -18532,8 +19322,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-from-string": { "version": "2.0.2", @@ -18884,6 +19673,11 @@ "sparse-bitfield": "^3.0.3" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -18994,7 +19788,6 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "requires": { "inherits": "^2.0.1", "safe-buffer": "^5.0.1" @@ -19699,7 +20492,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -19864,6 +20656,22 @@ "minimatch": "^3.0.4" } }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, "throat": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", @@ -20207,6 +21015,118 @@ "is-typedarray": "^1.0.0" } }, + "typeorm": { + "version": "0.2.37", + "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.2.37.tgz", + "integrity": "sha512-7rkW0yCgFC24I5T0f3S/twmLSuccPh1SQmxET/oDWn2sSDVzbyWdnItSdKy27CdJGTlKHYtUVeOcMYw5LRsXVw==", + "requires": { + "@sqltools/formatter": "^1.2.2", + "app-root-path": "^3.0.0", + "buffer": "^6.0.3", + "chalk": "^4.1.0", + "cli-highlight": "^2.1.11", + "debug": "^4.3.1", + "dotenv": "^8.2.0", + "glob": "^7.1.6", + "js-yaml": "^4.0.0", + "mkdirp": "^1.0.4", + "reflect-metadata": "^0.1.13", + "sha.js": "^2.4.11", + "tslib": "^2.1.0", + "xml2js": "^0.4.23", + "yargonaut": "^1.1.4", + "yargs": "^17.0.1", + "zen-observable-ts": "^1.0.0" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==" + }, + "yargs": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.1.1.tgz", + "integrity": "sha512-c2k48R0PwKIqKhPMWjeiF6y2xY/gPMUlro0sgxqXpbOIohWiLNXWslsootttv7E1e73QPAMQSg5FeySbVcpsPQ==", + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + } + }, + "yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==" + } + } + }, "typescript": { "version": "4.3.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", @@ -20623,6 +21543,20 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, "xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", @@ -20645,6 +21579,53 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "yargonaut": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/yargonaut/-/yargonaut-1.1.4.tgz", + "integrity": "sha512-rHgFmbgXAAzl+1nngqOcwEljqHGG9uUZoPjsdZEs1w5JW9RXYzrSvH/u70C1JE5qFi0qjsdhnUX/dJRpWqitSA==", + "requires": { + "chalk": "^1.1.1", + "figlet": "^1.1.1", + "parent-require": "^1.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + } + } + }, "yargs": { "version": "15.4.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", @@ -20699,6 +21680,20 @@ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "dev": true }, + "zen-observable": { + "version": "0.8.15", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.15.tgz", + "integrity": "sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==" + }, + "zen-observable-ts": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-1.1.0.tgz", + "integrity": "sha512-1h4zlLSqI2cRLPJUHJFL8bCWHhkpuXkF+dbGkRaWjgDIG26DmzyshUMrdV/rL3UnR+mhaX4fRq8LPouq0MYYIA==", + "requires": { + "@types/zen-observable": "0.8.3", + "zen-observable": "0.8.15" + } + }, "zip-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", diff --git a/api/package.json b/api/package.json index 7b2de011..1310d577 100644 --- a/api/package.json +++ b/api/package.json @@ -5,7 +5,7 @@ "main": "dist/Server.js", "types": "dist/Server.d.ts", "scripts": { - "test": "jest", + "test": "npm run build && jest", "test:watch": "jest --watch", "start": "npm run build && node dist/start", "build": "npx tsc -b .", @@ -35,6 +35,7 @@ "@types/bcrypt": "^5.0.0", "@types/express": "^4.17.9", "@types/i18next-node-fs-backend": "^2.1.0", + "@types/jest": "^27.0.1", "@types/jsonwebtoken": "^8.5.0", "@types/mongodb": "^3.6.9", "@types/mongoose": "^5.10.5", @@ -79,6 +80,7 @@ "mongoose-autopopulate": "^0.12.3", "mongoose-long": "^0.3.2", "multer": "^1.4.2", - "node-fetch": "^2.6.1" + "node-fetch": "^2.6.1", + "typeorm": "^0.2.37" } } diff --git a/api/scripts/setup_test.js b/api/scripts/setup_test.js new file mode 100644 index 00000000..4b692f7c --- /dev/null +++ b/api/scripts/setup_test.js @@ -0,0 +1 @@ +// TODO: start api diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index d080e498..8e2cd923 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -2,6 +2,7 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; import { FieldError } from "../util/instanceOf"; +// TODO: update with new body/typorm validation export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { if (!error) next(); diff --git a/api/src/routes/auth/login.ts b/api/src/routes/auth/login.ts index 579a097e..c0acad4e 100644 --- a/api/src/routes/auth/login.ts +++ b/api/src/routes/auth/login.ts @@ -21,9 +21,6 @@ router.post( async (req: Request, res: Response) => { const { login, password, captcha_key, undelete } = req.body; const email = adjustEmail(login); - const query: any[] = [{ phone: login }]; - if (email) query.push({ email }); - console.log(req.body, email); const config = Config.get(); @@ -41,11 +38,10 @@ router.post( // TODO: check captcha } - const user = await User.findOneOrFail( - { $or: query }, - { "data.hash": true, id: true, disabled: true, deleted: true, "settings.locale": true, "settings.theme": true } - ).catch((e) => { - console.log(e, query); + const user = await User.findOneOrFail({ + where: [{ phone: login }, { email: login }], + select: ["data", "id", "disabled", "deleted", "settings"] + }).catch((e) => { throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } }); }); diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index 1405e219..62b039d5 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -1,12 +1,12 @@ import { Request, Response, Router } from "express"; -import { trimSpecial, User, Snowflake, User, Config } from "@fosscord/util"; +import { trimSpecial, User, Snowflake, Config } from "@fosscord/util"; import bcrypt from "bcrypt"; import { check, Email, EMAIL_REGEX, FieldErrors, Length } from "../../util/instanceOf"; import "missing-native-js-functions"; import { generateToken } from "./login"; import { getIpAdress, IPAnalysis, isProxy } from "../../util/ipAddress"; import { HTTPError } from "lambert-server"; -import RateLimit from "../../middlewares/RateLimit"; +import { In } from "typeorm"; const router: Router = Router(); @@ -55,13 +55,13 @@ router.post( // TODO: check password strength // adjusted_email will be slightly modified version of the user supplied email -> e.g. protection against GMail Trick - let adjusted_email: string | null = adjustEmail(email); + let adjusted_email = adjustEmail(email); // adjusted_password will be the hash of the password - let adjusted_password: string = ""; + let adjusted_password = ""; // trim special uf8 control characters -> Backspace, Newline, ... - let adjusted_username: string = trimSpecial(username); + let adjusted_username = trimSpecial(username); // discriminator will be randomly generated let discriminator = ""; @@ -129,7 +129,7 @@ router.post( if (!register.allowMultipleAccounts) { // TODO: check if fingerprint was eligible generated - const exists = await User.findOneOrFail({ fingerprints: fingerprint }).catch((e) => {}); + const exists = await User.findOne({ where: { fingerprints: In(fingerprint) } }); if (exists) { throw FieldErrors({ @@ -164,12 +164,8 @@ router.post( // TODO: is there any better way to generate a random discriminator only once, without checking if it already exists in the mongodb database? for (let tries = 0; tries < 5; tries++) { discriminator = Math.randomIntBetween(1, 9999).toString().padStart(4, "0"); - try { - exists = await User.findOneOrFail({ discriminator, username: adjusted_username }, "id"); - } catch (error) { - // doesn't exist -> break - break; - } + exists = await User.findOne({ where: { discriminator, username: adjusted_username }, select: ["id"] }); + if (!exists) break; } if (exists) { @@ -185,35 +181,26 @@ router.post( // appearently discord doesn't save the date of birth and just calculate if nsfw is allowed // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false - const user: User = { + const user = await new User({ id: Snowflake.generate(), created_at: new Date(), username: adjusted_username, discriminator, - avatar: null, - accent_color: null, - banner: null, + avatar: undefined, + accent_color: undefined, + banner: undefined, bot: false, system: false, desktop: false, mobile: false, premium: true, premium_type: 2, - phone: null, + phone: undefined, bio: "", mfa_enabled: false, verified: false, disabled: false, deleted: false, - presence: { - activities: [], - client_status: { - desktop: undefined, - mobile: undefined, - web: undefined - }, - status: "offline" - }, email: adjusted_email, nsfw_allowed: true, // TODO: depending on age public_flags: 0n, @@ -221,10 +208,7 @@ router.post( guilds: [], data: { hash: adjusted_password, - valid_tokens_since: new Date(), - relationships: [], - connected_accounts: [], - fingerprints: [] + valid_tokens_since: new Date() }, settings: { afk_timeout: 300, @@ -234,10 +218,10 @@ router.post( contact_sync_enabled: false, convert_emoticons: false, custom_status: { - emoji_id: null, - emoji_name: null, - expires_at: null, - text: null + emoji_id: undefined, + emoji_name: undefined, + expires_at: undefined, + text: undefined }, default_guilds_restricted: false, detect_platform_accounts: true, @@ -265,16 +249,13 @@ router.post( timezone_offset: 0 // timezone_offset: // TODO: timezone from request } - }; - - // insert user into database - await new User(user).save(); + }).save(); return res.json({ token: await generateToken(user.id) }); } ); -export function adjustEmail(email: string): string | null { +export function adjustEmail(email: string): string | undefined { // body parser already checked if it is a valid email const parts = email.match(EMAIL_REGEX); // @ts-ignore diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/api/src/routes/guilds/#guild_id/channels.ts index b53c9a5a..7b0e94b6 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/api/src/routes/guilds/#guild_id/channels.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { Channel, toObject, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; +import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { ChannelModifySchema } from "../../../schema/Channel"; @@ -48,15 +48,19 @@ router.patch( if (x.parent_id) { opts.parent_id = x.parent_id; - const parent_channel = await Channel.findOneOrFail({ id: x.parent_id, guild_id }, { permission_overwrites: true }); + const parent_channel = await Channel.findOneOrFail({ + where: { id: x.parent_id, guild_id }, + select: ["permission_overwrites"] + }); if (x.lock_permissions) { opts.permission_overwrites = parent_channel.permission_overwrites; } } - const channel = await Channel.findOneOrFailAndUpdate({ id: x.id, guild_id }, opts, { new: true }); + await Channel.update({ guild_id, id: x.id }, opts); + const channel = await Channel.findOneOrFail({ guild_id, id: x.id }); - await emitEvent({ event: "CHANNEL_UPDATE", data: channel), channel_id: x.id, guild_id } as ChannelUpdateEvent; + await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent); }) ]); diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/api/src/routes/guilds/#guild_id/templates.ts index 13917dbd..e1d2f5fd 100644 --- a/api/src/routes/guilds/#guild_id/templates.ts +++ b/api/src/routes/guilds/#guild_id/templates.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { TemplateModel, Guild, getPermission, toObject, User, Snowflake } from "@fosscord/util"; +import { Guild, getPermission, Template } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { TemplateCreateSchema, TemplateModifySchema } from "../../../schema/Template"; import { check } from "../../../util/instanceOf"; @@ -7,22 +7,22 @@ import { generateCode } from "../../../util/String"; const router: Router = Router(); -const TemplateGuildProjection = { - name: true, - description: true, - region: true, - verification_level: true, - default_message_notifications: true, - explicit_content_filter: true, - preferred_locale: true, - afk_timeout: true, - roles: true, - channels: true, - afk_channel_id: true, - system_channel_id: true, - system_channel_flags: true, - icon_hash: true -}; +const TemplateGuildProjection: (keyof Guild)[] = [ + "name", + "description", + "region", + "verification_level", + "default_message_notifications", + "explicit_content_filter", + "preferred_locale", + "afk_timeout", + "roles", + "channels", + "afk_channel_id", + "system_channel_id", + "system_channel_flags", + "icon" +]; router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; @@ -34,14 +34,14 @@ router.get("/", async (req: Request, res: Response) => { router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }, TemplateGuildProjection); + const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); const exists = await Template.findOneOrFail({ id: guild_id }).catch((e) => {}); if (exists) throw new HTTPError("Template already exists", 400); - const template = await new TemplateModel({ + const template = await new Template({ ...req.body, code: generateCode(), creator_id: req.user_id, @@ -51,7 +51,7 @@ router.post("/", check(TemplateCreateSchema), async (req: Request, res: Response serialized_source_guild: guild }).save(); - res.json(template)).send(; + res.json(template); }); router.delete("/:code", async (req: Request, res: Response) => { @@ -61,37 +61,39 @@ router.delete("/:code", async (req: Request, res: Response) => { const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); - const template = await Template.findOneOrFailAndDelete({ + const template = await Template.delete({ code }); - res.send(template); + res.json(template); }); router.put("/:code", async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - const { code } = req.params; + // synchronizes the template + const { code, guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }, TemplateGuildProjection); + const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); - const template = await Template.findOneOrFailAndUpdate({ code }, { serialized_source_guild: guild }, { new: true }); + const template = await new Template({ code, serialized_source_guild: guild }).save(); - res.json(template)).send(; + res.json(template); }); router.patch("/:code", check(TemplateModifySchema), async (req: Request, res: Response) => { + // updates the template description const { guild_id } = req.params; const { code } = req.params; + const { name, description } = req.body; const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); - const template = await Template.findOneOrFailAndUpdate({ code }, { name: req.body.name, description: req.body.description }, { new: true }); + const template = await new Template({ code, name: name, description: description }).save(); - res.json(template)).send(; + res.json(template); }); export default router; diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index ad8b676b..b8c1012d 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -1,9 +1,8 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); -import { TemplateModel, Guild, toObject, User, Role, Snowflake, Guild, Config } from "@fosscord/util"; +import { Template, Guild, Role, Snowflake, Config, User } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { GuildTemplateCreateSchema } from "../../../schema/Guild"; -import { getPublicUser } from "../../../util/User"; import { check } from "../../../util/instanceOf"; import { addMember } from "../../../util/Member"; @@ -12,7 +11,7 @@ router.get("/:code", async (req: Request, res: Response) => { const template = await Template.findOneOrFail({ code: code }); - res.json(template)).send(; + res.json(template); }); router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res: Response) => { @@ -20,7 +19,7 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res const body = req.body as GuildTemplateCreateSchema; const { maxGuilds } = Config.get().limits.user; - const user = await getPublicUser(req.user_id, { guilds: true }); + const user = await User.getPublicUser(req.user_id, { guilds: true }); if (user.guilds.length >= maxGuilds) { throw new HTTPError(`Maximum number of guilds reached ${maxGuilds}`, 403); diff --git a/api/src/routes/invites/index.ts b/api/src/routes/invites/index.ts index e871af86..a488c44b 100644 --- a/api/src/routes/invites/index.ts +++ b/api/src/routes/invites/index.ts @@ -1,5 +1,5 @@ import { Router, Request, Response } from "express"; -import { getPermission, Guild, InviteModel, toObject } from "@fosscord/util"; +import { getPermission, Guild, Invite } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { addMember } from "../../util/Member"; const router: Router = Router(); @@ -40,7 +40,7 @@ router.delete("/:code", async (req: Request, res: Response) => { await Guild.update({ vanity_url_code: code }, { $unset: { vanity_url_code: 1 } }).catch((e) => {}); - res.status(200).send({ invite: invite) }; + res.json({ invite: invite }); }); export default router; diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index 46c96698..9b2e2d5e 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -7,7 +7,7 @@ router.get("/", async (req: Request, res: Response) => { const user = await getPublicUser(req.params.id, { data: true }); res.json({ - connected_accounts: user.data.connected_accounts, + connected_accounts: user.connected_accounts, premium_guild_since: null, // TODO premium_since: null, // TODO user: { diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 9b8d6199..8c7469ed 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -15,14 +15,14 @@ import { check, Length } from "../../../util/instanceOf"; const router = Router(); -const userProjection = { "user_data.relationships": true, ...PublicUserProjection }; +const userProjection = { "data.relationships": true, ...PublicUserProjection }; router.get("/", async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ id: req.user_id }, { user_data: { relationships: true } }).populate({ - path: "user_data.relationships.id", + const user = await User.findOneOrFail({ id: req.user_id }, { data: { relationships: true } }).populate({ + path: "data.relationships.id", model: User }); - return res.json(user.user_data.relationships); + return res.json(user.data.relationships); }); async function addRelationship(req: Request, res: Response, friend: UserDocument, type: RelationshipType) { @@ -30,8 +30,8 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); const user = await User.findOneOrFail({ id: req.user_id }, userProjection); - const newUserRelationships = [...user.user_data.relationships]; - const newFriendRelationships = [...friend.user_data.relationships]; + const newUserRelationships = [...user.data.relationships]; + const newFriendRelationships = [...friend.data.relationships]; var relationship = newUserRelationships.find((x) => x.id === id); const friendRequest = newFriendRelationships.find((x) => x.id === req.user_id); @@ -48,7 +48,7 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument if (friendRequest && friendRequest.type !== RelationshipType.blocked) { newFriendRelationships.remove(friendRequest); await Promise.all([ - User.update({ id: friend.id }, { "user_data.relationships": newFriendRelationships }), + User.update({ id: friend.id }, { "data.relationships": newFriendRelationships }), emitEvent({ event: "RELATIONSHIP_REMOVE", data: friendRequest, @@ -58,12 +58,12 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument } await Promise.all([ - User.update({ id: req.user_id }, { "user_data.relationships": newUserRelationships }), + User.update({ id: req.user_id }, { "data.relationships": newUserRelationships }), emitEvent({ event: "RELATIONSHIP_ADD", data: { ...relationship, - user: { ...friend, user_data: undefined } + user: { ...friend, data: undefined } }, user_id: req.user_id } as RelationshipAddEvent) @@ -91,13 +91,13 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument } else newUserRelationships.push(outgoing_relationship); await Promise.all([ - User.update({ id: req.user_id }, { "user_data.relationships": newUserRelationships }), - User.update({ id: friend.id }, { "user_data.relationships": newFriendRelationships }), + User.update({ id: req.user_id }, { "data.relationships": newUserRelationships }), + User.update({ id: friend.id }, { "data.relationships": newFriendRelationships }), emitEvent({ event: "RELATIONSHIP_ADD", data: { ...outgoing_relationship, - user: { ...friend, user_data: undefined } + user: { ...friend, data: undefined } }, user_id: req.user_id } as RelationshipAddEvent), @@ -106,7 +106,7 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument data: { ...incoming_relationship, should_notify: true, - user: { ...user, user_data: undefined } + user: { ...user, data: undefined } }, user_id: id } as RelationshipAddEvent) @@ -138,11 +138,11 @@ router.delete("/:id", async (req: Request, res: Response) => { const friend = await User.findOneOrFail({ id }, userProjection); if (!friend) throw new HTTPError("User not found", 404); - const relationship = user.user_data.relationships.find((x) => x.id === id); - const friendRequest = friend.user_data.relationships.find((x) => x.id === req.user_id); + const relationship = user.data.relationships.find((x) => x.id === id); + const friendRequest = friend.data.relationships.find((x) => x.id === req.user_id); if (relationship?.type === RelationshipType.blocked) { // unblock user - user.user_data.relationships.remove(relationship); + user.data.relationships.remove(relationship); await Promise.all([ user.save(), @@ -153,8 +153,8 @@ router.delete("/:id", async (req: Request, res: Response) => { if (!relationship || !friendRequest) throw new HTTPError("You are not friends with the user", 404); if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); - user.user_data.relationships.remove(relationship); - friend.user_data.relationships.remove(friendRequest); + user.data.relationships.remove(relationship); + friend.data.relationships.remove(friendRequest); await Promise.all([ user.save(), diff --git a/api/src/util/Channel.ts b/api/src/util/Channel.ts index a618d2df..f518aefd 100644 --- a/api/src/util/Channel.ts +++ b/api/src/util/Channel.ts @@ -57,7 +57,7 @@ export async function createChannel( recipient_ids: null }).save(); - await emitEvent({ event: "CHANNEL_CREATE", data: channel), guild_id: channel.guild_id } as ChannelCreateEvent; + await emitEvent({ event: "CHANNEL_CREATE", data: channel, guild_id: channel.guild_id } as ChannelCreateEvent); return channel; } diff --git a/api/src/util/User.ts b/api/src/util/User.ts deleted file mode 100644 index 4d9065c4..00000000 --- a/api/src/util/User.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { toObject, User, PublicUserProjection } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; - -export { PublicUserProjection }; - -export async function getPublicUser(user_id: string, additional_fields?: any) { - const user = await User.findOneOrFail( - { id: user_id }, - { - ...PublicUserProjection, - ...additional_fields - } - ); - if (!user) throw new HTTPError("User not found", 404); - return user; -} diff --git a/api/tests/routes/auth/login.ts b/api/tests/routes/auth/login.ts new file mode 100644 index 00000000..e1938b36 --- /dev/null +++ b/api/tests/routes/auth/login.ts @@ -0,0 +1 @@ +test("works", () => {}); -- cgit 1.5.1 From c9ff1774b435b5af72faa97386890b3cb659744c Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 29 Aug 2021 00:03:40 +0200 Subject: :sparkles: typeorm api rewrite done --- api/src/Server.ts | 23 +-- api/src/index.ts | 2 - api/src/middlewares/Authentication.ts | 2 +- api/src/routes/auth/register.ts | 56 ------ api/src/routes/channels/#channel_id/index.ts | 30 +-- api/src/routes/channels/#channel_id/invites.ts | 4 +- .../#channel_id/messages/#message_id/ack.ts | 2 +- .../#channel_id/messages/#message_id/index.ts | 28 +-- .../#channel_id/messages/#message_id/reactions.ts | 45 +++-- .../channels/#channel_id/messages/bulk-delete.ts | 7 +- .../routes/channels/#channel_id/messages/index.ts | 30 ++- api/src/routes/channels/#channel_id/permissions.ts | 29 +-- api/src/routes/channels/#channel_id/pins.ts | 76 +++---- api/src/routes/channels/#channel_id/typing.ts | 4 +- api/src/routes/channels/#channel_id/webhooks.ts | 2 +- api/src/routes/guilds/#guild_id/bans.ts | 71 ++++--- api/src/routes/guilds/#guild_id/delete.ts | 19 +- api/src/routes/guilds/#guild_id/index.ts | 39 ++-- api/src/routes/guilds/#guild_id/invites.ts | 2 +- .../guilds/#guild_id/members/#member_id/index.ts | 27 +-- .../guilds/#guild_id/members/#member_id/nick.ts | 5 +- .../members/#member_id/roles/#role_id/index.ts | 7 +- api/src/routes/guilds/#guild_id/members/index.ts | 21 +- api/src/routes/guilds/#guild_id/roles.ts | 68 +++---- api/src/routes/guilds/#guild_id/vanity-url.ts | 38 ++-- api/src/routes/guilds/#guild_id/welcome_screen.ts | 28 +-- api/src/routes/guilds/#guild_id/widget.json.ts | 95 ++------- api/src/routes/guilds/index.ts | 17 +- api/src/routes/guilds/templates/index.ts | 25 ++- api/src/routes/invites/index.ts | 19 +- api/src/routes/users/#id/index.ts | 5 +- api/src/routes/users/#id/profile.ts | 4 +- api/src/routes/users/@me/channels.ts | 23 +-- api/src/routes/users/@me/delete.ts | 13 +- api/src/routes/users/@me/disable.ts | 9 +- api/src/routes/users/@me/guilds.ts | 19 +- api/src/routes/users/@me/index.ts | 47 +++-- api/src/routes/users/@me/profile.ts | 6 +- api/src/routes/users/@me/relationships.ts | 85 ++++---- api/src/schema/Guild.ts | 80 ++------ api/src/schema/Message.ts | 8 +- api/src/schema/Roles.ts | 2 +- api/src/test/mongo_test.ts | 40 ---- api/src/test/server_benchmark.ts | 39 ---- api/src/test/test.ts | 3 - api/src/util/Channel.ts | 17 +- api/src/util/Member.ts | 220 --------------------- api/src/util/Message.ts | 19 +- 48 files changed, 467 insertions(+), 993 deletions(-) delete mode 100644 api/src/test/mongo_test.ts delete mode 100644 api/src/test/server_benchmark.ts delete mode 100644 api/src/test/test.ts delete mode 100644 api/src/util/Member.ts (limited to 'api/src/middlewares') diff --git a/api/src/Server.ts b/api/src/Server.ts index adaf7f2c..0f444f86 100644 --- a/api/src/Server.ts +++ b/api/src/Server.ts @@ -2,7 +2,7 @@ import "missing-native-js-functions"; import { Connection } from "mongoose"; import { Server, ServerOptions } from "lambert-server"; import { Authentication, CORS } from "./middlewares/"; -import { Config, db, initEvent } from "@fosscord/util"; +import { Config, initDatabase, initEvent } from "@fosscord/util"; import { ErrorHandler } from "./middlewares/ErrorHandler"; import { BodyParser } from "./middlewares/BodyParser"; import { Router, Request, Response, NextFunction } from "express"; @@ -31,30 +31,13 @@ export class FosscordServer extends Server { super({ ...opts, errorHandler: false, jsonBody: false }); } - async setupSchema() { - return Promise.all([ - db.collection("users").createIndex({ id: 1 }, { unique: true }), - db.collection("messages").createIndex({ id: 1 }, { unique: true }), - db.collection("channels").createIndex({ id: 1 }, { unique: true }), - db.collection("guilds").createIndex({ id: 1 }, { unique: true }), - db.collection("members").createIndex({ id: 1, guild_id: 1 }, { unique: true }), - db.collection("roles").createIndex({ id: 1 }, { unique: true }), - db.collection("emojis").createIndex({ id: 1 }, { unique: true }), - db.collection("invites").createIndex({ code: 1 }, { unique: true }), - db.collection("invites").createIndex({ expires_at: 1 }, { expireAfterSeconds: 0 }), // after 0 seconds of expires_at the invite will get delete - db.collection("ratelimits").createIndex({ expires_at: 1 }, { expireAfterSeconds: 0 }) - ]); - } - async start() { - // @ts-ignore - await (db as Promise); - await this.setupSchema(); + await initDatabase(); await Config.init(); await initEvent(); this.app.use(CORS); - this.app.use(BodyParser({ inflate: true, limit: 1024 * 1024 * 10 })); // 2MB + this.app.use(BodyParser({ inflate: true, limit: 1024 * 1024 * 10 })); // 10MB const app = this.app; const api = Router(); // @ts-ignore diff --git a/api/src/index.ts b/api/src/index.ts index 73b9dbd2..0bba7f5e 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -8,8 +8,6 @@ export * from "./schema/Message"; export * from "./util/Constants"; export * from "./util/instanceOf"; export * from "./util/instanceOf"; -export * from "./util/Member"; export * from "./util/RandomInviteID"; export * from "./util/String"; -export * from "./util/User"; export { check as checkPassword } from "./util/passwordStrength"; diff --git a/api/src/middlewares/Authentication.ts b/api/src/middlewares/Authentication.ts index a8bfe196..34a66a6b 100644 --- a/api/src/middlewares/Authentication.ts +++ b/api/src/middlewares/Authentication.ts @@ -28,7 +28,7 @@ declare global { export async function Authentication(req: Request, res: Response, next: NextFunction) { if (req.method === "OPTIONS") return res.sendStatus(204); const url = req.url.replace(API_PREFIX, ""); - if (url.startsWith("/invites") && req.method === "GET") return next(); // @ts-ignore + if (url.startsWith("/invites") && req.method === "GET") return next(); if ( NO_AUTHORIZATION_ROUTES.some((x) => { if (typeof x === "string") return url.startsWith(x); diff --git a/api/src/routes/auth/register.ts b/api/src/routes/auth/register.ts index 62b039d5..5ad6d6a4 100644 --- a/api/src/routes/auth/register.ts +++ b/api/src/routes/auth/register.ts @@ -182,72 +182,16 @@ router.post( // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false const user = await new User({ - id: Snowflake.generate(), - created_at: new Date(), username: adjusted_username, discriminator, - avatar: undefined, - accent_color: undefined, - banner: undefined, - bot: false, - system: false, - desktop: false, - mobile: false, premium: true, premium_type: 2, - phone: undefined, - bio: "", - mfa_enabled: false, - verified: false, - disabled: false, - deleted: false, email: adjusted_email, nsfw_allowed: true, // TODO: depending on age - public_flags: 0n, - flags: 0n, // TODO: generate default flags guilds: [], data: { hash: adjusted_password, valid_tokens_since: new Date() - }, - settings: { - afk_timeout: 300, - allow_accessibility_detection: true, - animate_emoji: true, - animate_stickers: 0, - contact_sync_enabled: false, - convert_emoticons: false, - custom_status: { - emoji_id: undefined, - emoji_name: undefined, - expires_at: undefined, - text: undefined - }, - default_guilds_restricted: false, - detect_platform_accounts: true, - developer_mode: false, - disable_games_tab: false, - enable_tts_command: true, - explicit_content_filter: 0, - friend_source_flags: { all: true }, - gateway_connected: false, - gif_auto_play: true, - guild_folders: [], - guild_positions: [], - inline_attachment_media: true, - inline_embed_media: true, - locale: req.language, - message_display_compact: false, - native_phone_integration_enabled: true, - render_embeds: true, - render_reactions: true, - restricted_guilds: [], - show_current_game: true, - status: "offline", - stream_notifications_enabled: true, - theme: "dark", - timezone_offset: 0 - // timezone_offset: // TODO: timezone from request } }).save(); diff --git a/api/src/routes/channels/#channel_id/index.ts b/api/src/routes/channels/#channel_id/index.ts index 91534602..4aa5a5b9 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/api/src/routes/channels/#channel_id/index.ts @@ -1,4 +1,4 @@ -import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, getPermission, GuildUpdateEvent, toObject } from "@fosscord/util"; +import { ChannelDeleteEvent, Channel, ChannelUpdateEvent, emitEvent, getPermission } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; import { ChannelModifySchema } from "../../../schema/Channel"; @@ -23,7 +23,7 @@ router.delete("/", async (req: Request, res: Response) => { const channel = await Channel.findOneOrFail({ id: channel_id }); - const permission = await getPermission(req.user_id, channel?.guild_id, channel_id, { channel }); + const permission = await getPermission(req.user_id, channel?.guild_id, channel_id); permission.hasThrow("MANAGE_CHANNELS"); // TODO: Dm channel "close" not delete @@ -31,7 +31,7 @@ router.delete("/", async (req: Request, res: Response) => { await emitEvent({ event: "CHANNEL_DELETE", data, channel_id } as ChannelDeleteEvent); - await Channel.deleteOne({ id: channel_id }); + await Channel.delete({ id: channel_id }); res.send(data); }); @@ -43,17 +43,19 @@ router.patch("/", check(ChannelModifySchema), async (req: Request, res: Response const permission = await getPermission(req.user_id, undefined, channel_id); permission.hasThrow("MANAGE_CHANNELS"); - const channel = await Channel.findOneOrFailAndUpdate({ id: channel_id }, payload, { new: true }); - - const data = channel; - - await emitEvent({ - event: "CHANNEL_UPDATE", - data, - channel_id - } as ChannelUpdateEvent); - - res.send(data); + const channel = await Channel.findOneOrFail({ id: channel_id }); + channel.assign(payload); + + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + data: channel, + channel_id + } as ChannelUpdateEvent) + ]); + + res.send(channel); }); export default router; diff --git a/api/src/routes/channels/#channel_id/invites.ts b/api/src/routes/channels/#channel_id/invites.ts index cc77df26..35006ac4 100644 --- a/api/src/routes/channels/#channel_id/invites.ts +++ b/api/src/routes/channels/#channel_id/invites.ts @@ -6,7 +6,7 @@ import { random } from "../../../util/RandomInviteID"; import { InviteCreateSchema } from "../../../schema/Invite"; -import { getPermission, Channel, InviteModel, InviteCreateEvent, toObject, emitEvent } from "@fosscord/util"; +import { getPermission, Channel, Invite, InviteCreateEvent, emitEvent } from "@fosscord/util"; const router: Router = Router(); @@ -38,7 +38,7 @@ router.post("/", check(InviteCreateSchema), async (req: Request, res: Response) inviter_id: user_id }; - await new InviteModel(invite).save(); + await new Invite(invite).save(); await emitEvent({ event: "INVITE_CREATE", data: invite, guild_id } as InviteCreateEvent); res.status(201).send(invite); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts index ff576d90..0fd5f2be 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts @@ -14,7 +14,7 @@ router.post("/", check({ $manual: Boolean, $mention_count: Number }), async (req const permission = await getPermission(req.user_id, undefined, channel_id); permission.hasThrow("VIEW_CHANNEL"); - await ReadState.update({ user_id: req.user_id, channel_id, message_id }, { user_id: req.user_id, channel_id, message_id }); + await ReadState.update({ user_id: req.user_id, channel_id }, { user_id: req.user_id, channel_id, last_message_id: message_id }); await emitEvent({ event: "MESSAGE_ACK", diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts index 058e2390..32478763 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/index.ts @@ -1,6 +1,5 @@ -import { Channel, emitEvent, getPermission, MessageDeleteEvent, Message, MessageUpdateEvent, toObject } from "@fosscord/util"; +import { Channel, emitEvent, getPermission, MessageDeleteEvent, Message, MessageUpdateEvent } from "@fosscord/util"; import { Router, Response, Request } from "express"; -import { HTTPError } from "lambert-server"; import { MessageCreateSchema } from "../../../../../schema/Message"; import { check } from "../../../../../util/instanceOf"; @@ -12,7 +11,7 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response const { message_id, channel_id } = req.params; var body = req.body as MessageCreateSchema; - var message = await Message.findOneOrFail({ id: message_id, channel_id }, { author_id: true, message_reference: true }); + const message = await Message.findOneOrFail({ id: message_id, channel_id }); const permissions = await getPermission(req.user_id, undefined, channel_id); @@ -29,15 +28,16 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response id: message_id, edited_timestamp: new Date() }); + message.assign(opts); - // @ts-ignore - message = await Message.findOneOrFailAndUpdate({ id: message_id }, opts, { new: true }).populate("author"); - - await emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: { ...message, nonce: undefined } - } as MessageUpdateEvent); + await Promise.all([ + message.save(), + await emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: { ...message, nonce: undefined } + } as MessageUpdateEvent) + ]); postHandleMessage(message); @@ -49,13 +49,13 @@ router.patch("/", check(MessageCreateSchema), async (req: Request, res: Response router.delete("/", async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); - const message = await Message.findOneOrFail({ id: message_id }, { author_id: true }); + const channel = await Channel.findOneOrFail({ id: channel_id }); + const message = await Message.findOneOrFail({ id: message_id }); const permission = await getPermission(req.user_id, channel.guild_id, channel_id); if (message.author_id !== req.user_id) permission.hasThrow("MANAGE_MESSAGES"); - await Message.deleteOne({ id: message_id }); + await Message.delete({ id: message_id }); await emitEvent({ event: "MESSAGE_DELETE", diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts index 9a6e4436..f60484b5 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts +++ b/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts @@ -1,7 +1,7 @@ import { Channel, emitEvent, - EmojiModel, + Emoji, getPermission, Member, Message, @@ -11,11 +11,11 @@ import { MessageReactionRemoveEvent, PartialEmoji, PublicUserProjection, - toObject, User } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; +import { In } from "typeorm"; const router = Router(); // TODO: check if emoji is really an unicode emoji or a prperly encoded external emoji @@ -38,12 +38,12 @@ function getEmoji(emoji: string): PartialEmoji { router.delete("/", async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); + const channel = await Channel.findOneOrFail({ id: channel_id }); const permissions = await getPermission(req.user_id, undefined, channel_id); permissions.hasThrow("MANAGE_MESSAGES"); - await Message.findOneOrFailAndUpdate({ id: message_id, channel_id }, { reactions: [] }, { new: true }); + await Message.update({ id: message_id, channel_id }, { reactions: [] }); await emitEvent({ event: "MESSAGE_REACTION_REMOVE_ALL", @@ -62,8 +62,6 @@ router.delete("/:emoji", async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); - const permissions = await getPermission(req.user_id, undefined, channel_id); permissions.hasThrow("MANAGE_MESSAGES"); @@ -73,18 +71,19 @@ router.delete("/:emoji", async (req: Request, res: Response) => { if (!already_added) throw new HTTPError("Reaction not found", 404); message.reactions.remove(already_added); - await Message.update({ id: message_id, channel_id }, message); - - await emitEvent({ - event: "MESSAGE_REACTION_REMOVE_EMOJI", - channel_id, - data: { + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_REACTION_REMOVE_EMOJI", channel_id, - message_id, - guild_id: channel.guild_id, - emoji - } - } as MessageReactionRemoveEmojiEvent); + data: { + channel_id, + message_id, + guild_id: message.guild_id, + emoji + } + } as MessageReactionRemoveEmojiEvent) + ]); res.sendStatus(204); }); @@ -94,14 +93,18 @@ router.get("/:emoji", async (req: Request, res: Response) => { const emoji = getEmoji(req.params.emoji); const message = await Message.findOneOrFail({ id: message_id, channel_id }); - if (!message) throw new HTTPError("Message not found", 404); const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); if (!reaction) throw new HTTPError("Reaction not found", 404); const permissions = await getPermission(req.user_id, undefined, channel_id); permissions.hasThrow("VIEW_CHANNEL"); - const users = await User.find({ id: { $in: reaction.user_ids } }, PublicUserProjection); + const users = await User.find({ + where: { + id: In(reaction.user_ids) + }, + select: PublicUserProjection + }); res.json(users); }); @@ -111,7 +114,7 @@ router.put("/:emoji/:user_id", async (req: Request, res: Response) => { if (user_id !== "@me") throw new HTTPError("Invalid user"); const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); + const channel = await Channel.findOneOrFail({ id: channel_id }); const message = await Message.findOneOrFail({ id: message_id, channel_id }); const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); @@ -156,7 +159,7 @@ router.delete("/:emoji/:user_id", async (req: Request, res: Response) => { const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true }); + const channel = await Channel.findOneOrFail({ id: channel_id }); const message = await Message.findOneOrFail({ id: message_id, channel_id }); const permissions = await getPermission(req.user_id, undefined, channel_id); diff --git a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts index 028a5d73..5c486676 100644 --- a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/api/src/routes/channels/#channel_id/messages/bulk-delete.ts @@ -3,6 +3,7 @@ import { Channel, Config, emitEvent, getPermission, MessageDeleteBulkEvent, Mess import { HTTPError } from "lambert-server"; import { check } from "../../../../util/instanceOf"; +import { In } from "typeorm"; const router: Router = Router(); @@ -13,10 +14,10 @@ export default router; // https://discord.com/developers/docs/resources/channel#bulk-delete-messages router.post("/", check({ messages: [String] }), async (req: Request, res: Response) => { const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }, { permission_overwrites: true, guild_id: true }); + const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400); - const permission = await getPermission(req.user_id, channel?.guild_id, channel_id, { channel }); + const permission = await getPermission(req.user_id, channel?.guild_id, channel_id); permission.hasThrow("MANAGE_MESSAGES"); const { maxBulkDelete } = Config.get().limits.message; @@ -25,7 +26,7 @@ router.post("/", check({ messages: [String] }), async (req: Request, res: Respon if (messages.length < 2) throw new HTTPError("You must at least specify 2 messages to bulk delete"); if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`); - await Message.deleteMany({ id: { $in: messages } }); + await Message.delete({ id: In(messages) }); await emitEvent({ event: "MESSAGE_DELETE_BULK", diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index b5c650bb..6307c022 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -1,5 +1,5 @@ import { Router, Response, Request } from "express"; -import { Attachment, Channel, ChannelType, getPermission, MessageDocument, Message, toObject } from "@fosscord/util"; +import { Attachment, Channel, ChannelType, getPermission, Message } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { MessageCreateSchema } from "../../../../schema/Message"; import { check, instanceOf, Length } from "../../../../util/instanceOf"; @@ -7,6 +7,7 @@ import multer from "multer"; import { Query } from "mongoose"; import { sendMessage } from "../../../../util/Message"; import { uploadFile } from "../../../../util/cdn"; +import { FindManyOptions, LessThan, MoreThan } from "typeorm"; const router: Router = Router(); @@ -55,24 +56,21 @@ router.get("/", async (req: Request, res: Response) => { permissions.hasThrow("VIEW_CHANNEL"); if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); - var query: Query; - if (after) query = Message.find({ channel_id, id: { $gt: after } }); - else if (before) query = Message.find({ channel_id, id: { $lt: before } }); - else if (around) - query = Message.find({ - channel_id, - id: { $gt: (BigInt(around) - BigInt(halfLimit)).toString(), $lt: (BigInt(around) + BigInt(halfLimit)).toString() } - }); - else { - query = Message.find({ channel_id }); - } + var query: FindManyOptions & { where: { id?: any } } = { order: { id: "DESC" }, take: limit, where: { channel_id } }; - query = query.sort({ id: -1 }); + if (after) query.where.id = MoreThan(after); + else if (before) query.where.id = LessThan(before); + else if (around) { + query.where.id = [ + MoreThan((BigInt(around) - BigInt(halfLimit)).toString()), + LessThan((BigInt(around) + BigInt(halfLimit)).toString()) + ]; + } - const messages = await query.limit(limit); + const messages = await Message.find(query); return res.json(messages).map((x) => { - (x.reactions || []).forEach((x) => { + (x.reactions || []).forEach((x: any) => { // @ts-ignore if ((x.user_ids || []).includes(req.user_id)) x.me = true; // @ts-ignore @@ -135,7 +133,7 @@ router.post("/", messageUpload.single("file"), async (req: Request, res: Respons embeds, channel_id, attachments, - edited_timestamp: null + edited_timestamp: undefined }); return res.send(data); diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/api/src/routes/channels/#channel_id/permissions.ts index 2990e35b..9c49542b 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/api/src/routes/channels/#channel_id/permissions.ts @@ -1,4 +1,4 @@ -import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, getPermission, Member, Role, toObject } from "@fosscord/util"; +import { Channel, ChannelPermissionOverwrite, ChannelUpdateEvent, emitEvent, getPermission, Member, Role } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; @@ -11,16 +11,16 @@ router.put("/:overwrite_id", check({ allow: String, deny: String, type: Number, const { channel_id, overwrite_id } = req.params; const body = req.body as { allow: bigint; deny: bigint; type: number; id: string }; - var channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true, permission_overwrites: true }); + var channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); permissions.hasThrow("MANAGE_ROLES"); if (body.type === 0) { - if (!(await Role.exists({ id: overwrite_id }))) throw new HTTPError("role not found", 404); + if (!(await Role.count({ id: overwrite_id }))) throw new HTTPError("role not found", 404); } else if (body.type === 1) { - if (!(await Member.exists({ id: overwrite_id }))) throw new HTTPError("user not found", 404); + if (!(await Member.count({ id: overwrite_id }))) throw new HTTPError("user not found", 404); } else throw new HTTPError("type not supported", 501); // @ts-ignore @@ -57,18 +57,19 @@ router.delete("/:overwrite_id", async (req: Request, res: Response) => { const permissions = await getPermission(req.user_id, undefined, channel_id); permissions.hasThrow("MANAGE_ROLES"); - const channel = await Channel.findOneOrFailAndUpdate( - { id: channel_id }, - { $pull: { permission_overwrites: { id: overwrite_id } } }, - { new: true } - ); + const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); - await emitEvent({ - event: "CHANNEL_UPDATE", - channel_id, - data: channel - } as ChannelUpdateEvent); + channel.permission_overwrites = channel.permission_overwrites.filter((x) => x.id === overwrite_id); + + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + channel_id, + data: channel + } as ChannelUpdateEvent) + ]); return res.sendStatus(204); }); diff --git a/api/src/routes/channels/#channel_id/pins.ts b/api/src/routes/channels/#channel_id/pins.ts index 3ed42ab4..d83e36ed 100644 --- a/api/src/routes/channels/#channel_id/pins.ts +++ b/api/src/routes/channels/#channel_id/pins.ts @@ -1,4 +1,4 @@ -import { Channel, ChannelPinsUpdateEvent, Config, emitEvent, getPermission, Message, MessageUpdateEvent, toObject } from "@fosscord/util"; +import { Channel, ChannelPinsUpdateEvent, Config, emitEvent, getPermission, Message, MessageUpdateEvent } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; @@ -6,35 +6,36 @@ const router: Router = Router(); router.put("/:message_id", async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }); - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); + + const message = await Message.findOneOrFail({ id: message_id }); + const permission = await getPermission(req.user_id, message.guild_id, channel_id); permission.hasThrow("VIEW_CHANNEL"); // * in dm channels anyone can pin messages -> only check for guilds - if (channel.guild_id) permission.hasThrow("MANAGE_MESSAGES"); + if (message.guild_id) permission.hasThrow("MANAGE_MESSAGES"); - const pinned_count = await Messagecount({ channel_id, pinned: true }); + const pinned_count = await Message.count({ channel_id, pinned: true }); const { maxPins } = Config.get().limits.channel; if (pinned_count >= maxPins) throw new HTTPError("Max pin count reached: " + maxPins); - await Message.update({ id: message_id }, { pinned: true }); - const message = await Message.findOneOrFail({ id: message_id }); - - await emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message - } as MessageUpdateEvent); + await Promise.all([ + Message.update({ id: message_id }, { pinned: true }), + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message + } as MessageUpdateEvent), - await emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { + emitEvent({ + event: "CHANNEL_PINS_UPDATE", channel_id, - guild_id: channel.guild_id, - last_pin_timestamp: undefined - } - } as ChannelPinsUpdateEvent); + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined + } + } as ChannelPinsUpdateEvent) + ]); res.sendStatus(204); }); @@ -48,23 +49,28 @@ router.delete("/:message_id", async (req: Request, res: Response) => { permission.hasThrow("VIEW_CHANNEL"); if (channel.guild_id) permission.hasThrow("MANAGE_MESSAGES"); - const message = await Message.findOneOrFailAndUpdate({ id: message_id }, { pinned: false }, { new: true }); + const message = await Message.findOneOrFail({ id: message_id }); + message.pinned = false; - await emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message - } as MessageUpdateEvent); + await Promise.all([ + message.save(), + + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message + } as MessageUpdateEvent), - await emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { + emitEvent({ + event: "CHANNEL_PINS_UPDATE", channel_id, - guild_id: channel.guild_id, - last_pin_timestamp: undefined - } - } as ChannelPinsUpdateEvent); + data: { + channel_id, + guild_id: channel.guild_id, + last_pin_timestamp: undefined + } + } as ChannelPinsUpdateEvent) + ]); res.sendStatus(204); }); diff --git a/api/src/routes/channels/#channel_id/typing.ts b/api/src/routes/channels/#channel_id/typing.ts index 298d2f50..2305e8e8 100644 --- a/api/src/routes/channels/#channel_id/typing.ts +++ b/api/src/routes/channels/#channel_id/typing.ts @@ -1,4 +1,4 @@ -import { Channel, emitEvent, Member, toObject, TypingStartEvent } from "@fosscord/util"; +import { Channel, emitEvent, Member, TypingStartEvent } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; @@ -17,7 +17,7 @@ router.post("/", async (req: Request, res: Response) => { channel_id: channel_id, data: { // this is the paylod - member: member, + member: { ...member, roles: member.role_ids }, channel_id, timestamp, user_id, diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/api/src/routes/channels/#channel_id/webhooks.ts index 38a8226b..775053ba 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/api/src/routes/channels/#channel_id/webhooks.ts @@ -10,7 +10,7 @@ const router: Router = Router(); // TODO: use Image Data Type for avatar instead of String router.post("/", check({ name: new Length(String, 1, 80), $avatar: String }), async (req: Request, res: Response) => { const channel_id = req.params.channel_id; - const channel = await Channel.findOneOrFail({ id: channel_id }, { guild_id: true, type: true }); + const channel = await Channel.findOneOrFail({ id: channel_id }); isTextChannel(channel.type); if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400); diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/api/src/routes/guilds/#guild_id/bans.ts index cbc0b0fa..b84a68a7 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/api/src/routes/guilds/#guild_id/bans.ts @@ -1,22 +1,17 @@ import { Request, Response, Router } from "express"; -import { BanModel, emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, toObject } from "@fosscord/util"; +import { emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { getIpAdress } from "../../../util/ipAddress"; import { BanCreateSchema } from "../../../schema/Ban"; import { check } from "../../../util/instanceOf"; -import { removeMember } from "../../../util/Member"; -import { getPublicUser } from "../../../util/User"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await Guild.exists({ id: guild_id }); - if (!guild) throw new HTTPError("Guild not found", 404); - - var bans = await Ban.find({ guild_id: guild_id }, { user_id: true, reason: true }); + var bans = await Ban.find({ guild_id: guild_id }); return res.json(bans); }); @@ -32,29 +27,32 @@ router.put("/:user_id", check(BanCreateSchema), async (req: Request, res: Respon const { guild_id } = req.params; const banned_user_id = req.params.user_id; - const banned_user = await getPublicUser(banned_user_id); + const banned_user = await User.getPublicUser(banned_user_id); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("BAN_MEMBERS"); if (req.user_id === banned_user_id) throw new HTTPError("You can't ban yourself", 400); + if (perms.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); - await removeMember(banned_user_id, guild_id); - - const ban = await new BanModel({ + const ban = new Ban({ user_id: banned_user_id, guild_id: guild_id, ip: getIpAdress(req), executor_id: req.user_id, reason: req.body.reason // || otherwise empty - }).save(); + }); - await emitEvent({ - event: "GUILD_BAN_ADD", - data: { - guild_id: guild_id, - user: banned_user - }, - guild_id: guild_id - } as GuildBanAddEvent); + await Promise.all([ + Member.removeFromGuild(banned_user_id, guild_id), + ban.save(), + emitEvent({ + event: "GUILD_BAN_ADD", + data: { + guild_id: guild_id, + user: banned_user + }, + guild_id: guild_id + } as GuildBanAddEvent) + ]); return res.json(ban); }); @@ -63,26 +61,25 @@ router.delete("/:user_id", async (req: Request, res: Response) => { var { guild_id } = req.params; var banned_user_id = req.params.user_id; - const banned_user = await getPublicUser(banned_user_id); - const guild = await Guild.exists({ id: guild_id }); - if (!guild) throw new HTTPError("Guild not found", 404); - + const banned_user = await User.getPublicUser(banned_user_id); const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("BAN_MEMBERS"); - await Ban.deleteOne({ - user_id: banned_user_id, - guild_id - }); - - await emitEvent({ - event: "GUILD_BAN_REMOVE", - data: { - guild_id, - user: banned_user - }, - guild_id - } as GuildBanRemoveEvent); + await Promise.all([ + Ban.delete({ + user_id: banned_user_id, + guild_id + }), + + emitEvent({ + event: "GUILD_BAN_REMOVE", + data: { + guild_id, + user: banned_user + }, + guild_id + } as GuildBanRemoveEvent) + ]); return res.status(204).send(); }); diff --git a/api/src/routes/guilds/#guild_id/delete.ts b/api/src/routes/guilds/#guild_id/delete.ts index a53271ce..043260e9 100644 --- a/api/src/routes/guilds/#guild_id/delete.ts +++ b/api/src/routes/guilds/#guild_id/delete.ts @@ -1,4 +1,4 @@ -import { Channel, emitEvent, EmojiModel, GuildDeleteEvent, Guild, InviteModel, Member, Message, Role, User } from "@fosscord/util"; +import { Channel, emitEvent, GuildDeleteEvent, Guild, Member, Message, Role, Invite, Emoji } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; @@ -9,7 +9,7 @@ const router = Router(); router.post("/", async (req: Request, res: Response) => { var { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }, "owner_id"); + const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] }); if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401); await emitEvent({ @@ -21,14 +21,13 @@ router.post("/", async (req: Request, res: Response) => { } as GuildDeleteEvent); await Promise.all([ - Guild.deleteOne({ id: guild_id }), - User.updateMany({ guilds: guild_id }, { $pull: { guilds: guild_id } }), - Role.deleteMany({ guild_id }), - Channel.deleteMany({ guild_id }), - Emoji.deleteMany({ guild_id }), - Invite.deleteMany({ guild_id }), - Message.deleteMany({ guild_id }), - Member.deleteMany({ guild_id }) + Guild.delete({ id: guild_id }), + Role.delete({ guild_id }), + Channel.delete({ guild_id }), + Emoji.delete({ guild_id }), + Invite.delete({ guild_id }), + Message.delete({ guild_id }), + Member.delete({ guild_id }) ]); return res.sendStatus(204); diff --git a/api/src/routes/guilds/#guild_id/index.ts b/api/src/routes/guilds/#guild_id/index.ts index af9ea9d6..d205b164 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/api/src/routes/guilds/#guild_id/index.ts @@ -1,19 +1,5 @@ import { Request, Response, Router } from "express"; -import { - Channel, - emitEvent, - EmojiModel, - getPermission, - GuildDeleteEvent, - Guild, - GuildUpdateEvent, - InviteModel, - Member, - Message, - Role, - toObject, - User -} from "@fosscord/util"; +import { emitEvent, getPermission, Guild, GuildUpdateEvent, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { GuildUpdateSchema } from "../../../schema/Guild"; @@ -26,9 +12,15 @@ const router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }).populate({ path: "joined_at", match: { id: req.user_id } }); - const member = await Member.exists({ guild_id: guild_id, id: req.user_id }); - if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401); + const [guild, member_count, member] = await Promise.all([ + Guild.findOneOrFail({ id: guild_id }), + Member.count({ guild_id: guild_id, id: req.user_id }), + Member.findOneOrFail(req.user_id) + ]); + if (!member_count) throw new HTTPError("You are not a member of the guild you are trying to access", 401); + + // @ts-ignore + guild.joined_at = member?.joined_at; return res.json(guild); }); @@ -45,15 +37,12 @@ router.patch("/", check(GuildUpdateSchema), async (req: Request, res: Response) if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner); if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash); - const guild = await Guild.findOneOrFailAndUpdate({ id: guild_id }, body, { new: true }).populate({ - path: "joined_at", - match: { id: req.user_id } - }); - const data = guild; + const guild = await Guild.findOneOrFail({ id: guild_id }); + guild.assign(body); - emitEvent({ event: "GUILD_UPDATE", data: data, guild_id } as GuildUpdateEvent); + await Promise.all([guild.save(), emitEvent({ event: "GUILD_UPDATE", data: guild, guild_id } as GuildUpdateEvent)]); - return res.json(data); + return res.json(guild); }); export default router; diff --git a/api/src/routes/guilds/#guild_id/invites.ts b/api/src/routes/guilds/#guild_id/invites.ts index ca72cce8..1843b689 100644 --- a/api/src/routes/guilds/#guild_id/invites.ts +++ b/api/src/routes/guilds/#guild_id/invites.ts @@ -1,4 +1,4 @@ -import { getPermission, InviteModel, toObject } from "@fosscord/util"; +import { getPermission, Invite } from "@fosscord/util"; import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts index 1dacbdad..db29cd08 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/index.ts @@ -3,7 +3,6 @@ import { Guild, Member, User, - toObject, GuildMemberAddEvent, getPermission, PermissionResolvable, @@ -12,15 +11,15 @@ import { emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { addMember, isMember, removeMember } from "../../../../../util/Member"; import { check } from "../../../../../util/instanceOf"; import { MemberChangeSchema } from "../../../../../schema/Member"; +import { In } from "typeorm"; const router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id, member_id } = req.params; - await isMember(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); const member = await Member.findOneOrFail({ id: member_id, guild_id }); @@ -31,18 +30,22 @@ router.patch("/", check(MemberChangeSchema), async (req: Request, res: Response) const { guild_id, member_id } = req.params; const body = req.body as MemberChangeSchema; if (body.roles) { - const roles = await Role.find({ id: { $in: body.roles } }); + const roles = await Role.find({ id: In(body.roles) }); if (body.roles.length !== roles.length) throw new HTTPError("Roles not found", 404); // TODO: check if user has permission to add role } - const member = await Member.findOneOrFailAndUpdate({ id: member_id, guild_id }, body, { new: true }); + const member = await Member.findOneOrFail({ id: member_id, guild_id }); + member.assign(req.body); - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - guild_id, - data: member - } as GuildMemberUpdateEvent); + Promise.all([ + member.save(), + emitEvent({ + event: "GUILD_MEMBER_UPDATE", + guild_id, + data: { ...member, roles: member.role_ids } + } as GuildMemberUpdateEvent) + ]); res.json(member); }); @@ -52,7 +55,7 @@ router.put("/", async (req: Request, res: Response) => { throw new HTTPError("Maintenance: Currently you can't add a member", 403); // TODO: only for oauth2 applications - await addMember(member_id, guild_id); + await Member.addToGuild(member_id, guild_id); res.sendStatus(204); }); @@ -62,7 +65,7 @@ router.delete("/", async (req: Request, res: Response) => { const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("KICK_MEMBERS"); - await removeMember(member_id, guild_id); + await Member.removeFromGuild(member_id, guild_id); res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts b/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts index e4308364..3f2975e6 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts @@ -1,8 +1,7 @@ -import { getPermission, PermissionResolvable } from "@fosscord/util"; +import { getPermission, Member, PermissionResolvable } from "@fosscord/util"; import { Request, Response, Router } from "express"; import { check } from "lambert-server"; import { MemberNickChangeSchema } from "../../../../../schema/Member"; -import { changeNickname } from "../../../../../util/Member"; const router = Router(); @@ -17,7 +16,7 @@ router.patch("/", check(MemberNickChangeSchema), async (req: Request, res: Respo const perms = await getPermission(req.user_id, guild_id); perms.hasThrow(permissionString); - await changeNickname(member_id, guild_id, req.body.nick); + await Member.changeNickname(member_id, guild_id, req.body.nick); res.status(200).send(); }); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts index fad0695e..cb9bad9a 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts +++ b/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts @@ -1,6 +1,5 @@ -import { getPermission } from "@fosscord/util"; +import { getPermission, Member } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { addRole, removeRole } from "../../../../../../../util/Member"; const router = Router(); @@ -10,7 +9,7 @@ router.delete("/:member_id/roles/:role_id", async (req: Request, res: Response) const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_ROLES"); - await removeRole(member_id, guild_id, role_id); + await Member.removeRole(member_id, guild_id, role_id); res.sendStatus(204); }); @@ -20,7 +19,7 @@ router.put("/:member_id/roles/:role_id", async (req: Request, res: Response) => const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_ROLES"); - await addRole(member_id, guild_id, role_id); + await Member.addRole(member_id, guild_id, role_id); res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/api/src/routes/guilds/#guild_id/members/index.ts index 656d3acd..0bfd71cb 100644 --- a/api/src/routes/guilds/#guild_id/members/index.ts +++ b/api/src/routes/guilds/#guild_id/members/index.ts @@ -1,8 +1,7 @@ import { Request, Response, Router } from "express"; -import { Guild, Member, toObject } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; +import { Guild, Member, PublicMemberProjection } from "@fosscord/util"; import { instanceOf, Length } from "../../../../util/instanceOf"; -import { PublicMemberProjection, isMember } from "../../../../util/Member"; +import { MoreThan } from "typeorm"; const router = Router(); @@ -11,7 +10,7 @@ const router = Router(); router.get("/", async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ id: guild_id }); - await isMember(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); try { instanceOf({ $limit: new Length(Number, 1, 1000), $after: String }, req.query, { @@ -23,12 +22,16 @@ router.get("/", async (req: Request, res: Response) => { return res.status(400).json({ code: 50035, message: "Invalid Query", success: false, errors: error }); } - // @ts-ignore - if (!req.query.limit) req.query.limit = 1; - const { limit, after } = (req.query) as { limit: number; after: string }; - const query = after ? { id: { $gt: after } } : {}; + const { limit, after } = (req.query) as { limit?: number; after?: string }; + const query = after ? { id: MoreThan(after) } : {}; + + const members = await Member.find({ + where: { guild_id, ...query }, + select: PublicMemberProjection, + take: limit || 1, + order: { id: "ASC" } + }); - var members = await Member.find({ guild_id, ...query }, PublicMemberProjection).limit(limit); return res.json(members); }); diff --git a/api/src/routes/guilds/#guild_id/roles.ts b/api/src/routes/guilds/#guild_id/roles.ts index 5ebc0580..e9e777b9 100644 --- a/api/src/routes/guilds/#guild_id/roles.ts +++ b/api/src/routes/guilds/#guild_id/roles.ts @@ -1,10 +1,7 @@ import { Request, Response, Router } from "express"; import { Role, - Guild, getPermission, - toObject, - User, Snowflake, Member, GuildRoleCreateEvent, @@ -16,15 +13,13 @@ import { HTTPError } from "lambert-server"; import { check } from "../../../util/instanceOf"; import { RoleModifySchema } from "../../../schema/Roles"; -import { getPublicUser } from "../../../util/User"; -import { isMember } from "../../../util/Member"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { const guild_id = req.params.guild_id; - await isMember(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); const roles = await Role.find({ guild_id: guild_id }); @@ -35,12 +30,8 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => const guild_id = req.params.guild_id; const body = req.body as RoleModifySchema; - const guild = await Guild.findOneOrFail({ id: guild_id }, { id: true }); - const user = await User.findOneOrFail({ id: req.user_id }); - const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_ROLES"); - if (!body.name) throw new HTTPError("You need to specify a name"); const role = await new Role({ ...body, @@ -49,7 +40,7 @@ router.post("/", check(RoleModifySchema), async (req: Request, res: Response) => managed: false, position: 0, tags: null, - permissions: body.permissions || 0n + permissions: perms.bitfield & (body.permissions || 0n) }).save(); await emitEvent({ @@ -72,19 +63,20 @@ router.delete("/:role_id", async (req: Request, res: Response) => { const permissions = await getPermission(req.user_id, guild_id); permissions.hasThrow("MANAGE_ROLES"); - await Role.deleteOne({ - id: role_id, - guild_id: guild_id - }); - - await emitEvent({ - event: "GUILD_ROLE_DELETE", - guild_id, - data: { + await Promise.all([ + Role.delete({ + id: role_id, + guild_id: guild_id + }), + emitEvent({ + event: "GUILD_ROLE_DELETE", guild_id, - role_id - } - } as GuildRoleDeleteEvent); + data: { + guild_id, + role_id + } + } as GuildRoleDeleteEvent) + ]); res.sendStatus(204); }); @@ -96,30 +88,22 @@ router.patch("/:role_id", check(RoleModifySchema), async (req: Request, res: Res const { role_id } = req.params; const body = req.body as RoleModifySchema; - const guild = await Guild.findOneOrFail({ id: guild_id }, { id: true }); - const user = await User.findOneOrFail({ id: req.user_id }); - const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_ROLES"); - const role = await Role.findOneOrFailAndUpdate( - { - id: role_id, - guild_id: guild_id - }, - // @ts-ignore - body, - { new: true } - ); + const role = new Role({ ...body, role_id, guild_id, permissions: perms.bitfield & (body.permissions || 0n) }); - await emitEvent({ - event: "GUILD_ROLE_UPDATE", - guild_id, - data: { + await Promise.all([ + role.save(), + emitEvent({ + event: "GUILD_ROLE_UPDATE", guild_id, - role - } - } as GuildRoleUpdateEvent); + data: { + guild_id, + role + } + } as GuildRoleUpdateEvent) + ]); res.json(role); }); diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/api/src/routes/guilds/#guild_id/vanity-url.ts index 335cea27..58940b42 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/api/src/routes/guilds/#guild_id/vanity-url.ts @@ -1,8 +1,7 @@ -import { Channel, ChannelType, getPermission, Guild, InviteModel, trimSpecial } from "@fosscord/util"; +import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util"; import { Router, Request, Response } from "express"; import { HTTPError } from "lambert-server"; import { check, Length } from "../../../util/instanceOf"; -import { isMember } from "../../../util/Member"; const router = Router(); @@ -14,42 +13,37 @@ router.get("/", async (req: Request, res: Response) => { const permission = await getPermission(req.user_id, guild_id); permission.hasThrow("MANAGE_GUILD"); - const guild = await Guild.findOneOrFail({ id: guild_id }); - if (!guild.vanity_url_code) return res.json({ code: null }); - const { uses } = await Invite.findOneOrFail({ code: guild.vanity_url_code }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id }, relations: ["vanity_url"] }); + if (!guild.vanity_url) return res.json({ code: null }); - return res.json({ code: guild.vanity_url_code, uses }); + return res.json({ code: guild.vanity_url_code, uses: guild.vanity_url.uses }); }); // TODO: check if guild is elgible for vanity url router.patch("/", check({ code: new Length(String, 0, 20) }), async (req: Request, res: Response) => { const { guild_id } = req.params; - var code = req.body.code.replace(InviteRegex); - if (!code) code = null; + const code = req.body.code.replace(InviteRegex); + + await Invite.findOneOrFail({ code }); const guild = await Guild.findOneOrFail({ id: guild_id }); - const permission = await getPermission(req.user_id, guild_id, undefined, { guild }); + const permission = await getPermission(req.user_id, guild_id); permission.hasThrow("MANAGE_GUILD"); - const alreadyExists = await Promise.all([ - Guild.findOneOrFail({ vanity_url_code: code }).catch(() => null), - Invite.findOneOrFail({ code: code }).catch(() => null) - ]); - if (alreadyExists.some((x) => x)) throw new HTTPError("Vanity url already exists", 400); - - await Guild.update({ id: guild_id }, { vanity_url_code: code }); const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT }); - await Invite.update( - { code: guild.vanity_url_code }, - { + guild.vanity_url_code = code; + + Promise.all([ + guild.save(), + Invite.delete({ code: guild.vanity_url_code }), + new Invite({ code: code, uses: 0, created_at: new Date(), guild_id, channel_id: id - }, - { upsert: true } - ); + }).save() + ]); return res.json({ code: code }); }); diff --git a/api/src/routes/guilds/#guild_id/welcome_screen.ts b/api/src/routes/guilds/#guild_id/welcome_screen.ts index b457efb6..defbcd40 100644 --- a/api/src/routes/guilds/#guild_id/welcome_screen.ts +++ b/api/src/routes/guilds/#guild_id/welcome_screen.ts @@ -1,11 +1,9 @@ import { Request, Response, Router } from "express"; -import { Guild, getPermission, toObject, Snowflake } from "@fosscord/util"; +import { Guild, getPermission, Snowflake, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { check } from "../../../util/instanceOf"; -import { isMember } from "../../../util/Member"; -import { GuildAddChannelToWelcomeScreenSchema } from "../../../schema/Guild"; -import { getPublicUser } from "../../../util/User"; +import { GuildUpdateWelcomeScreenSchema } from "../../../schema/Guild"; const router: Router = Router(); @@ -14,34 +12,24 @@ router.get("/", async (req: Request, res: Response) => { const guild = await Guild.findOneOrFail({ id: guild_id }); - await isMember(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); res.json(guild.welcome_screen); }); -router.post("/", check(GuildAddChannelToWelcomeScreenSchema), async (req: Request, res: Response) => { +router.patch("/", check(GuildUpdateWelcomeScreenSchema), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; - const body = req.body as GuildAddChannelToWelcomeScreenSchema; + const body = req.body as GuildUpdateWelcomeScreenSchema; const guild = await Guild.findOneOrFail({ id: guild_id }); - var channelObject = { - ...body - }; - const perms = await getPermission(req.user_id, guild_id); perms.hasThrow("MANAGE_GUILD"); if (!guild.welcome_screen.enabled) throw new HTTPError("Welcome screen disabled", 400); - if (guild.welcome_screen.welcome_channels.some((channel) => channel.channel_id === body.channel_id)) - throw new Error("Welcome Channel exists"); - - await Guild.findOneOrFailAndUpdate( - { - id: guild_id - }, - { $push: { "welcome_screen.welcome_channels": channelObject } } - ); + if (body.welcome_channels) guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid + if (body.description) guild.welcome_screen.description = body.description; + if (body.enabled != null) guild.welcome_screen.enabled = body.enabled; res.sendStatus(204); }); diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/api/src/routes/guilds/#guild_id/widget.json.ts index 10bc3ac0..ae1f0599 100644 --- a/api/src/routes/guilds/#guild_id/widget.json.ts +++ b/api/src/routes/guilds/#guild_id/widget.json.ts @@ -1,5 +1,5 @@ import { Request, Response, Router } from "express"; -import { Config, Permissions, Guild, InviteModel, Channel, Member } from "@fosscord/util"; +import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { random } from "../../../util/RandomInviteID"; @@ -21,7 +21,8 @@ router.get("/", async (req: Request, res: Response) => { if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404); // Fetch existing widget invite for widget channel - var invite = await Invite.findOneOrFail({ channel_id: guild.widget_channel_id, inviter_id: { $type: 10 } }); + var invite = await Invite.findOne({ channel_id: guild.widget_channel_id }); + if (guild.widget_channel_id && !invite) { // Create invite for channel if none exists // TODO: Refactor invite create code to a shared function @@ -40,85 +41,29 @@ router.get("/", async (req: Request, res: Response) => { inviter_id: null }; - invite = await new InviteModel(body).save(); + invite = await new Invite(body).save(); } // Fetch voice channels, and the @everyone permissions object - let channels: any[] = []; - await Channel.find({ guild_id: guild_id, type: 2 }, { permission_overwrites: { $elemMatch: { id: guild_id } } }) - .select("id name position permission_overwrites") - .sort({ position: 1 }) - .cursor() - .eachAsync((doc) => { - // Only return channels where @everyone has the CONNECT permission - if ( - doc.permission_overwrites === undefined || - Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) === Permissions.FLAGS.CONNECT - ) { - channels.push({ - id: doc.id, - name: doc.name, - position: doc.position - }); - } - }); + const channels = [] as any[]; + + (await Channel.find({ where: { guild_id: guild_id, type: 2 }, order: { position: "ASC" } })).filter((doc) => { + // Only return channels where @everyone has the CONNECT permission + if ( + doc.permission_overwrites === undefined || + Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) === Permissions.FLAGS.CONNECT + ) { + channels.push({ + id: doc.id, + name: doc.name, + position: doc.position + }); + } + }); // Fetch members // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file) - let members: any[] = []; - await Member.find({ guild_id: guild_id }) - .populate({ path: "user", select: { _id: 0, username: 1, avatar: 1, presence: 1 } }) - .select("id user nick deaf mute") - .cursor() - .eachAsync((doc) => { - const status = doc.user?.presence?.status || "offline"; - if (status == "offline") return; - - let item = {}; - - item = { - ...item, - id: null, // this is updated during the sort outside of the query - username: doc.nick || doc.user?.username, - discriminator: "0000", // intended (https://github.com/discord/discord-api-docs/issues/1287) - avatar: null, // intended, avatar_url below will return a unique guild + user url to the avatar - status: status - }; - - const activity = doc.user?.presence?.activities?.[0]; - if (activity) { - item = { - ...item, - game: { name: activity.name } - }; - } - - // TODO: If the member is in a voice channel, return extra widget details - // Extra fields returned include deaf, mute, self_deaf, self_mute, supress, and channel_id (voice channel connected to) - // Get this from VoiceState - - // TODO: Implement a widget-avatar endpoint on the CDN, and implement logic here to request it - // Get unique avatar url for guild user, cdn to serve the actual avatar image on this url - /* - const avatar = doc.user?.avatar; - if (avatar) { - const CDN_HOST = Config.get().cdn.endpoint || "http://localhost:3003"; - const avatar_url = "/widget-avatars/" + ; - item = { - ...item, - avatar_url: avatar_url - } - } - */ - - members.push(item); - }); - - // Sort members, and update ids (Unable to do under the mongoose query due to https://mongoosejs.com/docs/faq.html#populate_sort_order) - members = members.sort((first, second) => 0 - (first.username > second.username ? -1 : 1)); - members.forEach((x, i) => { - x.id = i; - }); + let members = await Member.find({ where: { guild_id: guild_id } }); // Construct object to respond with const data = { diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index 05be07d9..c158c7d4 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -1,10 +1,8 @@ import { Router, Request, Response } from "express"; -import { Role, Guild, Snowflake, Guild, RoleDocument, Config } from "@fosscord/util"; +import { Role, Guild, Snowflake, Config, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { check } from "./../../util/instanceOf"; import { GuildCreateSchema } from "../../schema/Guild"; -import { getPublicUser } from "../../util/User"; -import { addMember } from "../../util/Member"; import { createChannel } from "../../util/Channel"; const router: Router = Router(); @@ -15,14 +13,13 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = const body = req.body as GuildCreateSchema; const { maxGuilds } = Config.get().limits.user; - const user = await getPublicUser(req.user_id, { guilds: true }); - - if (user.guilds.length >= maxGuilds) { + const guild_count = await Member.count({ where: { id: req.user_id } }); + if (guild_count >= maxGuilds) { throw new HTTPError(`Maximum number of guilds reached ${maxGuilds}`, 403); } const guild_id = Snowflake.generate(); - const guild: Guild = { + const guild = new Guild({ name: body.name, region: Config.get().regions.default, owner_id: req.user_id, @@ -62,7 +59,7 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = }, widget_channel_id: undefined, widget_enabled: false - }; + }); const [guild_doc, role] = await Promise.all([ new Guild(guild).save(), @@ -94,14 +91,14 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = body.channels?.map((x) => { var id = ids.get(x.id) || Snowflake.generate(); - // TODO: should we abort if parent_id is a category? (or not to allow sub category channels) + // TODO: should we abort if parent_id is a category? (to disallow sub category channels) var parent_id = ids.get(x.parent_id); return createChannel({ ...x, guild_id, id, parent_id }, req.user_id, { keepId: true, skipExistsCheck: true }); }) ); - await addMember(req.user_id, guild_id); + await Member.addToGuild(req.user_id, guild_id); res.status(201).json({ id: guild.id }); }); diff --git a/api/src/routes/guilds/templates/index.ts b/api/src/routes/guilds/templates/index.ts index b8c1012d..7a8ac886 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/api/src/routes/guilds/templates/index.ts @@ -1,10 +1,9 @@ import { Request, Response, Router } from "express"; const router: Router = Router(); -import { Template, Guild, Role, Snowflake, Config, User } from "@fosscord/util"; +import { Template, Guild, Role, Snowflake, Config, User, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { GuildTemplateCreateSchema } from "../../../schema/Guild"; import { check } from "../../../util/instanceOf"; -import { addMember } from "../../../util/Member"; router.get("/:code", async (req: Request, res: Response) => { const { code } = req.params; @@ -19,9 +18,9 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res const body = req.body as GuildTemplateCreateSchema; const { maxGuilds } = Config.get().limits.user; - const user = await User.getPublicUser(req.user_id, { guilds: true }); - if (user.guilds.length >= maxGuilds) { + const guild_count = await Member.count({ id: req.user_id }); + if (guild_count >= maxGuilds) { throw new HTTPError(`Maximum number of guilds reached ${maxGuilds}`, 403); } @@ -29,15 +28,13 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res const guild_id = Snowflake.generate(); - const guild: Guild = { - ...body, - ...template.serialized_source_guild, - id: guild_id, - owner_id: req.user_id - }; - - const [guild_doc, role] = await Promise.all([ - new Guild(guild).save(), + const [guild, role] = await Promise.all([ + new Guild({ + ...body, + ...template.serialized_source_guild, + id: guild_id, + owner_id: req.user_id + }).save(), new Role({ id: guild_id, guild_id: guild_id, @@ -52,7 +49,7 @@ router.post("/:code", check(GuildTemplateCreateSchema), async (req: Request, res }).save() ]); - await addMember(req.user_id, guild_id, { guild: guild_doc }); + await Member.addToGuild(req.user_id, guild_id); res.status(201).json({ id: guild.id }); }); diff --git a/api/src/routes/invites/index.ts b/api/src/routes/invites/index.ts index a488c44b..23fa3aec 100644 --- a/api/src/routes/invites/index.ts +++ b/api/src/routes/invites/index.ts @@ -1,14 +1,12 @@ import { Router, Request, Response } from "express"; -import { getPermission, Guild, Invite } from "@fosscord/util"; +import { getPermission, Guild, Invite, Member } from "@fosscord/util"; import { HTTPError } from "lambert-server"; -import { addMember } from "../../util/Member"; const router: Router = Router(); router.get("/:code", async (req: Request, res: Response) => { const { code } = req.params; const invite = await Invite.findOneOrFail({ code }); - if (!invite) throw new HTTPError("Unknown Invite", 404); res.status(200).send(invite); }); @@ -16,11 +14,11 @@ router.get("/:code", async (req: Request, res: Response) => { router.post("/:code", async (req: Request, res: Response) => { const { code } = req.params; - const invite = await Invite.findOneOrFailAndUpdate({ code }, { $inc: { uses: 1 } }, { new: true }); - if (!invite) throw new HTTPError("Unknown Invite", 404); - if (invite.uses >= invite.max_uses) await Invite.deleteOne({ code }); + const invite = await Invite.findOneOrFail({ code }); + if (invite.uses++ >= invite.max_uses) await Invite.delete({ code }); + else await invite.save(); - await addMember(req.user_id, invite.guild_id); + await Member.addToGuild(req.user_id, invite.guild_id); res.status(200).send(invite); }); @@ -30,15 +28,12 @@ router.delete("/:code", async (req: Request, res: Response) => { const invite = await Invite.findOneOrFail({ code }); const { guild_id, channel_id } = invite; - const guild = await Guild.findOneOrFail({ id: guild_id }); - const permission = await getPermission(req.user_id, guild_id, channel_id, { guild }); + const permission = await getPermission(req.user_id, guild_id, channel_id); if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS")) throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401); - await Invite.deleteOne({ code }); - - await Guild.update({ vanity_url_code: code }, { $unset: { vanity_url_code: 1 } }).catch((e) => {}); + await Promise.all([Invite.delete({ code }), Guild.update({ vanity_url_code: code }, { vanity_url_code: undefined })]); res.json({ invite: invite }); }); diff --git a/api/src/routes/users/#id/index.ts b/api/src/routes/users/#id/index.ts index a2ad3ae6..3841756b 100644 --- a/api/src/routes/users/#id/index.ts +++ b/api/src/routes/users/#id/index.ts @@ -1,13 +1,12 @@ import { Router, Request, Response } from "express"; -import { getPublicUser } from "../../../util/User"; -import { HTTPError } from "lambert-server"; +import { User } from "../../../../../util/dist"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { const { id } = req.params; - res.json(await getPublicUser(id)); + res.json(await User.getPublicUser(id)); }); export default router; diff --git a/api/src/routes/users/#id/profile.ts b/api/src/routes/users/#id/profile.ts index 9b2e2d5e..afccfed5 100644 --- a/api/src/routes/users/#id/profile.ts +++ b/api/src/routes/users/#id/profile.ts @@ -1,10 +1,10 @@ import { Router, Request, Response } from "express"; -import { getPublicUser } from "../../../util/User"; +import { User } from "../../../../../util/dist"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - const user = await getPublicUser(req.params.id, { data: true }); + const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] }); res.json({ connected_accounts: user.connected_accounts, diff --git a/api/src/routes/users/@me/channels.ts b/api/src/routes/users/@me/channels.ts index 28e77dd9..ab203571 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/api/src/routes/users/@me/channels.ts @@ -1,20 +1,10 @@ import { Router, Request, Response } from "express"; -import { - Channel, - ChannelCreateEvent, - toObject, - ChannelType, - Snowflake, - trimSpecial, - Channel, - DMChannel, - User, - emitEvent -} from "@fosscord/util"; +import { Channel, ChannelCreateEvent, ChannelType, Snowflake, trimSpecial, User, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import { DmChannelCreateSchema } from "../../../schema/Channel"; import { check } from "../../../util/instanceOf"; +import { In } from "typeorm"; const router: Router = Router(); @@ -29,8 +19,10 @@ router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Respons body.recipients = body.recipients.filter((x) => x !== req.user_id).unique(); - if (!(await Promise.all(body.recipients.map((x) => User.exists({ id: x })))).every((x) => x)) { - throw new HTTPError("Recipient not found"); + const recipients = await User.find({ id: In(body.recipients) }); + + if (recipients.length !== body.recipients.length) { + throw new HTTPError("Recipient/s not found"); } const type = body.recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM; @@ -40,13 +32,12 @@ router.post("/", check(DmChannelCreateSchema), async (req: Request, res: Respons name, type, owner_id: req.user_id, - id: Snowflake.generate(), created_at: new Date(), last_message_id: null, recipient_ids: [...body.recipients, req.user_id] }).save(); - await emitEvent({ event: "CHANNEL_CREATE", data: channel), user_id: req.user_id } as ChannelCreateEvent; + await emitEvent({ event: "CHANNEL_CREATE", data: channel, user_id: req.user_id } as ChannelCreateEvent); res.json(channel); }); diff --git a/api/src/routes/users/@me/delete.ts b/api/src/routes/users/@me/delete.ts index fa8134cc..6bfe0b93 100644 --- a/api/src/routes/users/@me/delete.ts +++ b/api/src/routes/users/@me/delete.ts @@ -5,12 +5,19 @@ const router = Router(); router.post("/", async (req: Request, res: Response) => { const user = await User.findOneOrFail({ id: req.user_id }); //User object + let correctpass = true; + + if (user.data.hash) { + // guest accounts can delete accounts without password + correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/ + } + + // TODO: decrement guild member count - let correctpass = await bcrypt.compare(req.body.password, user!.data.hash); //Not sure if user typed right password :/ if (correctpass) { await Promise.all([ - User.deleteOne({ id: req.user_id }), //Yeetus user deletus - Member.deleteMany({ id: req.user_id }) + User.delete({ id: req.user_id }), //Yeetus user deletus + Member.delete({ id: req.user_id }) ]); res.sendStatus(204); diff --git a/api/src/routes/users/@me/disable.ts b/api/src/routes/users/@me/disable.ts index a40c9e59..ed1dedcc 100644 --- a/api/src/routes/users/@me/disable.ts +++ b/api/src/routes/users/@me/disable.ts @@ -5,9 +5,14 @@ import bcrypt from "bcrypt"; const router = Router(); router.post("/", async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ id: req.user_id }); //User object + const user = await User.findOneOrFail(req.user_id); //User object + let correctpass = true; + + if (user.data.hash) { + // guest accounts can delete accounts without password + correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/ + } - let correctpass = await bcrypt.compare(req.body.password, user!.data.hash); //Not sure if user typed right password :/ if (correctpass) { await User.update({ id: req.user_id }, { disabled: true }); diff --git a/api/src/routes/users/@me/guilds.ts b/api/src/routes/users/@me/guilds.ts index e40bfec9..fb88281b 100644 --- a/api/src/routes/users/@me/guilds.ts +++ b/api/src/routes/users/@me/guilds.ts @@ -1,31 +1,26 @@ import { Router, Request, Response } from "express"; -import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, toObject, emitEvent } from "@fosscord/util"; +import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, emitEvent } from "@fosscord/util"; import { HTTPError } from "lambert-server"; - -import { getPublicUser } from "../../../util/User"; +import { In } from "typeorm"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ id: req.user_id }, { guilds: true }); - if (!user) throw new HTTPError("User not found", 404); + const members = await Member.find({ relations: ["guild"], where: { id: req.user_id } }); - var guildIDs = user.guilds || []; - var guild = await Guild.find({ id: { $in: guildIDs } }).populate({ path: "joined_at", match: { id: req.user_id } }); - res.json(guild); + res.json(members.map((x) => x.guild)); }); // user send to leave a certain guild router.delete("/:id", async (req: Request, res: Response) => { const guild_id = req.params.id; - const guild = await Guild.findOneOrFail({ id: guild_id }, { guild_id: true }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] }); if (!guild) throw new HTTPError("Guild doesn't exist", 404); if (guild.owner_id === req.user_id) throw new HTTPError("You can't leave your own guild", 400); await Promise.all([ - Member.deleteOne({ id: req.user_id, guild_id: guild_id }), - User.update({ id: req.user_id }, { $pull: { guilds: guild_id } }), + Member.delete({ id: req.user_id, guild_id: guild_id }), emitEvent({ event: "GUILD_DELETE", data: { @@ -35,7 +30,7 @@ router.delete("/:id", async (req: Request, res: Response) => { } as GuildDeleteEvent) ]); - const user = await getPublicUser(req.user_id); + const user = await User.getPublicUser(req.user_id); await emitEvent({ event: "GUILD_MEMBER_REMOVE", diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 903b24aa..274cfb24 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -1,6 +1,5 @@ import { Router, Request, Response } from "express"; -import { User, toObject, PublicUserProjection } from "@fosscord/util"; -import { getPublicUser } from "../../../util/User"; +import { User } from "@fosscord/util"; import { UserModifySchema } from "../../../schema/User"; import { check } from "../../../util/instanceOf"; import { handleFile } from "../../../util/cdn"; @@ -8,29 +7,29 @@ import { handleFile } from "../../../util/cdn"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - res.json(await getPublicUser(req.user_id)); + res.json(await User.getPublicUser(req.user_id)); }); -const UserUpdateProjection = { - accent_color: true, - avatar: true, - banner: true, - bio: true, - bot: true, - discriminator: true, - email: true, - flags: true, - id: true, - locale: true, - mfa_enabled: true, - nsfw_alllowed: true, - phone: true, - public_flags: true, - purchased_flags: true, - // token: true, // this isn't saved in the db and needs to be set manually - username: true, - verified: true -}; +const UserUpdateProjection = [ + "accent_color", + "avatar", + "banner", + "bio", + "bot", + "discriminator", + "email", + "flags", + "id", + "locale", + "mfa_enabled", + "nsfw_alllowed", + "phone", + "public_flags", + "purchased_flags", + // "token", // this isn't saved in the db and needs to be set manually + "username", + "verified" +]; router.patch("/", check(UserModifySchema), async (req: Request, res: Response) => { const body = req.body as UserModifySchema; @@ -38,7 +37,7 @@ router.patch("/", check(UserModifySchema), async (req: Request, res: Response) = if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); - const user = await User.findOneOrFailAndUpdate({ id: req.user_id }, body, { projection: UserUpdateProjection, new: true }); + const user = await new User({ ...body }, { id: req.user_id }).save(); // TODO: dispatch user update event res.json(user); diff --git a/api/src/routes/users/@me/profile.ts b/api/src/routes/users/@me/profile.ts index fdb969dc..5ba03c68 100644 --- a/api/src/routes/users/@me/profile.ts +++ b/api/src/routes/users/@me/profile.ts @@ -1,13 +1,13 @@ import { Router, Request, Response } from "express"; -import { getPublicUser } from "../../../util/User"; +import { User } from "../../../../../util/dist"; const router: Router = Router(); router.get("/", async (req: Request, res: Response) => { - const user = await getPublicUser(req.user_id, { data: true }); + const user = await User.getPublicUser(req.user_id, { relations: ["connected_accounts"] }); res.json({ - connected_accounts: user.data.connected_accounts, + connected_accounts: user.connected_accounts, premium_guild_since: null, // TODO premium_since: null, // TODO user: { diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 8c7469ed..1a89b110 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -2,11 +2,10 @@ import { RelationshipAddEvent, User, PublicUserProjection, - toObject, RelationshipType, RelationshipRemoveEvent, - UserDocument, - emitEvent + emitEvent, + Relationship } from "@fosscord/util"; import { Router, Response, Request } from "express"; import { HTTPError } from "lambert-server"; @@ -15,40 +14,36 @@ import { check, Length } from "../../../util/instanceOf"; const router = Router(); -const userProjection = { "data.relationships": true, ...PublicUserProjection }; +const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection]; router.get("/", async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ id: req.user_id }, { data: { relationships: true } }).populate({ - path: "data.relationships.id", - model: User - }); - return res.json(user.data.relationships); + const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["relationships"] }); + + return res.json(user.relationships); }); -async function addRelationship(req: Request, res: Response, friend: UserDocument, type: RelationshipType) { +async function updateRelationship(req: Request, res: Response, friend: User, type: RelationshipType) { const id = friend.id; if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); - const user = await User.findOneOrFail({ id: req.user_id }, userProjection); - const newUserRelationships = [...user.data.relationships]; - const newFriendRelationships = [...friend.data.relationships]; + const user = await User.findOneOrFail(req.user_id, { relations: ["relationships"], select: userProjection }); - var relationship = newUserRelationships.find((x) => x.id === id); - const friendRequest = newFriendRelationships.find((x) => x.id === req.user_id); + var relationship = user.relationships.find((x) => x.id === id); + const friendRequest = friend.relationships.find((x) => x.id === req.user_id); if (type === RelationshipType.blocked) { if (relationship) { if (relationship.type === RelationshipType.blocked) throw new HTTPError("You already blocked the user"); relationship.type = RelationshipType.blocked; } else { - relationship = { id, type: RelationshipType.blocked }; - newUserRelationships.push(relationship); + relationship = new Relationship({ id, type: RelationshipType.blocked }); + user.relationships.push(relationship); } if (friendRequest && friendRequest.type !== RelationshipType.blocked) { - newFriendRelationships.remove(friendRequest); + friend.relationships.remove(friendRequest); await Promise.all([ - User.update({ id: friend.id }, { "data.relationships": newFriendRelationships }), + user.save(), emitEvent({ event: "RELATIONSHIP_REMOVE", data: friendRequest, @@ -58,12 +53,12 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument } await Promise.all([ - User.update({ id: req.user_id }, { "data.relationships": newUserRelationships }), + user.save(), emitEvent({ event: "RELATIONSHIP_ADD", data: { ...relationship, - user: { ...friend, data: undefined } + user: { ...friend } }, user_id: req.user_id } as RelationshipAddEvent) @@ -72,32 +67,31 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument return res.sendStatus(204); } - var incoming_relationship = { id: req.user_id, nickname: undefined, type: RelationshipType.incoming }; - var outgoing_relationship = { id, nickname: undefined, type: RelationshipType.outgoing }; + var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming }, { id: req.user_id }); + var outgoing_relationship = new Relationship({ nickname: undefined, type: RelationshipType.outgoing }, { id }); if (friendRequest) { if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); // accept friend request - // @ts-ignore incoming_relationship = friendRequest; incoming_relationship.type = RelationshipType.friends; outgoing_relationship.type = RelationshipType.friends; - } else newFriendRelationships.push(incoming_relationship); + } else friend.relationships.push(incoming_relationship); if (relationship) { if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request"); if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request"); if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); - } else newUserRelationships.push(outgoing_relationship); + } else user.relationships.push(outgoing_relationship); await Promise.all([ - User.update({ id: req.user_id }, { "data.relationships": newUserRelationships }), - User.update({ id: friend.id }, { "data.relationships": newFriendRelationships }), + user.save(), + friend.save(), emitEvent({ event: "RELATIONSHIP_ADD", data: { ...outgoing_relationship, - user: { ...friend, data: undefined } + user: { ...friend } }, user_id: req.user_id } as RelationshipAddEvent), @@ -106,7 +100,7 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument data: { ...incoming_relationship, should_notify: true, - user: { ...user, data: undefined } + user: { ...user } }, user_id: id } as RelationshipAddEvent) @@ -116,14 +110,23 @@ async function addRelationship(req: Request, res: Response, friend: UserDocument } router.put("/:id", check({ $type: new Length(Number, 1, 4) }), async (req: Request, res: Response) => { - return await addRelationship(req, res, await User.findOneOrFail({ id: req.params.id }), req.body.type); + return await updateRelationship( + req, + res, + await User.findOneOrFail(req.params.id, { relations: ["relationships"], select: userProjection }), + req.body.type + ); }); router.post("/", check({ discriminator: String, username: String }), async (req: Request, res: Response) => { - return await addRelationship( + return await updateRelationship( req, res, - await User.findOneOrFail(req.body as { discriminator: string; username: string }), + await User.findOneOrFail({ + relations: ["relationships"], + select: userProjection, + where: req.body as { discriminator: string; username: string } + }), req.body.type ); }); @@ -132,17 +135,15 @@ router.delete("/:id", async (req: Request, res: Response) => { const { id } = req.params; if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend"); - const user = await User.findOneOrFail({ id: req.user_id }); - if (!user) throw new HTTPError("Invalid token", 400); + const user = await User.findOneOrFail(req.user_id, { select: userProjection, relations: ["relationships"] }); + const friend = await User.findOneOrFail(id, { select: userProjection, relations: ["relationships"] }); - const friend = await User.findOneOrFail({ id }, userProjection); - if (!friend) throw new HTTPError("User not found", 404); + const relationship = user.relationships.find((x) => x.id === id); + const friendRequest = friend.relationships.find((x) => x.id === req.user_id); - const relationship = user.data.relationships.find((x) => x.id === id); - const friendRequest = friend.data.relationships.find((x) => x.id === req.user_id); if (relationship?.type === RelationshipType.blocked) { // unblock user - user.data.relationships.remove(relationship); + user.relationships.remove(relationship); await Promise.all([ user.save(), @@ -153,8 +154,8 @@ router.delete("/:id", async (req: Request, res: Response) => { if (!relationship || !friendRequest) throw new HTTPError("You are not friends with the user", 404); if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); - user.data.relationships.remove(relationship); - friend.data.relationships.remove(friendRequest); + user.relationships.remove(relationship); + friend.relationships.remove(friendRequest); await Promise.all([ user.save(), diff --git a/api/src/schema/Guild.ts b/api/src/schema/Guild.ts index e5971baf..01690ae9 100644 --- a/api/src/schema/Guild.ts +++ b/api/src/schema/Guild.ts @@ -1,4 +1,4 @@ -import { ChannelSchema, GuildChannel } from "@fosscord/util"; +import { Channel } from "@fosscord/util"; import { Length } from "../util/instanceOf"; import { ChannelModifySchema } from "./Channel"; @@ -59,54 +59,6 @@ export interface GuildUpdateSchema extends Omit { preferred_locale?: string; } -export const GuildGetSchema = { - id: true, - name: true, - icon: true, - splash: true, - discovery_splash: true, - owner: true, - owner_id: true, - permissions: true, - region: true, - afk_channel_id: true, - afk_timeout: true, - widget_enabled: true, - widget_channel_id: true, - verification_level: true, - default_message_notifications: true, - explicit_content_filter: true, - roles: true, - emojis: true, - features: true, - mfa_level: true, - application_id: true, - system_channel_id: true, - system_channel_flags: true, - rules_channel_id: true, - joined_at: true, - // large: true, - // unavailable: true, - member_count: true, - // voice_states: true, - // members: true, - // channels: true, - // presences: true, - max_presences: true, - max_members: true, - vanity_url_code: true, - description: true, - banner: true, - premium_tier: true, - premium_subscription_count: true, - preferred_locale: true, - public_updates_channel_id: true, - max_video_channel_users: true, - approximate_member_count: true, - approximate_presence_count: true - // welcome_screen: true, -}; - export const GuildTemplateCreateSchema = { name: String, $avatar: String @@ -117,16 +69,26 @@ export interface GuildTemplateCreateSchema { avatar?: string; } -export const GuildAddChannelToWelcomeScreenSchema = { - channel_id: String, - description: String, - $emoji_id: String, - emoji_name: String +export const GuildUpdateWelcomeScreenSchema = { + $welcome_channels: [ + { + channel_id: String, + description: String, + $emoji_id: String, + emoji_name: String + } + ], + $enabled: Boolean, + $description: new Length(String, 0, 140) }; -export interface GuildAddChannelToWelcomeScreenSchema { - channel_id: string; - description: string; - emoji_id?: string; - emoji_name: string; +export interface GuildUpdateWelcomeScreenSchema { + welcome_channels?: { + channel_id: string; + description: string; + emoji_id?: string; + emoji_name: string; + }[]; + enabled?: boolean; + description?: string; } diff --git a/api/src/schema/Message.ts b/api/src/schema/Message.ts index 2dd54f0c..f9bfcc67 100644 --- a/api/src/schema/Message.ts +++ b/api/src/schema/Message.ts @@ -1,6 +1,12 @@ -import { Embed, EmbedImage } from "@fosscord/util"; +import { Embed } from "@fosscord/util"; import { Length } from "../util/instanceOf"; +export const EmbedImage = { + $url: String, + $width: Number, + $height: Number +}; + export const MessageCreateSchema = { $content: new Length(String, 0, 2000), $nonce: String, diff --git a/api/src/schema/Roles.ts b/api/src/schema/Roles.ts index f662e61b..1e5f560f 100644 --- a/api/src/schema/Roles.ts +++ b/api/src/schema/Roles.ts @@ -9,7 +9,7 @@ export const RoleModifySchema = { export interface RoleModifySchema { name?: string; - permissions?: BigInt; + permissions?: bigint; color?: number; hoist?: boolean; // whether the role should be displayed separately in the sidebar mentionable?: boolean; // whether the role should be mentionable diff --git a/api/src/test/mongo_test.ts b/api/src/test/mongo_test.ts deleted file mode 100644 index bf203ea6..00000000 --- a/api/src/test/mongo_test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import mongoose, { Schema, Types } from "mongoose"; -require("mongoose-long")(mongoose); - -const userSchema = new Schema({ - id: String -}); - -const messageSchema = new Schema({ - id: String, - content: String -}); -const message = mongoose.model("message", messageSchema, "messages"); -const user = mongoose.model("user", userSchema, "users"); - -messageSchema.virtual("u", { - ref: user, - localField: "id", - foreignField: "id", - justOne: true -}); - -messageSchema.set("toObject", { virtuals: true }); -messageSchema.set("toJSON", { virtuals: true }); - -async function main() { - const conn = await mongoose.connect("mongodb://localhost:27017/lambert?readPreference=secondaryPreferred", { - useNewUrlParser: true, - useUnifiedTopology: false - }); - console.log("connected"); - - // const u = await new user({ name: "test" }).save(); - // await new message({ user: u._id, content: "test" }).save(); - - const test = await message.findOneOrFail({}).populate("u"); - // @ts-ignore - console.log(test?.toJSON()); -} - -main(); diff --git a/api/src/test/server_benchmark.ts b/api/src/test/server_benchmark.ts deleted file mode 100644 index c582ee89..00000000 --- a/api/src/test/server_benchmark.ts +++ /dev/null @@ -1,39 +0,0 @@ -// @ts-nocheck -import "missing-native-js-functions"; -import { config } from "dotenv"; -config(); -import { DiscordServer } from "../Server"; -import fetch from "node-fetch"; -import { promises } from "fs"; -const count = 100; - -async function main() { - const server = new DiscordServer({ port: 3000 }); - await server.start(); - - const tasks = []; - for (let i = 0; i < count; i++) { - tasks.push(test()); - } - - await Promise.all(tasks); - - console.log("logging in 5secs"); - setTimeout(async () => { - await test(); - - process.exit(); - }, 5000); -} -main(); - -async function test() { - const res = await fetch("http://localhost:3000/api/v8/guilds/813524615463698433/members/813524464300982272", { - headers: { - authorization: - "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjgxMzUyNDQ2NDMwMDk4MjI3MiIsImlhdCI6MTYxNDAyOTc0Nn0.6WQiU4D5HHRi3sliHOQe1hsW-hZTEttvdtZuNIdviNI", - }, - }); - - return await res.text(); -} diff --git a/api/src/test/test.ts b/api/src/test/test.ts deleted file mode 100644 index c37c762b..00000000 --- a/api/src/test/test.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { Snowflake } from "@fosscord/util"; - -console.log(Snowflake.deconstruct("0")); diff --git a/api/src/util/Channel.ts b/api/src/util/Channel.ts index f518aefd..bc9217ce 100644 --- a/api/src/util/Channel.ts +++ b/api/src/util/Channel.ts @@ -1,20 +1,9 @@ -import { - ChannelCreateEvent, - Channel, - ChannelType, - emitEvent, - getPermission, - Guild, - Snowflake, - TextChannel, - toObject, - VoiceChannel -} from "@fosscord/util"; +import { ChannelCreateEvent, Channel, ChannelType, emitEvent, getPermission, Snowflake } from "@fosscord/util"; import { HTTPError } from "lambert-server"; // TODO: DM channel export async function createChannel( - channel: Partial, + channel: Partial, user_id: string = "0", opts?: { keepId?: boolean; @@ -29,7 +18,7 @@ export async function createChannel( case ChannelType.GUILD_TEXT: case ChannelType.GUILD_VOICE: if (channel.parent_id && !opts?.skipExistsCheck) { - const exists = await Channel.findOneOrFail({ id: channel.parent_id }, { guild_id: true }); + const exists = await Channel.findOneOrFail({ id: channel.parent_id }); if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400); if (exists.guild_id !== channel.guild_id) throw new HTTPError("The category channel needs to be in the guild"); } diff --git a/api/src/util/Member.ts b/api/src/util/Member.ts deleted file mode 100644 index 6cb14d71..00000000 --- a/api/src/util/Member.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { - Guild, - GuildCreateEvent, - GuildDeleteEvent, - GuildMemberAddEvent, - GuildMemberRemoveEvent, - GuildMemberUpdateEvent, - Guild, - Member, - Role, - toObject, - User, - GuildDocument, - Config, - emitEvent -} from "@fosscord/util"; - -import { HTTPError } from "lambert-server"; - -import { getPublicUser } from "./User"; - -export const PublicMemberProjection = { - id: true, - guild_id: true, - nick: true, - roles: true, - joined_at: true, - pending: true, - deaf: true, - mute: true, - premium_since: true -}; - -export async function isMember(user_id: string, guild_id: string) { - const exists = await Member.exists({ id: user_id, guild_id }); - if (!exists) throw new HTTPError("You are not a member of this guild", 403); - return exists; -} - -export async function addMember(user_id: string, guild_id: string, cache?: { guild?: GuildDocument }) { - const user = await getPublicUser(user_id, { guilds: true }); - - const { maxGuilds } = Config.get().limits.user; - if (user.guilds.length >= maxGuilds) { - throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403); - } - - const guild = cache?.guild || (await Guild.findOneOrFail({ id: guild_id })); - - if (!guild) throw new HTTPError("Guild not found", 404); - - if (await Member.exists({ id: user.id, guild_id })) throw new HTTPError("You are already a member of this guild", 400); - - const member = { - id: user_id, - guild_id: guild_id, - nick: undefined, - roles: [guild_id], // @everyone role - joined_at: new Date(), - premium_since: undefined, - deaf: false, - mute: false, - pending: false - }; - - await Promise.all([ - new Member({ - ...member, - read_state: {}, - settings: { - channel_overrides: [], - message_notifications: 0, - mobile_push: true, - mute_config: null, - muted: false, - suppress_everyone: false, - suppress_roles: false, - version: 0 - } - }).save(), - - User.update({ id: user_id }, { $push: { guilds: guild_id } }), - Guild.update({ id: guild_id }, { $inc: { member_count: 1 } }), - - emitEvent({ - event: "GUILD_MEMBER_ADD", - data: { - ...member, - user, - guild_id: guild_id - }, - guild_id: guild_id - } as GuildMemberAddEvent) - ]); - - await emitEvent({ - event: "GUILD_CREATE", - data: await guild - .populate({ path: "members", match: { guild_id } }) - .populate({ path: "joined_at", match: { id: user.id } }) - .execPopulate(), - user_id - } as GuildCreateEvent); -} - -export async function removeMember(user_id: string, guild_id: string) { - const user = await getPublicUser(user_id); - - const guild = await Guild.findOneOrFail({ id: guild_id }, { owner_id: true }); - if (!guild) throw new HTTPError("Guild not found", 404); - if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild"); - if (!(await Member.exists({ id: user.id, guild_id }))) throw new HTTPError("Is not member of this guild", 404); - - // use promise all to execute all promises at the same time -> save time - return Promise.all([ - Member.deleteOne({ - id: user_id, - guild_id: guild_id - }), - User.update({ id: user.id }, { $pull: { guilds: guild_id } }), - Guild.update({ id: guild_id }, { $inc: { member_count: -1 } }), - - emitEvent({ - event: "GUILD_DELETE", - data: { - id: guild_id - }, - user_id: user_id - } as GuildDeleteEvent), - emitEvent({ - event: "GUILD_MEMBER_REMOVE", - data: { - guild_id: guild_id, - user: user - }, - guild_id: guild_id - } as GuildMemberRemoveEvent) - ]); -} - -export async function addRole(user_id: string, guild_id: string, role_id: string) { - const user = await getPublicUser(user_id); - - const role = await Role.findOneOrFail({ id: role_id, guild_id: guild_id }); - if (!role) throw new HTTPError("role not found", 404); - - var memberObj = await Member.findOneOrFailAndUpdate( - { - id: user_id, - guild_id: guild_id - }, - { $push: { roles: role_id } }, - { new: true } - ); - - if (!memberObj) throw new HTTPError("Member not found", 404); - - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - data: { - guild_id: guild_id, - user: user, - roles: memberObj.roles - }, - guild_id: guild_id - } as GuildMemberUpdateEvent); -} - -export async function removeRole(user_id: string, guild_id: string, role_id: string) { - const user = await getPublicUser(user_id); - - const role = await Role.findOneOrFail({ id: role_id, guild_id: guild_id }); - if (!role) throw new HTTPError("role not found", 404); - - var memberObj = await Member.findOneOrFailAndUpdate( - { - id: user_id, - guild_id: guild_id - }, - { $pull: { roles: role_id } }, - { new: true } - ); - - if (!memberObj) throw new HTTPError("Member not found", 404); - - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - data: { - guild_id: guild_id, - user: user, - roles: memberObj.roles - }, - guild_id: guild_id - } as GuildMemberUpdateEvent); -} - -export async function changeNickname(user_id: string, guild_id: string, nickname: string) { - const user = await getPublicUser(user_id); - - var memberObj = await Member.findOneOrFailAndUpdate( - { - id: user_id, - guild_id: guild_id - }, - { nick: nickname }, - { new: true } - ); - - if (!memberObj) throw new HTTPError("Member not found", 404); - - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - data: { - guild_id: guild_id, - user: user, - nick: nickname - }, - guild_id: guild_id - } as GuildMemberUpdateEvent); -} diff --git a/api/src/util/Message.ts b/api/src/util/Message.ts index 5561904b..70989301 100644 --- a/api/src/util/Message.ts +++ b/api/src/util/Message.ts @@ -8,18 +8,17 @@ import { getPermission, CHANNEL_MENTION, Snowflake, - PublicMemberProjection, USER_MENTION, ROLE_MENTION, Role, EVERYONE_MENTION, - HERE_MENTION + HERE_MENTION, + MessageType } from "@fosscord/util"; import { HTTPError } from "lambert-server"; import fetch from "node-fetch"; import cheerio from "cheerio"; -import { MessageType } from "@fosscord/util/dist/util/Constants"; // TODO: check webhook, application, system author const LINK_REGEX = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g; @@ -61,7 +60,7 @@ export async function handleMessage(opts: Partial): Promise { } var content = opts.content; - var mention_channels_ids = [] as string[]; + var mention_channel_ids = [] as string[]; var mention_role_ids = [] as string[]; var mention_user_ids = [] as string[]; var mention_everyone = false; @@ -70,7 +69,7 @@ export async function handleMessage(opts: Partial): Promise { if (content) { content = content.trim(); for (const [_, mention] of content.matchAll(CHANNEL_MENTION)) { - if (!mention_channels_ids.includes(mention)) mention_channels_ids.push(mention); + if (!mention_channel_ids.includes(mention)) mention_channel_ids.push(mention); } for (const [_, mention] of content.matchAll(USER_MENTION)) { @@ -92,11 +91,12 @@ export async function handleMessage(opts: Partial): Promise { } // TODO: check and put it all in the body + return { ...opts, guild_id: channel.guild_id, channel_id: opts.channel_id, - mention_channels_ids, + mention_channel_ids, mention_role_ids, mention_user_ids, mention_everyone, @@ -104,7 +104,7 @@ export async function handleMessage(opts: Partial): Promise { embeds: opts.embeds || [], reactions: opts.reactions || [], type: opts.type ?? 0 - }; + } as Message; } // TODO: cache link result in db @@ -163,10 +163,7 @@ export async function postHandleMessage(message: Message) { export async function sendMessage(opts: Partial) { const message = await handleMessage({ ...opts, id: Snowflake.generate(), timestamp: new Date() }); - const data = await new Message(message) - .populate({ path: "member", select: PublicMemberProjection }) - .populate("referenced_message") - .save(); + const data = await new Message(message).save(); await emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data } as MessageCreateEvent); -- cgit 1.5.1 From e0f2a5548ddb8db509898d4913b503c9fbfa2279 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 29 Aug 2021 16:58:23 +0200 Subject: fix rate limit --- api/src/middlewares/ErrorHandler.ts | 5 +- api/src/middlewares/RateLimit.ts | 95 +++++++++++++++++++------------------ 2 files changed, 52 insertions(+), 48 deletions(-) (limited to 'api/src/middlewares') diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index 8e2cd923..0ed37bb4 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -4,7 +4,7 @@ import { FieldError } from "../util/instanceOf"; // TODO: update with new body/typorm validation export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { - if (!error) next(); + if (!error) return next(); try { let code = 400; @@ -18,7 +18,6 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne message = error.message; errors = error.errors; } else { - console.error(error); if (req.server?.options?.production) { message = "Internal Server Error"; } @@ -27,7 +26,7 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne if (httpcode > 511) httpcode = 400; - console.error(`[Error] ${code} ${req.url} ${message}`, errors || error); + console.error(`[Error] ${code} ${req.url}`, errors || error, "body:", req.body); res.status(httpcode).json({ code: code, message, errors }); } catch (error) { diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index 9601dad3..e0cf103a 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -1,6 +1,6 @@ -// @ts-nocheck -import { db, Bucket, Config, listenEvent, emitEvent } from "@fosscord/util"; +import { Config, listenEvent, emitEvent, RateLimit } from "@fosscord/util"; import { NextFunction, Request, Response, Router } from "express"; +import { LessThan } from "typeorm"; import { getIpAdress } from "../util/ipAddress"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; @@ -18,10 +18,10 @@ TODO: different for methods (GET/POST) */ -var Cache = new Map(); -const EventRateLimit = "ratelimit"; +var Cache = new Map(); +const EventRateLimit = "RATELIMIT"; -export default function RateLimit(opts: { +export default function rateLimit(opts: { bucket?: string; window: number; count: number; @@ -36,15 +36,15 @@ export default function RateLimit(opts: { }): any { return async (req: Request, res: Response, next: NextFunction): Promise => { const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); - var user_id = getIpAdress(req); - if (!opts.onlyIp && req.user_id) user_id = req.user_id; + var executor_id = getIpAdress(req); + if (!opts.onlyIp && req.user_id) executor_id = req.user_id; var max_hits = opts.count; if (opts.bot && req.user_bot) max_hits = opts.bot; if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET; else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY; - const offender = Cache.get(user_id + bucket_id) as Bucket | null; + const offender = Cache.get(executor_id + bucket_id); if (offender && offender.blocked) { const reset = offender.expires_at.getTime(); @@ -72,12 +72,12 @@ export default function RateLimit(opts: { offender.expires_at = new Date(Date.now() + opts.window * 1000); offender.blocked = false; // mongodb ttl didn't update yet -> manually update/delete - db.collection("ratelimits").update({ id: bucket_id, user_id }, { $set: offender }); - Cache.delete(user_id + bucket_id); + RateLimit.delete({ id: bucket_id, executor_id }); + Cache.delete(executor_id + bucket_id); } } next(); - const hitRouteOpts = { bucket_id, user_id, max_hits, window: opts.window }; + const hitRouteOpts = { bucket_id, executor_id, max_hits, window: opts.window }; if (opts.error || opts.success) { res.once("finish", () => { @@ -97,69 +97,74 @@ export default function RateLimit(opts: { export async function initRateLimits(app: Router) { const { routes, global, ip, error } = Config.get().limits.rate; await listenEvent(EventRateLimit, (event) => { - Cache.set(event.channel_id, event.data); + Cache.set(event.channel_id as string, event.data); event.acknowledge?.(); }); + await RateLimit.delete({ expires_at: LessThan(new Date()) }); // clean up if not already deleted + const limits = await RateLimit.find({ blocked: true }); + limits.forEach((limit) => { + Cache.set(limit.executor_id, limit); + }); setInterval(() => { Cache.forEach((x, key) => { - if (Date.now() > x.expires_at) Cache.delete(key); + if (new Date() > x.expires_at) { + Cache.delete(key); + RateLimit.delete({ executor_id: key }); + } }); }, 1000 * 60 * 10); app.use( - RateLimit({ + rateLimit({ bucket: "global", onlyIp: true, ...ip }) ); - app.use(RateLimit({ bucket: "global", ...global })); + app.use(rateLimit({ bucket: "global", ...global })); app.use( - RateLimit({ + rateLimit({ bucket: "error", error: true, onlyIp: true, ...error }) ); - app.use("/guilds/:id", RateLimit(routes.guild)); - app.use("/webhooks/:id", RateLimit(routes.webhook)); - app.use("/channels/:id", RateLimit(routes.channel)); - app.use("/auth/login", RateLimit(routes.auth.login)); - app.use("/auth/register", RateLimit({ onlyIp: true, success: true, ...routes.auth.register })); + app.use("/guilds/:id", rateLimit(routes.guild)); + app.use("/webhooks/:id", rateLimit(routes.webhook)); + app.use("/channels/:id", rateLimit(routes.channel)); + app.use("/auth/login", rateLimit(routes.auth.login)); + app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register })); } -async function hitRoute(opts: { user_id: string; bucket_id: string; max_hits: number; window: number }) { - const filter = { id: opts.bucket_id, user_id: opts.user_id }; - const { value } = await db.collection("ratelimits").findOneOrFailAndUpdate( - filter, - { - $setOnInsert: { - id: opts.bucket_id, - user_id: opts.user_id, - expires_at: new Date(Date.now() + opts.window * 1000) - }, - $inc: { - hits: 1 - } - // Conditionally update blocked doesn't work - }, - { upsert: true, returnDocument: "before" } - ); - if (!value) return; - const updateBlock = !value.blocked && value.hits >= opts.max_hits; +async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) { + var ratelimit = await RateLimit.findOne({ id: opts.bucket_id, executor_id: opts.executor_id }); + if (!ratelimit) { + ratelimit = new RateLimit({ + id: opts.bucket_id, + executor_id: opts.executor_id, + expires_at: new Date(Date.now() + opts.window * 1000), + hits: 0, + blocked: false + }); + } + + ratelimit.hits++; + + const updateBlock = !ratelimit.blocked && ratelimit.hits >= opts.max_hits; if (updateBlock) { - value.blocked = true; - Cache.set(opts.user_id + opts.bucket_id, value); + ratelimit.blocked = true; + Cache.set(opts.executor_id + opts.bucket_id, ratelimit); await emitEvent({ channel_id: EventRateLimit, event: EventRateLimit, - data: value + data: ratelimit }); - await db.collection("ratelimits").update(filter, { $set: { blocked: true } }); } else { - Cache.delete(opts.user_id); + Cache.delete(opts.executor_id); } + + await ratelimit.save(); } -- cgit 1.5.1 From d0292ef96bedde2dd23962c15b2a0446b8492fb0 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Sun, 29 Aug 2021 17:12:46 +0200 Subject: :bug: convert bigint -> string --- api/src/middlewares/RateLimit.ts | 4 ++-- .../routes/channels/#channel_id/messages/index.ts | 2 +- api/src/routes/guilds/index.ts | 2 +- api/src/schema/Guild.ts | 2 +- bundle/database.db | Bin 225280 -> 225280 bytes gateway/src/opcodes/Identify.ts | 2 +- gateway/src/schema/Activity.ts | 4 ++-- util/src/entities/Application.ts | 2 +- util/src/interfaces/Event.ts | 6 +++--- 9 files changed, 12 insertions(+), 12 deletions(-) (limited to 'api/src/middlewares') diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index e0cf103a..ed6b951a 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -1,6 +1,6 @@ import { Config, listenEvent, emitEvent, RateLimit } from "@fosscord/util"; import { NextFunction, Request, Response, Router } from "express"; -import { LessThan } from "typeorm"; +import { LessThan, MoreThan } from "typeorm"; import { getIpAdress } from "../util/ipAddress"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; @@ -100,7 +100,7 @@ export async function initRateLimits(app: Router) { Cache.set(event.channel_id as string, event.data); event.acknowledge?.(); }); - await RateLimit.delete({ expires_at: LessThan(new Date()) }); // clean up if not already deleted + await RateLimit.delete({ expires_at: MoreThan(new Date()) }); // cleans up if not already deleted, morethan -> older date const limits = await RateLimit.find({ blocked: true }); limits.forEach((limit) => { Cache.set(limit.executor_id, limit); diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index 6307c022..17944548 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -77,7 +77,7 @@ router.get("/", async (req: Request, res: Response) => { delete x.user_ids; }); // @ts-ignore - if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: 0n, avatar: null }; + if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: "0", avatar: null }; return x; }); diff --git a/api/src/routes/guilds/index.ts b/api/src/routes/guilds/index.ts index c158c7d4..1e83cf13 100644 --- a/api/src/routes/guilds/index.ts +++ b/api/src/routes/guilds/index.ts @@ -47,7 +47,7 @@ router.post("/", check(GuildCreateSchema), async (req: Request, res: Response) = premium_tier: 0, public_updates_channel_id: undefined, rules_channel_id: undefined, - system_channel_flags: 0, + system_channel_flags: "0", system_channel_id: undefined, unavailable: false, vanity_url_code: undefined, diff --git a/api/src/schema/Guild.ts b/api/src/schema/Guild.ts index 01690ae9..3e98fe76 100644 --- a/api/src/schema/Guild.ts +++ b/api/src/schema/Guild.ts @@ -33,7 +33,7 @@ export const GuildUpdateSchema = { $icon: String, $verification_level: Number, $default_message_notifications: Number, - $system_channel_flags: Number, + $system_channel_flags: String, $system_channel_id: String, $explicit_content_filter: Number, $public_updates_channel_id: String, diff --git a/bundle/database.db b/bundle/database.db index 9572c45e..2d4abd49 100644 Binary files a/bundle/database.db and b/bundle/database.db differ diff --git a/gateway/src/opcodes/Identify.ts b/gateway/src/opcodes/Identify.ts index 5be2acce..87008998 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/gateway/src/opcodes/Identify.ts @@ -42,7 +42,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { } } - const members = await Member.find({ where: { id: this.user_id }, relations: ["guilds"] }); + const members = await Member.find({ where: { id: this.user_id }, relations: ["guild"] }); const merged_members = members.map((x: any) => { const y = { ...x, user_id: x.id }; delete y.settings; diff --git a/gateway/src/schema/Activity.ts b/gateway/src/schema/Activity.ts index 0fd0592f..f1665efd 100644 --- a/gateway/src/schema/Activity.ts +++ b/gateway/src/schema/Activity.ts @@ -39,7 +39,7 @@ export const ActivitySchema = { $match: String, }, $instance: Boolean, - $flags: BigInt, + $flags: String, }, ], $since: Number, // unix time (in milliseconds) of when the client went idle, or null if the client is not idle @@ -79,7 +79,7 @@ export interface ActivitySchema { match?: string; // the secret for a specific instanced match }; instance?: boolean; - flags: bigint; // activity flags OR d together, describes what the payload includes + flags: string; // activity flags OR d together, describes what the payload includes } ]; since?: number; // unix time (in milliseconds) of when the client went idle, or null if the client is not idle diff --git a/util/src/entities/Application.ts b/util/src/entities/Application.ts index a87b5cea..b179d171 100644 --- a/util/src/entities/Application.ts +++ b/util/src/entities/Application.ts @@ -62,7 +62,7 @@ export class Application extends BaseClass { cover_image?: string; // the application's default rich presence invite cover image hash @Column() - flags: number; // the application's public flags + flags: string; // the application's public flags } export interface ApplicationCommand { diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts index 0de55f71..814a8beb 100644 --- a/util/src/interfaces/Event.ts +++ b/util/src/interfaces/Event.ts @@ -36,7 +36,7 @@ export interface ReadyEventData { mobile: boolean; desktop: boolean; email: string | undefined; - flags: bigint; + flags: string; mfa_enabled: boolean; nsfw_allowed: boolean; phone: string | undefined; @@ -85,7 +85,7 @@ export interface ReadyEventData { }; application?: { id: string; - flags: bigint; + flags: string; }; merged_members?: Omit[][]; // probably all users who the user is in contact with @@ -95,7 +95,7 @@ export interface ReadyEventData { id: string; username: string; bot: boolean; - public_flags: bigint; + public_flags: string; }[]; } -- cgit 1.5.1 From 954700b2d5c4090fdf9d7d25beef3d6529afa8d3 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Mon, 30 Aug 2021 12:14:32 +0200 Subject: :zap: only local rate limit to prevent to much pressure on the database --- api/src/middlewares/RateLimit.ts | 70 +++++++++++++++++++++++++++++----------- util/src/entities/RateLimit.ts | 3 +- 2 files changed, 52 insertions(+), 21 deletions(-) (limited to 'api/src/middlewares') diff --git a/api/src/middlewares/RateLimit.ts b/api/src/middlewares/RateLimit.ts index ed6b951a..dffbc0d9 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/api/src/middlewares/RateLimit.ts @@ -1,11 +1,12 @@ -import { Config, listenEvent, emitEvent, RateLimit } from "@fosscord/util"; +import { Config, listenEvent } from "@fosscord/util"; import { NextFunction, Request, Response, Router } from "express"; -import { LessThan, MoreThan } from "typeorm"; import { getIpAdress } from "../util/ipAddress"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; // Docs: https://discord.com/developers/docs/topics/rate-limits +// TODO: use better caching (e.g. redis) as else it creates to much pressure on the database + /* ? bucket limit? Max actions/sec per bucket? @@ -18,6 +19,14 @@ TODO: different for methods (GET/POST) */ +type RateLimit = { + id: "global" | "error" | string; + executor_id: string; + hits: number; + blocked: boolean; + expires_at: Date; +}; + var Cache = new Map(); const EventRateLimit = "RATELIMIT"; @@ -46,13 +55,22 @@ export default function rateLimit(opts: { const offender = Cache.get(executor_id + bucket_id); - if (offender && offender.blocked) { + if (offender) { const reset = offender.expires_at.getTime(); const resetAfterMs = reset - Date.now(); const resetAfterSec = resetAfterMs / 1000; - const global = bucket_id === "global"; - if (resetAfterMs > 0) { + if (resetAfterMs <= 0) { + offender.hits = 0; + offender.expires_at = new Date(Date.now() + opts.window * 1000); + offender.blocked = false; + + Cache.delete(executor_id + bucket_id); + } + + if (offender.blocked) { + const global = bucket_id === "global"; + console.log("blocked bucket: " + bucket_id, { resetAfterMs }); return ( res @@ -67,15 +85,9 @@ export default function rateLimit(opts: { // TODO: error rate limit message translation .send({ message: "You are being rate limited.", retry_after: resetAfterSec, global }) ); - } else { - offender.hits = 0; - offender.expires_at = new Date(Date.now() + opts.window * 1000); - offender.blocked = false; - // mongodb ttl didn't update yet -> manually update/delete - RateLimit.delete({ id: bucket_id, executor_id }); - Cache.delete(executor_id + bucket_id); } } + next(); const hitRouteOpts = { bucket_id, executor_id, max_hits, window: opts.window }; @@ -100,20 +112,20 @@ export async function initRateLimits(app: Router) { Cache.set(event.channel_id as string, event.data); event.acknowledge?.(); }); - await RateLimit.delete({ expires_at: MoreThan(new Date()) }); // cleans up if not already deleted, morethan -> older date - const limits = await RateLimit.find({ blocked: true }); - limits.forEach((limit) => { - Cache.set(limit.executor_id, limit); - }); + // await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date + // const limits = await RateLimit.find({ blocked: true }); + // limits.forEach((limit) => { + // Cache.set(limit.executor_id, limit); + // }); setInterval(() => { Cache.forEach((x, key) => { if (new Date() > x.expires_at) { Cache.delete(key); - RateLimit.delete({ executor_id: key }); + // RateLimit.delete({ executor_id: key }); } }); - }, 1000 * 60 * 10); + }, 1000 * 60); app.use( rateLimit({ @@ -139,6 +151,25 @@ export async function initRateLimits(app: Router) { } async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) { + const id = opts.executor_id + opts.bucket_id; + var limit = Cache.get(id); + if (!limit) { + limit = { + id: opts.bucket_id, + executor_id: opts.executor_id, + expires_at: new Date(Date.now() + opts.window * 1000), + hits: 0, + blocked: false + }; + Cache.set(id, limit); + } + + limit.hits++; + if (limit.hits >= opts.max_hits) { + limit.blocked = true; + } + + /* var ratelimit = await RateLimit.findOne({ id: opts.bucket_id, executor_id: opts.executor_id }); if (!ratelimit) { ratelimit = new RateLimit({ @@ -167,4 +198,5 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits } await ratelimit.save(); + */ } diff --git a/util/src/entities/RateLimit.ts b/util/src/entities/RateLimit.ts index 49af0416..fa9c32c1 100644 --- a/util/src/entities/RateLimit.ts +++ b/util/src/entities/RateLimit.ts @@ -1,6 +1,5 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity } from "typeorm"; import { BaseClass } from "./BaseClass"; -import { User } from "./User"; @Entity("rate_limits") export class RateLimit extends BaseClass { -- cgit 1.5.1 From 3db1abdb50a5528893c1325d907c69d5534a5d31 Mon Sep 17 00:00:00 2001 From: xnacly Date: Mon, 30 Aug 2021 13:44:52 +0200 Subject: fixed translation path to display correct error msg --- api/src/middlewares/Translation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'api/src/middlewares') diff --git a/api/src/middlewares/Translation.ts b/api/src/middlewares/Translation.ts index edc14707..baabf221 100644 --- a/api/src/middlewares/Translation.ts +++ b/api/src/middlewares/Translation.ts @@ -19,7 +19,7 @@ export async function initTranslation(router: Router) { fallbackLng: "en", ns, backend: { - loadPath: __dirname + "/../locales/{{lng}}/{{ns}}.json" + loadPath: __dirname + "/../../locales/{{lng}}/{{ns}}.json" }, load: "all" }); -- cgit 1.5.1 From 3def279c5a0f23a2a62097979e96a6c9b78b5db9 Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:54:57 +0200 Subject: :sparkles: typeorm error handler --- api/src/middlewares/ErrorHandler.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'api/src/middlewares') diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index 0ed37bb4..e1ab592c 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -1,5 +1,6 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; +import { EntityNotFoundError } from "typeorm"; import { FieldError } from "../util/instanceOf"; // TODO: update with new body/typorm validation @@ -13,12 +14,18 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne let errors = undefined; if (error instanceof HTTPError && error.code) code = httpcode = error.code; - else if (error instanceof FieldError) { + else if (error instanceof EntityNotFoundError) { + message = `${(error as any).stringifyTarget} can not be found`; + code = 404; + } else if (error instanceof FieldError) { code = Number(error.code); message = error.message; errors = error.errors; } else { + console.error(`[Error] ${code} ${req.url}`, errors || error, "body:", req.body); + if (req.server?.options?.production) { + // don't expose internal errors to the user, instead human errors should be thrown as HTTPError message = "Internal Server Error"; } code = httpcode = 500; @@ -26,8 +33,6 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne if (httpcode > 511) httpcode = 400; - console.error(`[Error] ${code} ${req.url}`, errors || error, "body:", req.body); - res.status(httpcode).json({ code: code, message, errors }); } catch (error) { console.error(`[Internal Server Error] 500`, error); -- cgit 1.5.1 From 7fcb68f0825b99a830f7c1ee24bd58a456c1ed5b Mon Sep 17 00:00:00 2001 From: AlTech98 Date: Tue, 31 Aug 2021 17:56:38 +0200 Subject: Created list of all possible api errors and made them throwable in routes code --- api/src/middlewares/ErrorHandler.ts | 6 + api/src/util/ApiError.ts | 23 +++ api/src/util/Constants.ts | 293 ++++++++++++++++++++++++++++-------- 3 files changed, 256 insertions(+), 66 deletions(-) create mode 100644 api/src/util/ApiError.ts (limited to 'api/src/middlewares') diff --git a/api/src/middlewares/ErrorHandler.ts b/api/src/middlewares/ErrorHandler.ts index 0ed37bb4..5fc36f33 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/api/src/middlewares/ErrorHandler.ts @@ -1,6 +1,7 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; import { FieldError } from "../util/instanceOf"; +import {ApiError} from "../util/ApiError"; // TODO: update with new body/typorm validation export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { @@ -13,6 +14,11 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne let errors = undefined; if (error instanceof HTTPError && error.code) code = httpcode = error.code; + else if (error instanceof ApiError) { + code = error.code; + message = error.message; + httpcode = error.httpStatus; + } else if (error instanceof FieldError) { code = Number(error.code); message = error.message; diff --git a/api/src/util/ApiError.ts b/api/src/util/ApiError.ts new file mode 100644 index 00000000..2316cd71 --- /dev/null +++ b/api/src/util/ApiError.ts @@ -0,0 +1,23 @@ +export class ApiError extends Error { + constructor(readonly message: string, public readonly code: number, public readonly httpStatus: number = 400, public readonly defaultParams?: string[]) { + super(message); + } + + withDefaultParams(): ApiError { + if(this.defaultParams) + return new ApiError(applyParamsToString(this.message, this.defaultParams), this.code, this.httpStatus) + return this + } + + withParams(...params: string[]): ApiError { + return new ApiError(applyParamsToString(this.message, params), this.code, this.httpStatus) + } +} + +export function applyParamsToString(s: string, params: string[]): string { + let newString = s + params.forEach(a => { + newString = newString.replace("{}", a) + }) + return newString +} diff --git a/api/src/util/Constants.ts b/api/src/util/Constants.ts index f3a8dd67..15fdc519 100644 --- a/api/src/util/Constants.ts +++ b/api/src/util/Constants.ts @@ -1,3 +1,5 @@ +import {ApiError} from "./ApiError"; + export const WSCodes = { 1000: "WS_CLOSE_REQUESTED", 4004: "TOKEN_INVALID", @@ -421,6 +423,7 @@ export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"] /** * An error encountered while performing an API request. Here are the potential errors: + * * GENERAL_ERROR * * UNKNOWN_ACCOUNT * * UNKNOWN_APPLICATION * * UNKNOWN_CHANNEL @@ -436,27 +439,70 @@ export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"] * * UNKNOWN_USER * * UNKNOWN_EMOJI * * UNKNOWN_WEBHOOK + * * UNKNOWN_WEBHOOK_SERVICE + * * UNKNOWN_SESSION * * UNKNOWN_BAN + * * UNKNOWN_SKU + * * UNKNOWN_STORE_LISTING + * * UNKNOWN_ENTITLEMENT + * * UNKNOWN_BUILD + * * UNKNOWN_LOBBY + * * UNKNOWN_BRANCH + * * UNKNOWN_STORE_DIRECTORY_LAYOUT + * * UNKNOWN_REDISTRIBUTABLE + * * UNKNOWN_GIFT_CODE + * * UNKNOWN_STREAM + * * UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN * * UNKNOWN_GUILD_TEMPLATE + * * UNKNOWN_DISCOVERABLE_SERVER_CATEGORY + * * UNKNOWN_STICKER + * * UNKNOWN_INTERACTION + * * UNKNOWN_APPLICATION_COMMAND + * * UNKNOWN_APPLICATION_COMMAND_PERMISSIONS + * * UNKNOWN_STAGE_INSTANCE + * * UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM + * * UNKNOWN_GUILD_WELCOME_SCREEN + * * UNKNOWN_GUILD_SCHEDULED_EVENT + * * UNKNOWN_GUILD_SCHEDULED_EVENT_USER * * BOT_PROHIBITED_ENDPOINT * * BOT_ONLY_ENDPOINT - * * CHANNEL_HIT_WRITE_RATELIMIT + * * EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT + * * ACTION_NOT_AUTHORIZED_ON_APPLICATION + * * SLOWMODE_RATE_LIMIT + * * ONLY_OWNER + * * ANNOUNCEMENT_RATE_LIMITS + * * CHANNEL_WRITE_RATELIMIT + * * WORDS_NOT_ALLOWED + * * GUILD_PREMIUM_LEVEL_TOO_LOW * * MAXIMUM_GUILDS * * MAXIMUM_FRIENDS * * MAXIMUM_PINS + * * MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED * * MAXIMUM_ROLES * * MAXIMUM_WEBHOOKS + * * MAXIMUM_NUMBER_OF_EMOJIS_REACHED * * MAXIMUM_REACTIONS * * MAXIMUM_CHANNELS * * MAXIMUM_ATTACHMENTS * * MAXIMUM_INVITES + * * MAXIMUM_ANIMATED_EMOJIS + * * MAXIMUM_SERVER_MEMBERS + * * MAXIMUM_SERVER_CATEGORIES * * GUILD_ALREADY_HAS_TEMPLATE + * * MAXIMUM_THREAD_PARTICIPANTS + * * MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS + * * MAXIMUM_BANS_FETCHES + * * MAXIMUM_STICKERS + * * MAXIMUM_PRUNE_REQUESTS * * UNAUTHORIZED * * ACCOUNT_VERIFICATION_REQUIRED + * * OPENING_DIRECT_MESSAGES_TOO_FAST * * REQUEST_ENTITY_TOO_LARGE * * FEATURE_TEMPORARILY_DISABLED * * USER_BANNED + * * TARGET_USER_IS_NOT_CONNECTED_TO_VOICE * * ALREADY_CROSSPOSTED + * * APPLICATION_COMMAND_ALREADY_EXISTS * * MISSING_ACCESS * * INVALID_ACCOUNT_TYPE * * CANNOT_EXECUTE_ON_DM @@ -476,81 +522,196 @@ export const VerificationLevels = ["NONE", "LOW", "MEDIUM", "HIGH", "VERY_HIGH"] * * CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL * * INVALID_OR_TAKEN_INVITE_CODE * * CANNOT_EXECUTE_ON_SYSTEM_MESSAGE + * * CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE * * INVALID_OAUTH_TOKEN + * * MISSING_REQUIRED_OAUTH2_SCOPE + * * INVALID_WEBHOOK_TOKEN_PROVIDED + * * INVALID_ROLE + * * INVALID_RECIPIENT * * BULK_DELETE_MESSAGE_TOO_OLD * * INVALID_FORM_BODY * * INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT * * INVALID_API_VERSION + * * FILE_EXCEEDS_MAXIMUM_SIZE + * * INVALID_FILE_UPLOADED + * * CANNOT_SELF_REDEEM_GIFT + * * PAYMENT_SOURCE_REQUIRED * * CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL + * * INVALID_STICKER_SENT + * * CANNOT_EDIT_ARCHIVED_THREAD + * * INVALID_THREAD_NOTIFICATION_SETTINGS + * * BEFORE_EARLIER_THAN_THREAD_CREATION_DATE + * * SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION + * * SERVER_NEEDS_MONETIZATION_ENABLED + * * TWO_FACTOR_REQUIRED + * * NO_USERS_WITH_DISCORDTAG_EXIST * * REACTION_BLOCKED * * RESOURCE_OVERLOADED + * * STAGE_ALREADY_OPEN + * * THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE + * * THREAD_IS_LOCKED + * * MAXIMUM_NUMBER_OF_ACTIVE_THREADS + * * MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS + * * INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE + * * LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES + * * STICKER_MAXIMUM_FRAMERATE + * * STICKER_MAXIMUM_FRAME_COUNT + * * LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS + * * STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE + * * STICKER_ANIMATION_DURATION_MAXIMUM + * * UNKNOWN_VOICE_STATE * @typedef {string} APIError */ -export const APIErrors = { - UNKNOWN_ACCOUNT: 10001, - UNKNOWN_APPLICATION: 10002, - UNKNOWN_CHANNEL: 10003, - UNKNOWN_GUILD: 10004, - UNKNOWN_INTEGRATION: 10005, - UNKNOWN_INVITE: 10006, - UNKNOWN_MEMBER: 10007, - UNKNOWN_MESSAGE: 10008, - UNKNOWN_OVERWRITE: 10009, - UNKNOWN_PROVIDER: 10010, - UNKNOWN_ROLE: 10011, - UNKNOWN_TOKEN: 10012, - UNKNOWN_USER: 10013, - UNKNOWN_EMOJI: 10014, - UNKNOWN_WEBHOOK: 10015, - UNKNOWN_BAN: 10026, - UNKNOWN_GUILD_TEMPLATE: 10057, - BOT_PROHIBITED_ENDPOINT: 20001, - BOT_ONLY_ENDPOINT: 20002, - CHANNEL_HIT_WRITE_RATELIMIT: 20028, - MAXIMUM_GUILDS: 30001, - MAXIMUM_FRIENDS: 30002, - MAXIMUM_PINS: 30003, - MAXIMUM_ROLES: 30005, - MAXIMUM_WEBHOOKS: 30007, - MAXIMUM_REACTIONS: 30010, - MAXIMUM_CHANNELS: 30013, - MAXIMUM_ATTACHMENTS: 30015, - MAXIMUM_INVITES: 30016, - GUILD_ALREADY_HAS_TEMPLATE: 30031, - UNAUTHORIZED: 40001, - ACCOUNT_VERIFICATION_REQUIRED: 40002, - REQUEST_ENTITY_TOO_LARGE: 40005, - FEATURE_TEMPORARILY_DISABLED: 40006, - USER_BANNED: 40007, - ALREADY_CROSSPOSTED: 40033, - MISSING_ACCESS: 50001, - INVALID_ACCOUNT_TYPE: 50002, - CANNOT_EXECUTE_ON_DM: 50003, - EMBED_DISABLED: 50004, - CANNOT_EDIT_MESSAGE_BY_OTHER: 50005, - CANNOT_SEND_EMPTY_MESSAGE: 50006, - CANNOT_MESSAGE_USER: 50007, - CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: 50008, - CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: 50009, - OAUTH2_APPLICATION_BOT_ABSENT: 50010, - MAXIMUM_OAUTH2_APPLICATIONS: 50011, - INVALID_OAUTH_STATE: 50012, - MISSING_PERMISSIONS: 50013, - INVALID_AUTHENTICATION_TOKEN: 50014, - NOTE_TOO_LONG: 50015, - INVALID_BULK_DELETE_QUANTITY: 50016, - CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: 50019, - INVALID_OR_TAKEN_INVITE_CODE: 50020, - CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: 50021, - INVALID_OAUTH_TOKEN: 50025, - BULK_DELETE_MESSAGE_TOO_OLD: 50034, - INVALID_FORM_BODY: 50035, - INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: 50036, - INVALID_API_VERSION: 50041, - CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: 50074, - REACTION_BLOCKED: 90001, - RESOURCE_OVERLOADED: 130000, -}; +export const DiscordApiErrors = { + //https://discord.com/developers/docs/topics/opcodes-and-status-codes#json-json-error-codes + GENERAL_ERROR: new ApiError("General error (such as a malformed request body, amongst other things)", 0), + UNKNOWN_ACCOUNT: new ApiError("Unknown account", 10001), + UNKNOWN_APPLICATION: new ApiError("Unknown application", 10002), + UNKNOWN_CHANNEL: new ApiError("Unknown channel", 10003), + UNKNOWN_GUILD: new ApiError("Unknown guild", 10004), + UNKNOWN_INTEGRATION: new ApiError("Unknown integration", 10005), + UNKNOWN_INVITE: new ApiError("Unknown invite", 10006), + UNKNOWN_MEMBER: new ApiError("Unknown member", 10007), + UNKNOWN_MESSAGE: new ApiError("Unknown message", 10008), + UNKNOWN_OVERWRITE: new ApiError("Unknown permission overwrite", 10009), + UNKNOWN_PROVIDER: new ApiError("Unknown provider", 10010), + UNKNOWN_ROLE: new ApiError("Unknown role", 10011), + UNKNOWN_TOKEN: new ApiError("Unknown token", 10012), + UNKNOWN_USER: new ApiError("Unknown user", 10013), + UNKNOWN_EMOJI: new ApiError("Unknown emoji", 10014), + UNKNOWN_WEBHOOK: new ApiError("Unknown webhook", 10015), + UNKNOWN_WEBHOOK_SERVICE: new ApiError("Unknown webhook service", 10016), + UNKNOWN_SESSION: new ApiError("Unknown session", 10020), + UNKNOWN_BAN: new ApiError("Unknown ban", 10026), + UNKNOWN_SKU: new ApiError("Unknown SKU", 10027), + UNKNOWN_STORE_LISTING: new ApiError("Unknown Store Listing", 10028), + UNKNOWN_ENTITLEMENT: new ApiError("Unknown entitlement", 10029), + UNKNOWN_BUILD: new ApiError("Unknown build", 10030), + UNKNOWN_LOBBY: new ApiError("Unknown lobby", 10031), + UNKNOWN_BRANCH: new ApiError("Unknown branch", 10032), + UNKNOWN_STORE_DIRECTORY_LAYOUT: new ApiError("Unknown store directory layout", 10033), + UNKNOWN_REDISTRIBUTABLE: new ApiError("Unknown redistributable", 10036), + UNKNOWN_GIFT_CODE: new ApiError("Unknown gift code", 10038), + UNKNOWN_STREAM: new ApiError("Unknown stream", 10049), + UNKNOWN_PREMIUM_SERVER_SUBSCRIBE_COOLDOWN: new ApiError("Unknown premium server subscribe cooldown", 10050), + UNKNOWN_GUILD_TEMPLATE: new ApiError("Unknown guild template", 10057), + UNKNOWN_DISCOVERABLE_SERVER_CATEGORY: new ApiError("Unknown discoverable server category", 10059), + UNKNOWN_STICKER: new ApiError("Unknown sticker", 10060), + UNKNOWN_INTERACTION: new ApiError("Unknown interaction", 10062), + UNKNOWN_APPLICATION_COMMAND: new ApiError("Unknown application command", 10063), + UNKNOWN_APPLICATION_COMMAND_PERMISSIONS: new ApiError("Unknown application command permissions", 10066), + UNKNOWN_STAGE_INSTANCE: new ApiError("Unknown Stage Instance", 10067), + UNKNOWN_GUILD_MEMBER_VERIFICATION_FORM: new ApiError("Unknown Guild Member Verification Form", 10068), + UNKNOWN_GUILD_WELCOME_SCREEN: new ApiError("Unknown Guild Welcome Screen", 10069), + UNKNOWN_GUILD_SCHEDULED_EVENT: new ApiError("Unknown Guild Scheduled Event", 10070), + UNKNOWN_GUILD_SCHEDULED_EVENT_USER: new ApiError("Unknown Guild Scheduled Event User", 10071), + BOT_PROHIBITED_ENDPOINT: new ApiError("Bots cannot use this endpoint", 20001), + BOT_ONLY_ENDPOINT: new ApiError("Only bots can use this endpoint", 20002), + EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT: new ApiError("Explicit content cannot be sent to the desired recipient(s)", 20009), + ACTION_NOT_AUTHORIZED_ON_APPLICATION: new ApiError("You are not authorized to perform this action on this application", 20012), + SLOWMODE_RATE_LIMIT: new ApiError("This action cannot be performed due to slowmode rate limit", 20016), + ONLY_OWNER: new ApiError("Only the owner of this account can perform this action", 20018), + ANNOUNCEMENT_RATE_LIMITS: new ApiError("This message cannot be edited due to announcement rate limits", 20022), + CHANNEL_WRITE_RATELIMIT: new ApiError("The channel you are writing has hit the write rate limit", 20028), + WORDS_NOT_ALLOWED: new ApiError("Your Stage topic, server name, server description, or channel names contain words that are not allowed", 20031), + GUILD_PREMIUM_LEVEL_TOO_LOW: new ApiError("Guild premium subscription level too low", 20035), + MAXIMUM_GUILDS: new ApiError("Maximum number of guilds reached ({})", 30001, undefined, ["100"]), + MAXIMUM_FRIENDS: new ApiError("Maximum number of friends reached ({})", 30002, undefined, ["1000"]), + MAXIMUM_PINS: new ApiError("Maximum number of pins reached for the channel ({})", 30003, undefined, ["50"]), + MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED: new ApiError("Maximum number of recipients reached ({})", 30004, undefined, ["10"]), + MAXIMUM_ROLES: new ApiError("Maximum number of guild roles reached ({})", 30005, undefined, ["250"]), + MAXIMUM_WEBHOOKS: new ApiError("Maximum number of webhooks reached ({})", 30007, undefined, ["10"]), + MAXIMUM_NUMBER_OF_EMOJIS_REACHED: new ApiError("Maximum number of emojis reached", 30008), + MAXIMUM_REACTIONS: new ApiError("Maximum number of reactions reached ({})", 30010, undefined, ["20"]), + MAXIMUM_CHANNELS: new ApiError("Maximum number of guild channels reached ({})", 30013, undefined, ["500"]), + MAXIMUM_ATTACHMENTS: new ApiError("Maximum number of attachments in a message reached ({})", 30015, undefined, ["10"]), + MAXIMUM_INVITES: new ApiError("Maximum number of invites reached ({})", 30016, undefined, ["1000"]), + MAXIMUM_ANIMATED_EMOJIS: new ApiError("Maximum number of animated emojis reached", 30018), + MAXIMUM_SERVER_MEMBERS: new ApiError("Maximum number of server members reached", 30019), + MAXIMUM_SERVER_CATEGORIES: new ApiError("Maximum number of server categories has been reached ({})", 30030, undefined, ["5"]), + GUILD_ALREADY_HAS_TEMPLATE: new ApiError("Guild already has a template", 30031), + MAXIMUM_THREAD_PARTICIPANTS: new ApiError("Max number of thread participants has been reached", 30033), + MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS: new ApiError("Maximum number of bans for non-guild members have been exceeded", 30035), + MAXIMUM_BANS_FETCHES: new ApiError("Maximum number of bans fetches has been reached", 30037), + MAXIMUM_STICKERS: new ApiError("Maximum number of stickers reached", 30039), + MAXIMUM_PRUNE_REQUESTS: new ApiError("Maximum number of prune requests has been reached. Try again later", 30040), + UNAUTHORIZED: new ApiError("Unauthorized. Provide a valid token and try again", 40001), + ACCOUNT_VERIFICATION_REQUIRED: new ApiError("You need to verify your account in order to perform this action", 40002), + OPENING_DIRECT_MESSAGES_TOO_FAST: new ApiError("You are opening direct messages too fast", 40003), + REQUEST_ENTITY_TOO_LARGE: new ApiError("Request entity too large. Try sending something smaller in size", 40005), + FEATURE_TEMPORARILY_DISABLED: new ApiError("This feature has been temporarily disabled server-side", 40006), + USER_BANNED: new ApiError("The user is banned from this guild", 40007), + TARGET_USER_IS_NOT_CONNECTED_TO_VOICE: new ApiError("Target user is not connected to voice", 40032), + ALREADY_CROSSPOSTED: new ApiError("This message has already been crossposted", 40033), + APPLICATION_COMMAND_ALREADY_EXISTS: new ApiError("An application command with that name already exists", 40041), + MISSING_ACCESS: new ApiError("Missing access", 50001), + INVALID_ACCOUNT_TYPE: new ApiError("Invalid account type", 50002), + CANNOT_EXECUTE_ON_DM: new ApiError("Cannot execute action on a DM channel", 50003), + EMBED_DISABLED: new ApiError("Guild widget disabled", 50004), + CANNOT_EDIT_MESSAGE_BY_OTHER: new ApiError("Cannot edit a message authored by another user", 50005), + CANNOT_SEND_EMPTY_MESSAGE: new ApiError("Cannot send an empty message", 50006), + CANNOT_MESSAGE_USER: new ApiError("Cannot send messages to this user", 50007), + CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: new ApiError("Cannot send messages in a voice channel", 50008), + CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: new ApiError("Channel verification level is too high for you to gain access", 50009), + OAUTH2_APPLICATION_BOT_ABSENT: new ApiError("OAuth2 application does not have a bot", 50010), + MAXIMUM_OAUTH2_APPLICATIONS: new ApiError("OAuth2 application limit reached", 50011), + INVALID_OAUTH_STATE: new ApiError("Invalid OAuth2 state", 50012), + MISSING_PERMISSIONS: new ApiError("You lack permissions to perform that action", 50013), + INVALID_AUTHENTICATION_TOKEN: new ApiError("Invalid authentication token provided", 50014), + NOTE_TOO_LONG: new ApiError("Note was too long", 50015), + INVALID_BULK_DELETE_QUANTITY: new ApiError("Provided too few or too many messages to delete. Must provide at least {} and fewer than {} messages to delete", 50016, undefined, ["2","100"]), + CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: new ApiError("A message can only be pinned to the channel it was sent in", 50019), + INVALID_OR_TAKEN_INVITE_CODE: new ApiError("Invite code was either invalid or taken", 50020), + CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: new ApiError("Cannot execute action on a system message", 50021), + CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE: new ApiError("Cannot execute action on this channel type", 50024), + INVALID_OAUTH_TOKEN: new ApiError("Invalid OAuth2 access token provided", 50025), + MISSING_REQUIRED_OAUTH2_SCOPE: new ApiError("Missing required OAuth2 scope", 50026), + INVALID_WEBHOOK_TOKEN_PROVIDED: new ApiError("Invalid webhook token provided", 50027), + INVALID_ROLE: new ApiError("Invalid role", 50028), + INVALID_RECIPIENT: new ApiError("Invalid Recipient(s)", 50033), + BULK_DELETE_MESSAGE_TOO_OLD: new ApiError("A message provided was too old to bulk delete", 50034), + INVALID_FORM_BODY: new ApiError("Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided", 50035), + INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: new ApiError("An invite was accepted to a guild the application's bot is not in", 50036), + INVALID_API_VERSION: new ApiError("Invalid API version provided", 50041), + FILE_EXCEEDS_MAXIMUM_SIZE: new ApiError("File uploaded exceeds the maximum size", 50045), + INVALID_FILE_UPLOADED: new ApiError("Invalid file uploaded", 50046), + CANNOT_SELF_REDEEM_GIFT: new ApiError("Cannot self-redeem this gift", 50054), + PAYMENT_SOURCE_REQUIRED: new ApiError("Payment source required to redeem gift", 50070), + CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: new ApiError("Cannot delete a channel required for Community guilds", 50074), + INVALID_STICKER_SENT: new ApiError("Invalid sticker sent", 50081), + CANNOT_EDIT_ARCHIVED_THREAD: new ApiError("Tried to perform an operation on an archived thread, such as editing a message or adding a user to the thread", 50083), + INVALID_THREAD_NOTIFICATION_SETTINGS: new ApiError("Invalid thread notification settings", 50084), + BEFORE_EARLIER_THAN_THREAD_CREATION_DATE: new ApiError("before value is earlier than the thread creation date", 50085), + SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION: new ApiError("This server is not available in your location", 50095), + SERVER_NEEDS_MONETIZATION_ENABLED: new ApiError("This server needs monetization enabled in order to perform this action", 50097), + TWO_FACTOR_REQUIRED: new ApiError("Two factor is required for this operation", 60003), + NO_USERS_WITH_DISCORDTAG_EXIST: new ApiError("No users with DiscordTag exist", 80004), + REACTION_BLOCKED: new ApiError("Reaction was blocked", 90001), + RESOURCE_OVERLOADED: new ApiError("API resource is currently overloaded. Try again a little later", 130000), + STAGE_ALREADY_OPEN: new ApiError("The Stage is already open", 150006), + THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE: new ApiError("A thread has already been created for this message", 160004), + THREAD_IS_LOCKED: new ApiError("Thread is locked", 160005), + MAXIMUM_NUMBER_OF_ACTIVE_THREADS: new ApiError("Maximum number of active threads reached", 160006), + MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS: new ApiError("Maximum number of active announcement threads reached", 160007), + INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: new ApiError("Invalid JSON for uploaded Lottie file", 170001), + LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: new ApiError("Uploaded Lotties cannot contain rasterized images such as PNG or JPEG", 170002), + STICKER_MAXIMUM_FRAMERATE: new ApiError("Sticker maximum framerate exceeded", 170003), + STICKER_MAXIMUM_FRAME_COUNT: new ApiError("Sticker frame count exceeds maximum of {} frames", 170004, undefined, ["1000"]), + LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS: new ApiError("Lottie animation maximum dimensions exceeded", 170005), + STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE: new ApiError("Sticker frame rate is either too small or too large", 170006), + STICKER_ANIMATION_DURATION_MAXIMUM: new ApiError("Sticker animation duration exceeds maximum of {} seconds", 170007, undefined, ["5"]), + + + //Other errors + UNKNOWN_VOICE_STATE: new ApiError("Unknown Voice State", 10065, 404), +} + +/** + * An error encountered while performing an API request (Fosscord only). Here are the potential errors: + */ +export const FosscordApiErrors = { + +} /** * The value set for a guild's default message notifications, e.g. `ALL`. Here are the available types: -- cgit 1.5.1 From 3f66c101789f9a6d065490d3e3265c764698113f Mon Sep 17 00:00:00 2001 From: Flam3rboy <34555296+Flam3rboy@users.noreply.github.com> Date: Tue, 31 Aug 2021 17:58:47 +0200 Subject: :bug: db query fixes --- Status.ts | 7 ----- api/package-lock.json | 18 +++++------ api/package.json | 2 +- api/src/middlewares/Authentication.ts | 8 ++--- .../routes/channels/#channel_id/messages/index.ts | 36 ++++++++++++---------- api/src/routes/channels/#channel_id/pins.ts | 2 +- api/src/routes/channels/#channel_id/typing.ts | 2 +- api/src/routes/users/@me/disable.ts | 2 +- api/src/routes/users/@me/index.ts | 2 +- api/src/routes/users/@me/relationships.ts | 12 ++++---- api/src/schema/Message.ts | 2 +- bundle/package-lock.json | 13 ++++++++ bundle/package.json | 1 + 13 files changed, 59 insertions(+), 48 deletions(-) delete mode 100644 Status.ts (limited to 'api/src/middlewares') diff --git a/Status.ts b/Status.ts deleted file mode 100644 index c4dab586..00000000 --- a/Status.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type Status = "idle" | "dnd" | "online" | "offline"; - -export interface ClientStatus { - desktop?: string; // e.g. Windows/Linux/Mac - mobile?: string; // e.g. iOS/Android - web?: string; // e.g. browser, bot account -} diff --git a/api/package-lock.json b/api/package-lock.json index 758e51c8..63724688 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -60,7 +60,7 @@ "saslprep": "^1.0.3", "ts-node": "^9.1.1", "ts-node-dev": "^1.1.6", - "typescript": "^4.1.2" + "typescript": "^4.4.2" } }, "../util": { @@ -83,7 +83,7 @@ "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", "typeorm": "^0.2.37", - "typescript": "^4.3.5", + "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" }, "devDependencies": { @@ -11430,9 +11430,9 @@ } }, "node_modules/typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz", + "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -12637,7 +12637,7 @@ "reflect-metadata": "^0.1.13", "sqlite3": "^5.0.2", "typeorm": "^0.2.37", - "typescript": "^4.3.5", + "typescript": "^4.4.2", "typescript-json-schema": "^0.50.1" } }, @@ -21387,9 +21387,9 @@ } }, "typescript": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", - "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.2.tgz", + "integrity": "sha512-gzP+t5W4hdy4c+68bfcv0t400HVJMMd2+H9B7gae1nQlBzCqvrXX+6GL/b3GAgyTH966pzrZ70/fRjwAtZksSQ==", "dev": true }, "umd": { diff --git a/api/package.json b/api/package.json index b8bb4d45..37db6ff3 100644 --- a/api/package.json +++ b/api/package.json @@ -52,7 +52,7 @@ "saslprep": "^1.0.3", "ts-node": "^9.1.1", "ts-node-dev": "^1.1.6", - "typescript": "^4.1.2" + "typescript": "^4.4.2" }, "dependencies": { "@fosscord/util": "file:../util", diff --git a/api/src/middlewares/Authentication.ts b/api/src/middlewares/Authentication.ts index 34a66a6b..a300c786 100644 --- a/api/src/middlewares/Authentication.ts +++ b/api/src/middlewares/Authentication.ts @@ -18,9 +18,9 @@ export const API_PREFIX_TRAILING_SLASH = /^\/api(\/v\d+)?\//; declare global { namespace Express { interface Request { - user_id: any; + user_id: string; user_bot: boolean; - token: any; + token: string; } } } @@ -47,7 +47,7 @@ export async function Authentication(req: Request, res: Response, next: NextFunc req.user_id = decoded.id; req.user_bot = user.bot; return next(); - } catch (error) { - return next(new HTTPError(error.toString(), 400)); + } catch (error: any) { + return next(new HTTPError(error?.toString(), 400)); } } diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/api/src/routes/channels/#channel_id/messages/index.ts index 17944548..86de6de8 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/api/src/routes/channels/#channel_id/messages/index.ts @@ -31,10 +31,7 @@ export function isTextChannel(type: ChannelType): boolean { // get messages router.get("/", async (req: Request, res: Response) => { const channel_id = req.params.channel_id; - const channel = await Channel.findOneOrFail( - { id: channel_id }, - { select: ["guild_id", "type", "permission_overwrites", "recipient_ids", "owner_id"] } - ); // lean is needed, because we don't want to populate .recipients that also auto deletes .recipient_ids + const channel = await Channel.findOneOrFail({ id: channel_id }); if (!channel) throw new HTTPError("Channel not found", 404); isTextChannel(channel.type); @@ -56,7 +53,12 @@ router.get("/", async (req: Request, res: Response) => { permissions.hasThrow("VIEW_CHANNEL"); if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); - var query: FindManyOptions & { where: { id?: any } } = { order: { id: "DESC" }, take: limit, where: { channel_id } }; + var query: FindManyOptions & { where: { id?: any } } = { + order: { id: "DESC" }, + take: limit, + where: { channel_id }, + relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"] + }; if (after) query.where.id = MoreThan(after); else if (before) query.where.id = LessThan(before); @@ -69,18 +71,20 @@ router.get("/", async (req: Request, res: Response) => { const messages = await Message.find(query); - return res.json(messages).map((x) => { - (x.reactions || []).forEach((x: any) => { + return res.json( + messages.map((x) => { + (x.reactions || []).forEach((x: any) => { + // @ts-ignore + if ((x.user_ids || []).includes(req.user_id)) x.me = true; + // @ts-ignore + delete x.user_ids; + }); // @ts-ignore - if ((x.user_ids || []).includes(req.user_id)) x.me = true; - // @ts-ignore - delete x.user_ids; - }); - // @ts-ignore - if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: "0", avatar: null }; + if (!x.author) x.author = { discriminator: "0000", username: "Deleted User", public_flags: "0", avatar: null }; - return x; - }); + return x; + }) + ); }); // TODO: config max upload size @@ -136,5 +140,5 @@ router.post("/", messageUpload.single("file"), async (req: Request, res: Respons edited_timestamp: undefined }); - return res.send(data); + return res.json(data); }); diff --git a/api/src/routes/channels/#channel_id/pins.ts b/api/src/routes/channels/#channel_id/pins.ts index d83e36ed..96a3fdbf 100644 --- a/api/src/routes/channels/#channel_id/pins.ts +++ b/api/src/routes/channels/#channel_id/pins.ts @@ -14,7 +14,7 @@ router.put("/:message_id", async (req: Request, res: Response) => { // * in dm channels anyone can pin messages -> only check for guilds if (message.guild_id) permission.hasThrow("MANAGE_MESSAGES"); - const pinned_count = await Message.count({ channel_id, pinned: true }); + const pinned_count = await Message.count({ channel: { id: channel_id }, pinned: true }); const { maxPins } = Config.get().limits.channel; if (pinned_count >= maxPins) throw new HTTPError("Max pin count reached: " + maxPins); diff --git a/api/src/routes/channels/#channel_id/typing.ts b/api/src/routes/channels/#channel_id/typing.ts index 2305e8e8..f1fb3c86 100644 --- a/api/src/routes/channels/#channel_id/typing.ts +++ b/api/src/routes/channels/#channel_id/typing.ts @@ -17,7 +17,7 @@ router.post("/", async (req: Request, res: Response) => { channel_id: channel_id, data: { // this is the paylod - member: { ...member, roles: member.role_ids }, + member: { ...member, roles: member.roles.map((x) => x.id) }, channel_id, timestamp, user_id, diff --git a/api/src/routes/users/@me/disable.ts b/api/src/routes/users/@me/disable.ts index ed1dedcc..7b8a130c 100644 --- a/api/src/routes/users/@me/disable.ts +++ b/api/src/routes/users/@me/disable.ts @@ -5,7 +5,7 @@ import bcrypt from "bcrypt"; const router = Router(); router.post("/", async (req: Request, res: Response) => { - const user = await User.findOneOrFail(req.user_id); //User object + const user = await User.findOneOrFail({ id: req.user_id }); //User object let correctpass = true; if (user.data.hash) { diff --git a/api/src/routes/users/@me/index.ts b/api/src/routes/users/@me/index.ts index 274cfb24..d5a5723c 100644 --- a/api/src/routes/users/@me/index.ts +++ b/api/src/routes/users/@me/index.ts @@ -37,7 +37,7 @@ router.patch("/", check(UserModifySchema), async (req: Request, res: Response) = if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); - const user = await new User({ ...body }, { id: req.user_id }).save(); + const user = await new User({ ...body, id: req.user_id }).save(); // TODO: dispatch user update event res.json(user); diff --git a/api/src/routes/users/@me/relationships.ts b/api/src/routes/users/@me/relationships.ts index 1a89b110..0b864d88 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/api/src/routes/users/@me/relationships.ts @@ -26,7 +26,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ const id = friend.id; if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); - const user = await User.findOneOrFail(req.user_id, { relations: ["relationships"], select: userProjection }); + const user = await User.findOneOrFail({ id: req.user_id }, { relations: ["relationships"], select: userProjection }); var relationship = user.relationships.find((x) => x.id === id); const friendRequest = friend.relationships.find((x) => x.id === req.user_id); @@ -67,8 +67,8 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ return res.sendStatus(204); } - var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming }, { id: req.user_id }); - var outgoing_relationship = new Relationship({ nickname: undefined, type: RelationshipType.outgoing }, { id }); + var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming, id: req.user_id }); + var outgoing_relationship = new Relationship({ nickname: undefined, type: RelationshipType.outgoing, id }); if (friendRequest) { if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); @@ -113,7 +113,7 @@ router.put("/:id", check({ $type: new Length(Number, 1, 4) }), async (req: Reque return await updateRelationship( req, res, - await User.findOneOrFail(req.params.id, { relations: ["relationships"], select: userProjection }), + await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships"], select: userProjection }), req.body.type ); }); @@ -135,8 +135,8 @@ router.delete("/:id", async (req: Request, res: Response) => { const { id } = req.params; if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend"); - const user = await User.findOneOrFail(req.user_id, { select: userProjection, relations: ["relationships"] }); - const friend = await User.findOneOrFail(id, { select: userProjection, relations: ["relationships"] }); + const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] }); + const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] }); const relationship = user.relationships.find((x) => x.id === id); const friendRequest = friend.relationships.find((x) => x.id === req.user_id); diff --git a/api/src/schema/Message.ts b/api/src/schema/Message.ts index bf10c037..742542df 100644 --- a/api/src/schema/Message.ts +++ b/api/src/schema/Message.ts @@ -81,7 +81,7 @@ export interface MessageCreateSchema { message_id: string; channel_id: string; guild_id?: string; - fail_if_not_exists: boolean; + fail_if_not_exists?: boolean; }; payload_json?: string; file?: any; diff --git a/bundle/package-lock.json b/bundle/package-lock.json index 9fcb5490..59fd96ee 100644 --- a/bundle/package-lock.json +++ b/bundle/package-lock.json @@ -16,6 +16,7 @@ "@fosscord/util": "file:../util", "async-exit-hook": "^2.0.1", "express": "^4.17.1", + "missing-native-js-functions": "^1.2.13", "mongodb-memory-server": "^7.3.6", "node-os-utils": "^1.3.5" }, @@ -70,6 +71,7 @@ "mongoose-long": "^0.3.2", "multer": "^1.4.2", "node-fetch": "^2.6.1", + "supertest": "^6.1.6", "typeorm": "^0.2.37" }, "devDependencies": { @@ -1224,6 +1226,11 @@ "node": "*" } }, + "node_modules/missing-native-js-functions": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.13.tgz", + "integrity": "sha512-1RAArfUkrGkj5N3xJVW251F2PvfP2ozAcxsLLDR6uiiAixTP5Abh8zzGMadepbqgiHC0FGlTSAUNbh9abN4Osg==" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -1916,6 +1923,7 @@ "multer": "^1.4.2", "node-fetch": "^2.6.1", "saslprep": "^1.0.3", + "supertest": "^6.1.6", "ts-node": "^9.1.1", "ts-node-dev": "^1.1.6", "typeorm": "^0.2.37", @@ -2800,6 +2808,11 @@ "brace-expansion": "^1.1.7" } }, + "missing-native-js-functions": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.13.tgz", + "integrity": "sha512-1RAArfUkrGkj5N3xJVW251F2PvfP2ozAcxsLLDR6uiiAixTP5Abh8zzGMadepbqgiHC0FGlTSAUNbh9abN4Osg==" + }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", diff --git a/bundle/package.json b/bundle/package.json index 71d7efea..63945135 100644 --- a/bundle/package.json +++ b/bundle/package.json @@ -51,6 +51,7 @@ "@fosscord/util": "file:../util", "async-exit-hook": "^2.0.1", "express": "^4.17.1", + "missing-native-js-functions": "^1.2.13", "mongodb-memory-server": "^7.3.6", "node-os-utils": "^1.3.5" } -- cgit 1.5.1