1
0
mirror of https://github.com/spacebarchat/server.git synced 2024-09-20 09:41:35 +02:00

update util

This commit is contained in:
Flam3rboy 2021-08-29 00:03:58 +02:00
parent e99008a1a5
commit 766dcc24aa
35 changed files with 853 additions and 6515 deletions

View File

@ -18,6 +18,7 @@
"type": "node",
"request": "launch",
"runtimeArgs": ["--inspect-brk", "${workspaceRoot}/node_modules/jest/bin/jest.js", "--runInBand"],
"preLaunchTask": "tsc: build - tsconfig.json",
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen",
"port": 9229

313
util/package-lock.json generated
View File

@ -16,10 +16,11 @@
"dot-prop": "^6.0.1",
"env-paths": "^2.2.1",
"jsonwebtoken": "^8.5.1",
"lambert-server": "^1.2.8",
"missing-native-js-functions": "^1.2.10",
"lambert-server": "^1.2.10",
"missing-native-js-functions": "^1.2.11",
"node-fetch": "^2.6.1",
"patch-package": "^6.4.7",
"pg": "^8.7.1",
"reflect-metadata": "^0.1.13",
"sqlite3": "^5.0.2",
"typeorm": "^0.2.37",
@ -1765,6 +1766,14 @@
"resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz",
"integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg=="
},
"node_modules/buffer-writer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
"engines": {
"node": ">=4"
}
},
"node_modules/bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@ -6098,16 +6107,16 @@
}
},
"node_modules/lambert-server": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.8.tgz",
"integrity": "sha512-vi/Ku/QudY+WIdGO9bc0qLfVhfuJFWXk1+etesPW1vW29sPbmevLL6IwfvCtw+/MyzRAJLOyCBfQ310a68+2QQ==",
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.10.tgz",
"integrity": "sha512-BHGPmpUrRklFJHPu0vAA8NBewtEd4IX80FRpV4nX9z8kHTUYHqnYHoBeUEWoUmxAeFQvQae1Axk5RQXRQk4VNw==",
"dependencies": {
"body-parser": "^1.19.0",
"chalk": "^4.1.1",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"helmet": "^4.4.1",
"missing-native-js-functions": "^1.1.8"
"missing-native-js-functions": "^1.2.11"
}
},
"node_modules/leven": {
@ -6329,9 +6338,9 @@
}
},
"node_modules/missing-native-js-functions": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.10.tgz",
"integrity": "sha512-sq+oAw/C3OtUyKopLNOf/+U85YNx7db6fy5nVfGVKlGdcV8tX24GjOSkcZeCAnAIjMEnlQBWTr17JXa3OJj22g=="
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.11.tgz",
"integrity": "sha512-U97IscNBL4Wg9adYjEBT46Hb0Ld5dPT8vbdwFX+TNzGrFQCc4WqoGAZouaLNFwUqxzzHZ9DVg59unwnQyeIIQg=="
},
"node_modules/mkdirp": {
"version": "1.0.4",
@ -6916,6 +6925,11 @@
"node": ">=6"
}
},
"node_modules/packet-reader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
},
"node_modules/parent-require": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz",
@ -7065,6 +7079,80 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"optional": true
},
"node_modules/pg": {
"version": "8.7.1",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz",
"integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==",
"dependencies": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-connection-string": "^2.5.0",
"pg-pool": "^3.4.1",
"pg-protocol": "^1.5.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
},
"engines": {
"node": ">= 8.0.0"
},
"peerDependencies": {
"pg-native": ">=2.0.0"
},
"peerDependenciesMeta": {
"pg-native": {
"optional": true
}
}
},
"node_modules/pg-connection-string": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
},
"node_modules/pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/pg-pool": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz",
"integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==",
"peerDependencies": {
"pg": ">=8.0"
}
},
"node_modules/pg-protocol": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
"integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
},
"node_modules/pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"dependencies": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/pgpass": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz",
"integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==",
"dependencies": {
"split2": "^3.1.1"
}
},
"node_modules/picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
@ -7088,6 +7176,41 @@
"node": ">= 6"
}
},
"node_modules/postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
"engines": {
"node": ">=4"
}
},
"node_modules/postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"dependencies": {
"xtend": "^4.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -7604,6 +7727,35 @@
"memory-pager": "^1.0.2"
}
},
"node_modules/split2": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
"integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
"dependencies": {
"readable-stream": "^3.0.0"
}
},
"node_modules/split2/node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/split2/node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@ -8492,6 +8644,14 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"engines": {
"node": ">=0.4"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@ -10030,6 +10190,11 @@
"resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-1.0.0.tgz",
"integrity": "sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg=="
},
"buffer-writer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
"integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw=="
},
"bytes": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
@ -13373,16 +13538,16 @@
"dev": true
},
"lambert-server": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.8.tgz",
"integrity": "sha512-vi/Ku/QudY+WIdGO9bc0qLfVhfuJFWXk1+etesPW1vW29sPbmevLL6IwfvCtw+/MyzRAJLOyCBfQ310a68+2QQ==",
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/lambert-server/-/lambert-server-1.2.10.tgz",
"integrity": "sha512-BHGPmpUrRklFJHPu0vAA8NBewtEd4IX80FRpV4nX9z8kHTUYHqnYHoBeUEWoUmxAeFQvQae1Axk5RQXRQk4VNw==",
"requires": {
"body-parser": "^1.19.0",
"chalk": "^4.1.1",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"helmet": "^4.4.1",
"missing-native-js-functions": "^1.1.8"
"missing-native-js-functions": "^1.2.11"
}
},
"leven": {
@ -13570,9 +13735,9 @@
}
},
"missing-native-js-functions": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.10.tgz",
"integrity": "sha512-sq+oAw/C3OtUyKopLNOf/+U85YNx7db6fy5nVfGVKlGdcV8tX24GjOSkcZeCAnAIjMEnlQBWTr17JXa3OJj22g=="
"version": "1.2.11",
"resolved": "https://registry.npmjs.org/missing-native-js-functions/-/missing-native-js-functions-1.2.11.tgz",
"integrity": "sha512-U97IscNBL4Wg9adYjEBT46Hb0Ld5dPT8vbdwFX+TNzGrFQCc4WqoGAZouaLNFwUqxzzHZ9DVg59unwnQyeIIQg=="
},
"mkdirp": {
"version": "1.0.4",
@ -14013,6 +14178,11 @@
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"dev": true
},
"packet-reader": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
"integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
},
"parent-require": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/parent-require/-/parent-require-1.0.0.tgz",
@ -14136,6 +14306,61 @@
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"optional": true
},
"pg": {
"version": "8.7.1",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz",
"integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==",
"requires": {
"buffer-writer": "2.0.0",
"packet-reader": "1.0.0",
"pg-connection-string": "^2.5.0",
"pg-pool": "^3.4.1",
"pg-protocol": "^1.5.0",
"pg-types": "^2.1.0",
"pgpass": "1.x"
}
},
"pg-connection-string": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
"integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
},
"pg-int8": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
"integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw=="
},
"pg-pool": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz",
"integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==",
"requires": {}
},
"pg-protocol": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
"integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
},
"pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
"integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
"requires": {
"pg-int8": "1.0.1",
"postgres-array": "~2.0.0",
"postgres-bytea": "~1.0.0",
"postgres-date": "~1.0.4",
"postgres-interval": "^1.1.0"
}
},
"pgpass": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz",
"integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==",
"requires": {
"split2": "^3.1.1"
}
},
"picomatch": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
@ -14150,6 +14375,29 @@
"node-modules-regexp": "^1.0.0"
}
},
"postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
"integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="
},
"postgres-bytea": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
"integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU="
},
"postgres-date": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
"integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="
},
"postgres-interval": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
"integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
"requires": {
"xtend": "^4.0.0"
}
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -14564,6 +14812,34 @@
"memory-pager": "^1.0.2"
}
},
"split2": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
"integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
"requires": {
"readable-stream": "^3.0.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
}
}
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@ -15187,6 +15463,11 @@
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
"dev": true
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",

View File

@ -9,8 +9,7 @@
"patch": "patch-package",
"test": "npm run build && jest",
"postinstall": "npm run patch && npm run build",
"build": "npx tsc -b .",
"generate:schema": "npx typescript-json-schema tsconfig.json '*' -o src/entities/schema.json"
"build": "npx tsc -b ."
},
"repository": {
"type": "git",
@ -44,10 +43,11 @@
"dot-prop": "^6.0.1",
"env-paths": "^2.2.1",
"jsonwebtoken": "^8.5.1",
"lambert-server": "^1.2.8",
"missing-native-js-functions": "^1.2.10",
"lambert-server": "^1.2.10",
"missing-native-js-functions": "^1.2.11",
"node-fetch": "^2.6.1",
"patch-package": "^6.4.7",
"pg": "^8.7.1",
"reflect-metadata": "^0.1.13",
"sqlite3": "^5.0.2",
"typeorm": "^0.2.37",

View File

@ -8,13 +8,13 @@ export class Application extends BaseClass {
@Column()
name: string;
@Column()
@Column({ nullable: true })
icon?: string;
@Column()
description: string;
@Column("simple-array")
@Column({ type: "simple-array", nullable: true })
rpc_origins?: string[];
@Column()
@ -23,16 +23,16 @@ export class Application extends BaseClass {
@Column()
bot_require_code_grant: boolean;
@Column()
@Column({ nullable: true })
terms_of_service_url?: string;
@Column()
@Column({ nullable: true })
privacy_policy_url?: string;
@Column()
owner_id: string;
@Column()
@Column({ nullable: true })
summary?: string;
@Column()
@ -52,13 +52,13 @@ export class Application extends BaseClass {
@ManyToOne(() => Guild, (guild: Guild) => guild.id)
guild: Guild; // if this application is a game sold, this field will be the guild to which it has been linked
@Column()
@Column({ nullable: true })
primary_sku_id?: string; // if this application is a game sold, this field will be the id of the "Game SKU" that is created,
@Column()
@Column({ nullable: true })
slug?: string; // if this application is a game sold, this field will be the URL slug that links to the store page
@Column()
@Column({ nullable: true })
cover_image?: string; // the application's default rich presence invite cover image hash
@Column()

View File

@ -46,7 +46,7 @@ export class AuditLogEntry extends BaseClass {
@RelationId((auditlog: AuditLogEntry) => auditlog.target)
target_id: string;
@JoinColumn({ name: "user_id" })
@JoinColumn({ name: "target_id" })
@ManyToOne(() => User, (user: User) => user.id)
target?: User;
@ -63,7 +63,7 @@ export class AuditLogEntry extends BaseClass {
})
action_type: AuditLogEvents;
@Column("simple-json")
@Column({ type: "simple-json", nullable: true })
options?: {
delete_member_days?: string;
members_removed?: string;
@ -76,10 +76,10 @@ export class AuditLogEntry extends BaseClass {
};
@Column()
@Column("simple-json")
@Column({ type: "simple-json" })
changes: AuditLogChange[];
@Column()
@Column({ nullable: true })
reason?: string;
}

View File

@ -29,6 +29,6 @@ export class Ban extends BaseClass {
@Column()
ip: string;
@Column()
@Column({ nullable: true })
reason?: string;
}

View File

@ -1,34 +1,26 @@
import "reflect-metadata";
import { BaseEntity, BeforeInsert, BeforeUpdate, PrimaryColumn } from "typeorm";
import {
BaseEntity,
BeforeInsert,
BeforeUpdate,
EntityMetadata,
FindConditions,
FindManyOptions,
PrimaryColumn,
} from "typeorm";
import { Snowflake } from "../util/Snowflake";
import Ajv, { ValidateFunction } from "ajv";
import schema from "./schema.json";
import "missing-native-js-functions";
// TODO use class-validator https://typeorm.io/#/validation with class annotators (isPhone/isEmail) combined with types from typescript-json-schema
// btw. we don't use class-validator for everything, because we need to explicitly set the type instead of deriving it from typescript also it doesn't easily support nested objects
const ajv = new Ajv({
removeAdditional: "all",
useDefaults: true,
coerceTypes: true,
// @ts-ignore
validateFormats: false,
allowUnionTypes: true,
});
export class BaseClass extends BaseEntity {
@PrimaryColumn()
id: string;
// @ts-ignore
constructor(props?: any, public opts: { id?: string } = {}) {
constructor(public props?: any, public opts: { id?: string } = {}) {
super();
this.assign(props);
if (!this.construct.schema) {
this.construct.schema = ajv.compile({ ...schema, $ref: `#/definitions/${this.construct.name}` });
}
this.id = this.opts.id || Snowflake.generate();
}
@ -38,19 +30,20 @@ export class BaseClass extends BaseEntity {
}
get metadata() {
return this.construct.getRepository().metadata;
return this.construct.getRepository().metadata as EntityMetadata;
}
assign(props: any) {
if (!props || typeof props !== "object") return;
delete props.id;
delete props.opts;
delete props.props;
const properties = new Set(this.metadata.columns.map((x: any) => x.propertyName));
// will not include relational properties (e.g. @RelationId @ManyToMany)
for (const key in props) {
if (this.hasOwnProperty(key)) continue;
if (!properties.has(key)) continue;
// @ts-ignore
const setter = this[`set${key.capitalize()}`];
@ -66,8 +59,7 @@ export class BaseClass extends BaseEntity {
@BeforeUpdate()
@BeforeInsert()
validate() {
const valid = this.construct.schema(this.toJSON());
if (!valid) throw ajv.errors;
this.assign(this.props);
return this;
}
@ -75,4 +67,14 @@ export class BaseClass extends BaseEntity {
// @ts-ignore
return Object.fromEntries(this.metadata.columns.map((x) => [x.propertyName, this[x.propertyName]]));
}
static increment<T extends BaseClass>(conditions: FindConditions<T>, propertyPath: string, value: number | string) {
const repository = this.getRepository();
return repository.increment(conditions, propertyPath, value);
}
static decrement<T extends BaseClass>(conditions: FindConditions<T>, propertyPath: string, value: number | string) {
const repository = this.getRepository();
return repository.decrement(conditions, propertyPath, value);
}
}

View File

@ -60,25 +60,25 @@ export class Channel extends BaseClass {
@ManyToOne(() => User, (user: User) => user.id)
owner: User;
@Column()
@Column({ nullable: true })
last_pin_timestamp?: number;
@Column()
@Column({ nullable: true })
default_auto_archive_duration?: number;
@Column()
position: number;
@Column("simple-json")
@Column({ type: "simple-json" })
permission_overwrites: ChannelPermissionOverwrite[];
@Column()
@Column({ nullable: true })
video_quality_mode?: number;
@Column()
@Column({ nullable: true })
bitrate?: number;
@Column()
@Column({ nullable: true })
user_limit?: number;
@Column()
@ -87,7 +87,7 @@ export class Channel extends BaseClass {
@Column()
rate_limit_per_user: number;
@Column()
@Column({ nullable: true })
topic?: string;
}

View File

@ -5,7 +5,7 @@ import { Snowflake } from "../util/Snowflake";
@Entity("config")
export class ConfigEntity extends BaseClass {
@Column("simple-json")
@Column({ type: "simple-json" })
value: ConfigValue;
}

View File

@ -1,21 +1,29 @@
import { Column, Entity } from "typeorm";
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { User } from "./User";
@Entity("connected_accounts")
export class ConnectedAccount extends BaseClass {
@Column()
@RelationId((account: ConnectedAccount) => account.user)
user_id: string;
@JoinColumn({ name: "user_id" })
@ManyToOne(() => User, (user: User) => user.connected_accounts)
user: User;
@Column({ select: false })
access_token: string;
@Column()
@Column({ select: false })
friend_sync: boolean;
@Column()
name: string;
@Column()
@Column({ select: false })
revoked: boolean;
@Column()
@Column({ select: false })
show_activity: boolean;
@Column()
@ -24,6 +32,6 @@ export class ConnectedAccount extends BaseClass {
@Column()
verifie: boolean;
@Column()
@Column({ select: false })
visibility: number;
}

View File

@ -9,7 +9,7 @@ export class Emoji extends BaseClass {
animated: boolean;
@Column()
available: boolean;
available: boolean; // whether this emoji can be used, may be false due to loss of Server Boosts
@Column()
guild_id: string;
@ -27,9 +27,6 @@ export class Emoji extends BaseClass {
@Column()
require_colons: boolean;
@Column()
url: string;
@RelationId((emoji: Emoji) => emoji.roles)
role_ids: string[];

View File

@ -17,51 +17,51 @@ export class Guild extends BaseClass {
@ManyToOne(() => Channel, (channel: Channel) => channel.id)
afk_channel?: Channel;
@Column()
@Column({ nullable: true })
afk_timeout?: number;
// * commented out -> use owner instead
// application id of the guild creator if it is bot-created
// @Column()
// @Column({ nullable: true })
// application?: string;
@Column()
@Column({ nullable: true })
banner?: string;
@Column()
@Column({ nullable: true })
default_message_notifications?: number;
@Column()
@Column({ nullable: true })
description?: string;
@Column()
@Column({ nullable: true })
discovery_splash?: string;
@Column()
@Column({ nullable: true })
explicit_content_filter?: number;
@Column("simple-array")
@Column({ type: "simple-array" })
features: string[];
@Column()
@Column({ nullable: true })
icon?: string;
@Column()
@Column({ nullable: true })
large?: boolean;
@Column()
@Column({ nullable: true })
max_members?: number; // e.g. default 100.000
@Column()
@Column({ nullable: true })
max_presences?: number;
@Column()
@Column({ nullable: true })
max_video_channel_users?: number; // ? default: 25, is this max 25 streaming or watching
@Column()
@Column({ nullable: true })
member_count?: number;
@Column()
@Column({ nullable: true })
presence_count?: number; // users online
@RelationId((guild: Guild) => guild.members)
@ -99,7 +99,7 @@ export class Guild extends BaseClass {
@ManyToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.id)
voice_states: VoiceState[];
@Column()
@Column({ nullable: true })
mfa_level?: number;
@Column()
@ -112,13 +112,13 @@ export class Guild extends BaseClass {
@ManyToOne(() => User, (user: User) => user.id)
owner: User;
@Column()
@Column({ nullable: true })
preferred_locale?: string; // only community guilds can choose this
@Column()
@Column({ nullable: true })
premium_subscription_count?: number;
@Column()
@Column({ nullable: true })
premium_tier?: number; // nitro boost level
@RelationId((guild: Guild) => guild.public_updates_channel)
@ -135,10 +135,10 @@ export class Guild extends BaseClass {
@ManyToOne(() => Channel, (channel: Channel) => channel.id)
rules_channel?: string;
@Column()
@Column({ nullable: true })
region?: string;
@Column()
@Column({ nullable: true })
splash?: string;
@RelationId((guild: Guild) => guild.system_channel)
@ -148,10 +148,10 @@ export class Guild extends BaseClass {
@ManyToMany(() => Channel, (channel: Channel) => channel.id)
system_channel?: Channel;
@Column()
@Column({ nullable: true })
system_channel_flags?: number;
@Column()
@Column({ nullable: true })
unavailable?: boolean;
@RelationId((guild: Guild) => guild.vanity_url)
@ -161,10 +161,10 @@ export class Guild extends BaseClass {
@OneToOne(() => Invite, (invite: Invite) => invite.code)
vanity_url?: Invite;
@Column()
@Column({ nullable: true })
verification_level?: number;
@Column("simple-json")
@Column({ type: "simple-json" })
welcome_screen: {
enabled: boolean;
description: string;
@ -183,6 +183,6 @@ export class Guild extends BaseClass {
@ManyToOne(() => Channel, (channel: Channel) => channel.id)
widget_channel?: Channel;
@Column()
@Column({ nullable: true })
widget_enabled?: boolean;
}

View File

@ -1,4 +1,4 @@
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { Channel } from "./Channel";
import { Guild } from "./Guild";
@ -6,7 +6,7 @@ import { User } from "./User";
@Entity("invites")
export class Invite extends BaseClass {
@Column()
@PrimaryColumn()
code: string;
@Column()
@ -55,6 +55,6 @@ export class Invite extends BaseClass {
@ManyToOne(() => User, (user: User) => user.id)
target_user?: string; // could be used for "User specific invites" https://github.com/fosscord/fosscord/issues/62
@Column()
@Column({ nullable: true })
target_user_type?: number;
}

View File

@ -1,7 +1,17 @@
import { PublicUser, User } from "./User";
import { BaseClass } from "./BaseClass";
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, RelationId } from "typeorm";
import { Guild } from "./Guild";
import { Config, emitEvent } from "../util";
import {
GuildCreateEvent,
GuildDeleteEvent,
GuildMemberAddEvent,
GuildMemberRemoveEvent,
GuildMemberUpdateEvent,
} from "../interfaces";
import { HTTPError } from "lambert-server";
import { Role } from "./Role";
@Entity("members")
export class Member extends BaseClass {
@ -19,16 +29,20 @@ export class Member extends BaseClass {
@ManyToOne(() => Guild, (guild: Guild) => guild.id)
guild: Guild;
@Column()
@Column({ nullable: true })
nick?: string;
@Column("simple-array")
roles: string[];
@RelationId((member: Member) => member.roles)
role_ids: string[];
@JoinColumn({ name: "role_ids" })
@ManyToMany(() => Role)
roles: Role[];
@Column()
joined_at: Date;
@Column()
@Column({ nullable: true })
premium_since?: number;
@Column()
@ -40,12 +54,180 @@ export class Member extends BaseClass {
@Column()
pending: boolean;
@Column("simple-json")
@Column({ type: "simple-json" })
settings: UserGuildSettings;
// TODO: update
@Column("simple-json")
@Column({ type: "simple-json" })
read_state: Record<string, string | null>;
static async IsInGuildOrFail(user_id: string, guild_id: string) {
if (await Member.count({ id: user_id, guild_id })) return true;
throw new HTTPError("You are not member of this guild", 403);
}
static async removeFromGuild(user_id: string, guild_id: string) {
const guild = await Guild.findOneOrFail({ select: ["owner_id"], where: { id: guild_id } });
if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild");
const member = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user"] });
// use promise all to execute all promises at the same time -> save time
return Promise.all([
Member.delete({
id: user_id,
guild_id: guild_id,
}),
Guild.decrement({ id: guild_id }, "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: member.user,
},
guild_id: guild_id,
} as GuildMemberRemoveEvent),
]);
}
static async addRole(user_id: string, guild_id: string, role_id: string) {
const [member] = await Promise.all([
Member.findOneOrFail({
where: { id: user_id, guild_id: guild_id },
relations: ["user"], // we don't want to load the role objects just the ids
}),
await Role.findOneOrFail({ id: role_id, guild_id: guild_id }),
]);
member.role_ids.push(role_id);
member.save();
await emitEvent({
event: "GUILD_MEMBER_UPDATE",
data: {
guild_id: guild_id,
user: member.user,
roles: member.role_ids,
},
guild_id: guild_id,
} as GuildMemberUpdateEvent);
}
static async removeRole(user_id: string, guild_id: string, role_id: string) {
const [member] = await Promise.all([
Member.findOneOrFail({
where: { id: user_id, guild_id: guild_id },
relations: ["user"], // we don't want to load the role objects just the ids
}),
await Role.findOneOrFail({ id: role_id, guild_id: guild_id }),
]);
member.role_ids.remove(role_id);
member.save();
await emitEvent({
event: "GUILD_MEMBER_UPDATE",
data: {
guild_id: guild_id,
user: member.user,
roles: member.role_ids,
},
guild_id: guild_id,
} as GuildMemberUpdateEvent);
}
static async changeNickname(user_id: string, guild_id: string, nickname: string) {
const member = await Member.findOneOrFail({
where: {
id: user_id,
guild_id: guild_id,
},
relations: ["user"],
});
member.nick = nickname;
await Promise.all([
member.save(),
emitEvent({
event: "GUILD_MEMBER_UPDATE",
data: {
guild_id: guild_id,
user: member.user,
nick: nickname,
},
guild_id: guild_id,
} as GuildMemberUpdateEvent),
]);
}
static async addToGuild(user_id: string, guild_id: string) {
const user = await User.getPublicUser(user_id);
const { maxGuilds } = Config.get().limits.user;
const guild_count = await Member.count({ id: user_id });
if (guild_count >= maxGuilds) {
throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403);
}
const guild = await Guild.findOneOrFail(guild_id, {
relations: ["channels", "emojis", "members", "roles", "stickers"],
});
if (await Member.count({ 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,
};
// @ts-ignore
guild.joined_at = member.joined_at;
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(),
Guild.increment({ id: guild_id }, "member_count", 1),
emitEvent({
event: "GUILD_MEMBER_ADD",
data: {
...member,
user,
guild_id: guild_id,
},
guild_id: guild_id,
} as GuildMemberAddEvent),
emitEvent({
event: "GUILD_CREATE",
data: guild,
user_id,
} as GuildCreateEvent),
]);
}
}
export interface UserGuildSettings {
@ -69,19 +251,31 @@ export interface MuteConfig {
selected_time_window: number;
}
// @ts-ignore
export interface PublicMember extends Omit<Member, "settings" | "id" | "read_state"> {
user: PublicUser;
}
export type PublicMemberKeys =
| "id"
| "guild_id"
| "nick"
| "roles"
| "joined_at"
| "pending"
| "deaf"
| "mute"
| "premium_since";
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 const PublicMemberProjection: PublicMemberKeys[] = [
"id",
"guild_id",
"nick",
"roles",
"joined_at",
"pending",
"deaf",
"mute",
"premium_since",
];
// @ts-ignore
export type PublicMember = Pick<Member, Omit<PublicMemberKeys, "roles">> & {
user: PublicUser;
roles: string[]; // only role ids not objects
};

View File

@ -17,6 +17,7 @@ import {
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
import { Webhook } from "./Webhook";
import { Sticker } from "./Sticker";
export enum MessageType {
DEFAULT = 0,
@ -51,7 +52,7 @@ export class Message extends BaseClass {
channel: Channel;
@RelationId((message: Message) => message.guild)
guild_id: string;
guild_id?: string;
@JoinColumn({ name: "guild_id" })
@ManyToOne(() => Guild, (guild: Guild) => guild.id)
@ -85,7 +86,7 @@ export class Message extends BaseClass {
@ManyToOne(() => Application, (application: Application) => application.id)
application?: Application;
@Column()
@Column({ nullable: true })
content?: string;
@Column()
@ -96,18 +97,18 @@ export class Message extends BaseClass {
@UpdateDateColumn()
edited_timestamp?: Date;
@Column()
@Column({ nullable: true })
tts?: boolean;
@Column()
@Column({ nullable: true })
mention_everyone?: boolean;
@RelationId((message: Message) => message.mention_users)
@RelationId((message: Message) => message.mentions)
mention_user_ids: string[];
@JoinColumn({ name: "mention_user_ids" })
@ManyToMany(() => User, (user: User) => user.id)
mention_users: User[];
mentions: User[];
@RelationId((message: Message) => message.mention_roles)
mention_role_ids: string[];
@ -123,44 +124,52 @@ export class Message extends BaseClass {
@ManyToMany(() => Channel, (channel: Channel) => channel.id)
mention_channels: Channel[];
@Column("simple-json")
@Column({ type: "simple-json" })
attachments: Attachment[];
@Column("simple-json")
@Column({ type: "simple-json" })
embeds: Embed[];
@Column("simple-json")
@Column({ type: "simple-json" })
reactions: Reaction[];
@Column({ type: "text" })
@Column({ type: "text", nullable: true })
nonce?: string | number;
@Column()
@Column({ nullable: true })
pinned?: boolean;
@Column({ type: "simple-enum", enum: MessageType })
type: MessageType;
@Column("simple-json")
@Column({ type: "simple-json", nullable: true })
activity?: {
type: number;
party_id: string;
};
@Column({ type: "bigint" })
@Column({ type: "bigint", nullable: true })
flags?: bigint;
@Column("simple-json")
stickers?: any[];
@RelationId((message: Message) => message.stickers)
sticker_ids: string[];
@Column("simple-json")
@JoinColumn({ name: "sticker_ids" })
@ManyToMany(() => Sticker, (sticker: Sticker) => sticker.id)
stickers?: Sticker[];
@Column({ type: "simple-json", nullable: true })
message_reference?: {
message_id: string;
channel_id?: string;
guild_id?: string;
};
@Column("simple-json")
@JoinColumn({ name: "message_reference_id" })
@ManyToOne(() => Message, (message: Message) => message.id)
referenced_message?: Message;
@Column({ type: "simple-json", nullable: true })
interaction?: {
id: string;
type: InteractionType;
@ -169,7 +178,7 @@ export class Message extends BaseClass {
// user: User; // TODO: autopopulate user
};
@Column("simple-json")
@Column({ type: "simple-json" })
components: MessageComponent[];
}

View File

@ -31,7 +31,7 @@ export class ReadState extends BaseClass {
@ManyToOne(() => Message, (message: Message) => message.id)
last_message?: Message;
@Column()
@Column({ nullable: true })
last_pin_timestamp?: Date;
@Column()

View File

@ -1,4 +1,4 @@
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { User } from "./User";
@ -15,10 +15,10 @@ export class Relationship extends BaseClass {
user_id: string;
@JoinColumn({ name: "user_id" })
@ManyToOne(() => User, (user: User) => user.id)
@ManyToOne(() => User, (user: User) => user.relationships)
user: User;
@Column()
@Column({ nullable: true })
nickname?: string;
@Column({ type: "simple-enum", enum: RelationshipType })

View File

@ -32,7 +32,7 @@ export class Role extends BaseClass {
@Column()
position: number;
@Column({ type: "simple-json" })
@Column({ type: "simple-json", nullable: true })
tags?: {
bot_id?: string;
integration_id?: string;

View File

@ -0,0 +1,42 @@
import { Column, Entity, JoinColumn, ManyToOne } from "typeorm";
import { BaseClass } from "./BaseClass";
import { Guild } from "./Guild";
export enum StickerType {
STANDARD = 1,
GUILD = 2,
}
export enum StickerFormatType {
PNG = 1,
APNG = 2,
LOTTIE = 3,
}
@Entity("stickers")
export class Sticker extends BaseClass {
@Column()
name: string;
@Column({ nullable: true })
description?: string;
@Column()
tags: string;
@Column()
pack_id: string;
@Column({ nullable: true })
guild_id?: string;
@JoinColumn({ name: "guild_id" })
@ManyToOne(() => Guild, (guild: Guild) => guild.id)
guild?: Guild;
@Column({ type: "simple-enum", enum: StickerType })
type: StickerType;
@Column({ type: "simple-enum", enum: StickerFormatType })
format_type: StickerFormatType;
}

View File

@ -5,7 +5,7 @@ import { User } from "./User";
@Entity("teams")
export class Team extends BaseClass {
@Column()
@Column({ nullable: true })
icon?: string;
@RelationId((team: Team) => team.members)

View File

@ -12,7 +12,7 @@ export class TeamMember extends BaseClass {
@Column({ type: "simple-enum", enum: TeamMemberState })
membership_state: TeamMemberState;
@Column("simple-array")
@Column({ type: "simple-array" })
permissions: string[];
@RelationId((member: TeamMember) => member.team)

View File

@ -11,10 +11,10 @@ export class Template extends BaseClass {
@Column()
name: string;
@Column()
@Column({ nullable: true })
description?: string;
@Column()
@Column({ nullable: true })
usage_count?: number;
@RelationId((template: Template) => template.creator)
@ -37,6 +37,6 @@ export class Template extends BaseClass {
@ManyToOne(() => Guild, (guild: Guild) => guild.id)
source_guild: Guild;
@Column("simple-json")
@Column({ type: "simple-json" })
serialized_source_guild: Guild;
}

View File

@ -1,22 +1,34 @@
import { Column, Entity, JoinColumn, OneToMany, RelationId } from "typeorm";
import { Column, Entity, FindOneOptions, JoinColumn, OneToMany, RelationId } from "typeorm";
import { BaseClass } from "./BaseClass";
import { BitField } from "../util/BitField";
import { Relationship } from "./Relationship";
import { ConnectedAccount } from "./ConnectedAccount";
import { HTTPError } from "lambert-server";
import { Guild } from "./Guild";
export const PublicUserProjection = {
username: true,
discriminator: true,
id: true,
public_flags: true,
avatar: true,
accent_color: true,
banner: true,
bio: true,
bot: true,
};
type PublicUserKeys =
| "username"
| "discriminator"
| "id"
| "public_flags"
| "avatar"
| "accent_color"
| "banner"
| "bio"
| "bot";
export const PublicUserProjection: PublicUserKeys[] = [
"username",
"discriminator",
"id",
"public_flags",
"avatar",
"accent_color",
"banner",
"bio",
"bot",
];
// Private user data that should never get sent to the client
export type PublicUser = Pick<User, PublicUserKeys>;
@Entity("users")
export class User extends BaseClass {
@ -30,115 +42,145 @@ export class User extends BaseClass {
const number = Number(val);
if (isNaN(number)) throw new Error("invalid discriminator");
if (number <= 0 || number > 10000) throw new Error("discriminator must be between 1 and 9999");
this.discriminator = val.toString();
this.discriminator = val.toString().padStart(4, "0");
}
@Column()
@Column({ nullable: true })
avatar?: string; // hash of the user avatar
@Column()
accent_color?: number; // banner color of user
@Column({ nullable: true })
accent_color?: number = 0; // banner color of user
@Column()
@Column({ nullable: true })
banner?: string; // hash of the user banner
@Column()
@Column({ nullable: true })
phone?: string; // phone number of the user
@Column()
desktop: boolean; // if the user has desktop app installed
desktop: boolean = false; // if the user has desktop app installed
@Column()
mobile: boolean; // if the user has mobile app installed
mobile: boolean = false; // if the user has mobile app installed
@Column()
premium: boolean; // if user bought nitro
premium: boolean = false; // if user bought nitro
@Column()
premium_type: number; // nitro level
premium_type: number = 0; // nitro level
@Column()
bot: boolean; // if user is bot
bot: boolean = false; // if user is bot
@Column()
bio: string; // short description of the user (max 190 chars -> should be configurable)
bio: string = ""; // short description of the user (max 190 chars -> should be configurable)
@Column()
system: boolean; // shouldn't be used, the api sents this field type true, if the generated message comes from a system generated author
system: boolean = false; // shouldn't be used, the api sents this field type true, if the generated message comes from a system generated author
@Column()
nsfw_allowed: boolean; // if the user is older than 18 (resp. Config)
nsfw_allowed: boolean = false; // if the user is older than 18 (resp. Config)
@Column()
mfa_enabled: boolean; // if multi factor authentication is enabled
mfa_enabled: boolean = false; // if multi factor authentication is enabled
@Column()
created_at: Date; // registration date
created_at: Date = new Date(); // registration date
@Column()
verified: boolean; // if the user is offically verified
verified: boolean = false; // if the user is offically verified
@Column()
disabled: boolean; // if the account is disabled
disabled: boolean = false; // if the account is disabled
@Column()
deleted: boolean; // if the user was deleted
deleted: boolean = false; // if the user was deleted
@Column()
@Column({ nullable: true })
email?: string; // email of the user
@Column({ type: "bigint" })
flags: bigint; // UserFlags
flags: bigint = BigInt(0); // UserFlags
@Column({ type: "bigint" })
public_flags: bigint;
@RelationId((user: User) => user.guilds)
guild_ids: string[]; // array of guild ids the user is part of
@JoinColumn({ name: "guild_ids" })
@OneToMany(() => Guild, (guild: Guild) => guild.id)
guilds: Guild[];
public_flags: bigint = BigInt(0);
@RelationId((user: User) => user.relationships)
relationship_ids: string[]; // array of guild ids the user is part of
@JoinColumn({ name: "relationship_ids" })
@OneToMany(() => User, (user: User) => user.id)
@OneToMany(() => Relationship, (relationship: Relationship) => relationship.user, { cascade: true })
relationships: Relationship[];
@RelationId((user: User) => user.connected_accounts)
connected_account_ids: string[]; // array of guild ids the user is part of
@JoinColumn({ name: "connected_account_ids" })
@OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.id)
@OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user)
connected_accounts: ConnectedAccount[];
@Column({ type: "simple-json", select: false })
data: {
valid_tokens_since: Date; // all tokens with a previous issue date are invalid
hash: string; // hash of the password, salt is saved in password (bcrypt)
};
hash?: string; // hash of the password, salt is saved in password (bcrypt)
} = { valid_tokens_since: new Date() };
@Column({ type: "simple-array" })
fingerprints: string[]; // array of fingerprints -> used to prevent multiple accounts
fingerprints: string[] = []; // array of fingerprints -> used to prevent multiple accounts
@Column("simple-json")
settings: UserSettings;
@Column({ type: "simple-json" })
settings: UserSettings = defaultSettings;
static async getPublicUser(user_id: string, additional_fields?: any) {
const user = await User.findOne(
{ id: user_id },
{
...PublicUserProjection,
...additional_fields,
}
);
static async getPublicUser(user_id: string, opts?: FindOneOptions<User>) {
const user = await User.findOne(user_id, {
...opts,
select: [...PublicUserProjection, ...(opts?.select || [])],
});
if (!user) throw new HTTPError("User not found", 404);
return user;
}
}
export const defaultSettings: UserSettings = {
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: "en",
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
};
export interface UserSettings {
afk_timeout: number;
allow_accessibility_detection: boolean;
@ -184,18 +226,6 @@ export interface UserSettings {
timezone_offset: number; // e.g -60
}
// Private user data that should never get sent to the client
export interface PublicUser {
id: string;
discriminator: string;
username: string;
avatar?: string;
accent_color?: number;
banner?: string;
public_flags: bigint;
bot: boolean;
}
export class UserFlags extends BitField {
static FLAGS = {
DISCORD_EMPLOYEE: BigInt(1) << BigInt(0),

View File

@ -42,7 +42,7 @@ export class VoiceState extends BaseClass {
@Column()
self_mute: boolean;
@Column()
@Column({ nullable: true })
self_stream?: boolean;
@Column()

View File

@ -18,13 +18,13 @@ export class Webhook extends BaseClass {
@Column({ type: "simple-enum", enum: WebhookType })
type: WebhookType;
@Column()
@Column({ nullable: true })
name?: string;
@Column()
@Column({ nullable: true })
avatar?: string;
@Column()
@Column({ nullable: true })
token?: string;
@RelationId((webhook: Webhook) => webhook.guild)

File diff suppressed because it is too large Load Diff

View File

@ -259,22 +259,14 @@ export interface InviteDeleteEvent extends Event {
};
}
export type MessagePayload = Omit<Message, "author_id"> & {
channel_id: string;
guild_id?: string;
author: PublicUser;
member: PublicMember;
mentions: (PublicUser & { member: PublicMember })[];
};
export interface MessageCreateEvent extends Event {
event: "MESSAGE_CREATE";
data: MessagePayload;
data: Message;
}
export interface MessageUpdateEvent extends Event {
event: "MESSAGE_UPDATE";
data: MessagePayload;
data: Message;
}
export interface MessageDeleteEvent extends Event {

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

@ -0,0 +1,19 @@
import { performance } from "perf_hooks";
import { Guild, Relationship, RelationshipType } from "./entities";
import { User } from "./entities/User";
import { initDatabase } from "./util";
initDatabase().then(async (x) => {
try {
const user = await new User(
{ guilds: [], discriminator: "1", username: "test", flags: "0", public_flags: "0" },
{ id: "0" }
).save();
user.relationships = [new Relationship({ type: RelationshipType.friends })];
user.save();
} catch (error) {
console.error(error);
}
});

View File

@ -40,6 +40,7 @@ export function enableAutoUpdate(opts: {
console.log(`[Auto update] updating ...`);
download(opts.downloadUrl, opts.path);
} else {
console.log(`[Auto update] aborted`);
}
}
);

View File

@ -14,11 +14,17 @@ export function initDatabase() {
console.log("[Database] connecting ...");
// @ts-ignore
promise = createConnection({
type: "sqlite",
database: "database.db",
// type: "sqlite",
// database: "database.db",
type: "postgres",
url: "postgres://fosscord:wb94SmuURM2Syv&@localhost/fosscord",
//
entities: Object.values(Models).filter((x) => x.constructor.name !== "Object"),
synchronize: true,
logging: false,
logging: true,
cache: {
duration: 1000 * 3, // cache all find queries for 3 seconds
},
});
promise.then((connection) => {

View File

@ -198,50 +198,41 @@ export class Permissions extends BitField {
}
export type PermissionCache = {
channel?: Channel | null;
member?: Member | null;
guild?: Guild | null;
roles?: Role[] | null;
channel?: Channel | undefined;
member?: Member | undefined;
guild?: Guild | undefined;
roles?: Role[] | undefined;
user_id?: string;
};
export async function getPermission(
user_id?: string,
guild_id?: string,
channel_id?: string,
cache: PermissionCache = {}
) {
var { channel, member, guild, roles } = cache;
export async function getPermission(user_id?: string, guild_id?: string, channel_id?: string) {
if (!user_id) throw new HTTPError("User not found");
var channel: Channel | undefined;
var member: Member | undefined;
var guild: Guild | undefined;
if (channel_id && !channel) {
if (channel_id) {
channel = await Channel.findOneOrFail(
{ id: channel_id },
{ select: ["permission_overwrites", "recipients", "owner", "guild"] }
);
if (!channel) throw new HTTPError("Channel not found", 404);
if (channel.guild_id) guild_id = channel.guild_id;
if (channel.guild_id) guild_id = channel.guild_id; // derive guild_id from the channel
}
if (guild_id) {
if (!guild) guild = await Guild.findOneOrFail({ id: guild_id }, { select: ["owner"] });
if (!guild) throw new HTTPError("Guild not found");
guild = await Guild.findOneOrFail({ id: guild_id }, { select: ["owner"] });
if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR);
if (!member) member = await Member.findOneOrFail({ guild_id, id: user_id }, { select: ["roles"] });
if (!member) throw new HTTPError("Member not found");
if (!roles) roles = await Role.find({ guild_id, id: In(member.roles) });
member = await Member.findOneOrFail({ guild_id, id: user_id }, { select: ["roles"] });
}
var permission = Permissions.finalPermission({
user: {
id: user_id,
roles: member?.roles || [],
roles: member?.role_ids || [],
},
guild: {
roles: roles || [],
roles: member?.roles || [],
},
channel: {
overwrites: channel?.permission_overwrites,
@ -253,7 +244,7 @@ export async function getPermission(
const obj = new Permissions(permission);
// pass cache to permission for possible future getPermission calls
obj.cache = { guild, member, channel, roles, user_id };
obj.cache = { guild, member, channel, roles: member?.roles, user_id };
return obj;
}

View File

@ -1,10 +1,9 @@
const { initDatabase, closeDatabase } = require("../dist/util/Database");
const { User } = require("../dist/entities/User");
jest.setTimeout(10000);
jest.setTimeout(20000);
beforeAll((done) => {
initDatabase().then(() => {
new User().validate(); // warm up schema/model
done();
});
});
@ -28,4 +27,17 @@ describe("User", () => {
new User({ discriminator: "0" }).validate();
}).toThrow();
});
test("add guild", async () => {
try {
await new User({ guilds: [], discriminator: "1" }, { id: "0" }).save();
const user = await User.find("0");
user.guilds.push(new Guild({ name: "test" }));
user.save();
} catch (error) {
console.error(error);
}
});
});

View File

@ -1,4 +1,8 @@
const { performance } = require("perf_hooks");
const fs = require("fs");
const path = require("path");
// fs.unlinkSync(path.join(__dirname, "..", "database.db"));
global.expect.extend({
toBeFasterThan: async (func, target) => {

View File

@ -1,33 +0,0 @@
const { initDatabase, closeDatabase } = require("../dist/util/Database");
const { User } = require("../dist/entities/User");
jest.setTimeout(10000);
beforeAll((done) => {
initDatabase().then(() => {
new User().validate(); // warm up schema/model
done();
});
});
afterAll(() => {
closeDatabase();
});
describe("Validate model class properties", () => {
test("object instead of string", async () => {
expect(() => {
new User({}, { id: {} }).validate();
}).toThrow();
});
test("validation should be faster than 20ms", () => {
expect(() => {
new User().validate();
}).toBeFasterThan(20);
});
test("should not set opts", () => {
const user = new User({ opts: { id: 0 } });
expect(user.opts.id).not.toBe(0);
});
});

View File

@ -1,5 +1,5 @@
{
"include": ["src/**/*.ts"],
"include": ["src/**/*.ts", "tests/Test.ts"],
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */