Merge pull request #372 from fosscord/unittests
Automatic Unittests + documentation
This commit is contained in:
commit
5823f8efb4
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6
api/babel.config.js
Normal file
6
api/babel.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
presets: [
|
||||||
|
["@babel/preset-env", { targets: { node: "current" } }],
|
||||||
|
["@babel/preset-typescript", { allowDeclareFields: true }]
|
||||||
|
]
|
||||||
|
};
|
||||||
66
api/jest/getRouteDescriptions.js
Normal file
66
api/jest/getRouteDescriptions.js
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
const { traverseDirectory } = require("lambert-server");
|
||||||
|
const path = require("path");
|
||||||
|
const express = require("express");
|
||||||
|
const RouteUtility = require("../dist/util/route");
|
||||||
|
const Router = express.Router;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some documentation.
|
||||||
|
*
|
||||||
|
* @type {Map<string, RouteUtility.RouteOptions>}
|
||||||
|
*/
|
||||||
|
const routes = new Map();
|
||||||
|
let currentPath = "";
|
||||||
|
let currentFile = "";
|
||||||
|
const methods = ["get", "post", "put", "delete", "patch"];
|
||||||
|
|
||||||
|
function registerPath(file, method, prefix, path, ...args) {
|
||||||
|
const urlPath = prefix + path;
|
||||||
|
const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts");
|
||||||
|
const opts = args.find((x) => typeof x === "object");
|
||||||
|
if (opts) {
|
||||||
|
routes.set(urlPath + "|" + method, opts); // @ts-ignore
|
||||||
|
opts.file = sourceFile;
|
||||||
|
// console.log(method, urlPath, opts);
|
||||||
|
} else {
|
||||||
|
console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function routeOptions(opts) {
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
RouteUtility.route = routeOptions;
|
||||||
|
|
||||||
|
express.Router = (opts) => {
|
||||||
|
const path = currentPath;
|
||||||
|
const file = currentFile;
|
||||||
|
const router = Router(opts);
|
||||||
|
|
||||||
|
for (const method of methods) {
|
||||||
|
router[method] = registerPath.bind(null, file, method, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return router;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = function getRouteDescriptions() {
|
||||||
|
const root = path.join(__dirname, "..", "dist", "routes", "/");
|
||||||
|
traverseDirectory({ dirname: root, recursive: true }, (file) => {
|
||||||
|
currentFile = file;
|
||||||
|
let path = file.replace(root.slice(0, -1), "");
|
||||||
|
path = path.split(".").slice(0, -1).join("."); // trancate .js/.ts file extension of path
|
||||||
|
path = path.replaceAll("#", ":").replaceAll("\\", "/"); // replace # with : for path parameters and windows paths with slashes
|
||||||
|
if (path.endsWith("/index")) path = path.slice(0, "/index".length * -1); // delete index from path
|
||||||
|
currentPath = path;
|
||||||
|
|
||||||
|
try {
|
||||||
|
require(file);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("error loading file " + file, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return routes;
|
||||||
|
};
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
const { Config, initDatabase } = require("@fosscord/util");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { FosscordServer } = require("../dist/Server");
|
const { FosscordServer } = require("../dist/Server");
|
||||||
@ -5,8 +6,12 @@ const Server = new FosscordServer({ port: 3001 });
|
|||||||
global.server = Server;
|
global.server = Server;
|
||||||
module.exports = async () => {
|
module.exports = async () => {
|
||||||
try {
|
try {
|
||||||
fs.unlinkSync(path.join(__dirname, "..", "database.db"));
|
fs.unlinkSync(path.join(process.cwd(), "database.db"));
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
|
await initDatabase();
|
||||||
|
await Config.init();
|
||||||
|
Config.get().limits.rate.disabled = true;
|
||||||
return await Server.start();
|
return await Server.start();
|
||||||
};
|
};
|
||||||
|
|
||||||
BIN
api/package-lock.json
generated
BIN
api/package-lock.json
generated
Binary file not shown.
@ -5,17 +5,17 @@
|
|||||||
"main": "dist/Server.js",
|
"main": "dist/Server.js",
|
||||||
"types": "dist/Server.d.ts",
|
"types": "dist/Server.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test:only": "node -r ./scripts/tsconfig-paths-bootstrap.js node_modules/.bin/jest --coverage --verbose --forceExit ./tests",
|
"test:only": "jest --coverage --verbose --forceExit ./tests",
|
||||||
"test": "npm run build && npm run test:only",
|
"test": "npm run build && npm run test:only",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start",
|
"start": "npm run build && node dist/start",
|
||||||
"build": "npx tsc -b .",
|
"build": "npx tsc -b .",
|
||||||
"build-docker": "tsc -p tsconfig-docker.json",
|
"build-docker": "tsc -p tsconfig-docker.json",
|
||||||
"dev": "tsnd --respawn src/start.ts",
|
"dev": "tsnd --respawn src/start.ts",
|
||||||
"patch": "npx patch-package",
|
"patch": "ts-patch install -s && npx patch-package",
|
||||||
"postinstall": "npm run patch",
|
"postinstall": "npm run patch",
|
||||||
"generate:docs": "ts-node scripts/generate_openapi_schema.ts",
|
"generate:docs": "node scripts/generate_openapi.ts",
|
||||||
"generate:schema": "ts-node scripts/generate_body_schema.ts"
|
"generate:schema": "node scripts/generate_schema.ts"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -36,11 +36,15 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://fosscord.com",
|
"homepage": "https://fosscord.com",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@babel/core": "^7.15.5",
|
||||||
|
"@babel/preset-env": "^7.15.6",
|
||||||
|
"@babel/preset-typescript": "^7.15.0",
|
||||||
"@types/amqplib": "^0.8.1",
|
"@types/amqplib": "^0.8.1",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/express": "^4.17.9",
|
"@types/express": "^4.17.9",
|
||||||
"@types/i18next-node-fs-backend": "^2.1.0",
|
"@types/i18next-node-fs-backend": "^2.1.0",
|
||||||
"@types/jest": "^27.0.1",
|
"@types/jest": "^27.0.1",
|
||||||
|
"@types/jest-expect-message": "^1.0.3",
|
||||||
"@types/jsonwebtoken": "^8.5.0",
|
"@types/jsonwebtoken": "^8.5.0",
|
||||||
"@types/mongodb": "^3.6.9",
|
"@types/mongodb": "^3.6.9",
|
||||||
"@types/mongoose": "^5.10.5",
|
"@types/mongoose": "^5.10.5",
|
||||||
@ -49,14 +53,19 @@
|
|||||||
"@types/multer": "^1.4.5",
|
"@types/multer": "^1.4.5",
|
||||||
"@types/node": "^14.17.9",
|
"@types/node": "^14.17.9",
|
||||||
"@types/node-fetch": "^2.5.7",
|
"@types/node-fetch": "^2.5.7",
|
||||||
|
"@types/supertest": "^2.0.11",
|
||||||
"@zerollup/ts-transform-paths": "^1.7.18",
|
"@zerollup/ts-transform-paths": "^1.7.18",
|
||||||
"0x": "^4.10.2",
|
"0x": "^4.10.2",
|
||||||
|
"babel-jest": "^27.2.0",
|
||||||
"caxa": "^2.1.0",
|
"caxa": "^2.1.0",
|
||||||
"image-size": "^1.0.0",
|
"image-size": "^1.0.0",
|
||||||
"jest": "^26.6.3",
|
"jest": "^26.6.3",
|
||||||
|
"jest-expect-message": "^1.0.2",
|
||||||
|
"jest-runtime": "^27.2.1",
|
||||||
"saslprep": "^1.0.3",
|
"saslprep": "^1.0.3",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "^9.1.1",
|
||||||
"ts-node-dev": "^1.1.6",
|
"ts-node-dev": "^1.1.6",
|
||||||
|
"ts-patch": "^1.4.4",
|
||||||
"typescript": "^4.4.2",
|
"typescript": "^4.4.2",
|
||||||
"typescript-json-schema": "0.50.1"
|
"typescript-json-schema": "0.50.1"
|
||||||
},
|
},
|
||||||
@ -77,7 +86,7 @@
|
|||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-validator": "^6.9.2",
|
"express-validator": "^6.9.2",
|
||||||
"form-data": "^3.0.0",
|
"form-data": "^3.0.0",
|
||||||
"i18next": "^19.8.5",
|
"i18next": "^19.9.2",
|
||||||
"i18next-http-middleware": "^3.1.3",
|
"i18next-http-middleware": "^3.1.3",
|
||||||
"i18next-node-fs-backend": "^2.1.3",
|
"i18next-node-fs-backend": "^2.1.3",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^8.5.1",
|
||||||
@ -98,7 +107,10 @@
|
|||||||
"setupFiles": [
|
"setupFiles": [
|
||||||
"<rootDir>/jest/setup.js"
|
"<rootDir>/jest/setup.js"
|
||||||
],
|
],
|
||||||
"globalSetup": "<rootDir>/scripts/globalSetup.js",
|
"setupFilesAfterEnv": [
|
||||||
|
"jest-expect-message"
|
||||||
|
],
|
||||||
|
"globalSetup": "<rootDir>/jest/globalSetup.js",
|
||||||
"verbose": true
|
"verbose": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
137
api/scripts/generate_openapi.js
Normal file
137
api/scripts/generate_openapi.js
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
// https://mermade.github.io/openapi-gui/#
|
||||||
|
// https://editor.swagger.io/
|
||||||
|
const getRouteDescriptions = require("../jest/getRouteDescriptions");
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
require("missing-native-js-functions");
|
||||||
|
|
||||||
|
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
|
||||||
|
const SchemaPath = path.join(__dirname, "..", "assets", "schemas.json");
|
||||||
|
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
|
||||||
|
const specification = JSON.parse(fs.readFileSync(openapiPath, { encoding: "utf8" }));
|
||||||
|
|
||||||
|
function combineSchemas(schemas) {
|
||||||
|
var definitions = {};
|
||||||
|
|
||||||
|
for (const name in schemas) {
|
||||||
|
definitions = {
|
||||||
|
...definitions,
|
||||||
|
...schemas[name].definitions,
|
||||||
|
[name]: { ...schemas[name], definitions: undefined, $schema: undefined }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in definitions) {
|
||||||
|
specification.components.schemas[key] = definitions[key];
|
||||||
|
delete definitions[key].additionalProperties;
|
||||||
|
delete definitions[key].$schema;
|
||||||
|
const definition = definitions[key];
|
||||||
|
|
||||||
|
if (typeof definition.properties === "object") {
|
||||||
|
for (const property of Object.values(definition.properties)) {
|
||||||
|
if (Array.isArray(property.type)) {
|
||||||
|
if (property.type.includes("null")) {
|
||||||
|
property.type = property.type.find((x) => x !== "null");
|
||||||
|
property.nullable = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return definitions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTag(key) {
|
||||||
|
return key.match(/\/([\w-]+)/)[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
function apiRoutes() {
|
||||||
|
const routes = getRouteDescriptions();
|
||||||
|
|
||||||
|
const tags = Array.from(routes.keys()).map((x) => getTag(x));
|
||||||
|
specification.tags = [...specification.tags.map((x) => x.name), ...tags].unique().map((x) => ({ name: x }));
|
||||||
|
|
||||||
|
routes.forEach((route, pathAndMethod) => {
|
||||||
|
const [p, method] = pathAndMethod.split("|");
|
||||||
|
const path = p.replace(/:(\w+)/g, "{$1}");
|
||||||
|
|
||||||
|
let obj = specification.paths[path]?.[method] || {};
|
||||||
|
if (!obj.description) {
|
||||||
|
const permission = route.permission ? `##### Requires the \`\`${route.permission}\`\` permission\n` : "";
|
||||||
|
const event = route.test?.event ? `##### Fires a \`\`${route.test?.event}\`\` event\n` : "";
|
||||||
|
obj.description = permission + event;
|
||||||
|
}
|
||||||
|
if (route.body) {
|
||||||
|
obj.requestBody = {
|
||||||
|
required: true,
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: { $ref: `#/components/schemas/${route.body}` }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.merge(obj.requestBody);
|
||||||
|
}
|
||||||
|
if (!obj.responses) {
|
||||||
|
obj.responses = {
|
||||||
|
default: {
|
||||||
|
description: "not documented"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (route.test?.response) {
|
||||||
|
const status = route.test.response.status || 200;
|
||||||
|
let schema = {
|
||||||
|
allOf: [
|
||||||
|
{
|
||||||
|
$ref: `#/components/schemas/${route.test.response.body}`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
example: route.test.body
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
if (!route.test.body) schema = schema.allOf[0];
|
||||||
|
|
||||||
|
obj.responses = {
|
||||||
|
[status]: {
|
||||||
|
...(route.test.response.body
|
||||||
|
? {
|
||||||
|
description: obj.responses[status].description || "",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: schema
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
}
|
||||||
|
}.merge(obj.responses);
|
||||||
|
delete obj.responses.default;
|
||||||
|
}
|
||||||
|
if (p.includes(":")) {
|
||||||
|
obj.parameters = p.match(/:\w+/g)?.map((x) => ({
|
||||||
|
name: x.replace(":", ""),
|
||||||
|
in: "path",
|
||||||
|
required: true,
|
||||||
|
schema: { type: "string" },
|
||||||
|
description: x.replace(":", "")
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
obj.tags = [...(obj.tags || []), getTag(p)].unique();
|
||||||
|
|
||||||
|
specification.paths[path] = { ...specification.paths[path], [method]: obj };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
combineSchemas(schemas);
|
||||||
|
apiRoutes();
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
openapiPath,
|
||||||
|
JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@ -1,99 +0,0 @@
|
|||||||
// https://mermade.github.io/openapi-gui/#
|
|
||||||
// https://editor.swagger.io/
|
|
||||||
import path from "path";
|
|
||||||
import fs from "fs";
|
|
||||||
import * as TJS from "typescript-json-schema";
|
|
||||||
import "missing-native-js-functions";
|
|
||||||
|
|
||||||
const settings: TJS.PartialArgs = {
|
|
||||||
required: true,
|
|
||||||
ignoreErrors: true,
|
|
||||||
excludePrivate: true,
|
|
||||||
defaultNumberType: "integer",
|
|
||||||
noExtraProps: true,
|
|
||||||
defaultProps: false
|
|
||||||
};
|
|
||||||
const compilerOptions: TJS.CompilerOptions = {
|
|
||||||
strictNullChecks: false
|
|
||||||
};
|
|
||||||
const openapiPath = path.join(__dirname, "..", "assets", "openapi.json");
|
|
||||||
var specification = JSON.parse(fs.readFileSync(openapiPath, { encoding: "utf8" }));
|
|
||||||
|
|
||||||
async function utilSchemas() {
|
|
||||||
const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "..", "util", "src", "index.ts")], compilerOptions);
|
|
||||||
const generator = TJS.buildGenerator(program, settings);
|
|
||||||
|
|
||||||
const schemas = ["UserPublic", "UserPrivate", "PublicConnectedAccount"];
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
combineSchemas({ schemas, generator, program });
|
|
||||||
}
|
|
||||||
|
|
||||||
function combineSchemas(opts: { program: TJS.Program; generator: TJS.JsonSchemaGenerator; schemas: string[] }) {
|
|
||||||
var definitions: any = {};
|
|
||||||
|
|
||||||
for (const name of opts.schemas) {
|
|
||||||
const part = TJS.generateSchema(opts.program, name, settings, [], opts.generator as TJS.JsonSchemaGenerator);
|
|
||||||
if (!part) continue;
|
|
||||||
|
|
||||||
definitions = { ...definitions, [name]: { ...part, definitions: undefined, $schema: undefined } };
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const key in definitions) {
|
|
||||||
specification.components.schemas[key] = definitions[key];
|
|
||||||
delete definitions[key].additionalProperties;
|
|
||||||
delete definitions[key].$schema;
|
|
||||||
}
|
|
||||||
|
|
||||||
return definitions;
|
|
||||||
}
|
|
||||||
|
|
||||||
function apiSchemas() {
|
|
||||||
const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "src", "schema", "index.ts")], compilerOptions);
|
|
||||||
const generator = TJS.buildGenerator(program, settings);
|
|
||||||
|
|
||||||
const schemas = [
|
|
||||||
"BanCreateSchema",
|
|
||||||
"DmChannelCreateSchema",
|
|
||||||
"ChannelModifySchema",
|
|
||||||
"ChannelGuildPositionUpdateSchema",
|
|
||||||
"ChannelGuildPositionUpdateSchema",
|
|
||||||
"EmojiCreateSchema",
|
|
||||||
"GuildCreateSchema",
|
|
||||||
"GuildUpdateSchema",
|
|
||||||
"GuildTemplateCreateSchema",
|
|
||||||
"GuildUpdateWelcomeScreenSchema",
|
|
||||||
"InviteCreateSchema",
|
|
||||||
"MemberCreateSchema",
|
|
||||||
"MemberNickChangeSchema",
|
|
||||||
"MemberChangeSchema",
|
|
||||||
"MessageCreateSchema",
|
|
||||||
"RoleModifySchema",
|
|
||||||
"TemplateCreateSchema",
|
|
||||||
"TemplateModifySchema",
|
|
||||||
"UserModifySchema",
|
|
||||||
"UserSettingsSchema",
|
|
||||||
"WidgetModifySchema",
|
|
||||||
""
|
|
||||||
];
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
combineSchemas({ schemas, generator, program });
|
|
||||||
}
|
|
||||||
|
|
||||||
function addDefaultResponses() {
|
|
||||||
Object.values(specification.paths).forEach((path: any) => Object.values(path).forEach((request: any) => {}));
|
|
||||||
}
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
addDefaultResponses();
|
|
||||||
utilSchemas();
|
|
||||||
apiSchemas();
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
openapiPath,
|
|
||||||
JSON.stringify(specification, null, 4).replaceAll("#/definitions", "#/components/schemas").replaceAll("bigint", "number")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
main();
|
|
||||||
@ -6,7 +6,7 @@ import * as TJS from "typescript-json-schema";
|
|||||||
import "missing-native-js-functions";
|
import "missing-native-js-functions";
|
||||||
const schemaPath = path.join(__dirname, "..", "assets", "schemas.json");
|
const schemaPath = path.join(__dirname, "..", "assets", "schemas.json");
|
||||||
|
|
||||||
const settings: TJS.PartialArgs = {
|
const settings = {
|
||||||
required: true,
|
required: true,
|
||||||
ignoreErrors: true,
|
ignoreErrors: true,
|
||||||
excludePrivate: true,
|
excludePrivate: true,
|
||||||
@ -14,23 +14,34 @@ const settings: TJS.PartialArgs = {
|
|||||||
noExtraProps: true,
|
noExtraProps: true,
|
||||||
defaultProps: false
|
defaultProps: false
|
||||||
};
|
};
|
||||||
const compilerOptions: TJS.CompilerOptions = {
|
const compilerOptions = {
|
||||||
strictNullChecks: true
|
strictNullChecks: true
|
||||||
};
|
};
|
||||||
const ExcludedSchemas = ["DefaultSchema", "Schema", "EntitySchema"];
|
const Excluded = [
|
||||||
|
"DefaultSchema",
|
||||||
|
"Schema",
|
||||||
|
"EntitySchema",
|
||||||
|
"ServerResponse",
|
||||||
|
"Http2ServerResponse",
|
||||||
|
"global.Express.Response",
|
||||||
|
"Response",
|
||||||
|
"e.Response",
|
||||||
|
"request.Response",
|
||||||
|
"supertest.Response"
|
||||||
|
];
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
const program = TJS.getProgramFromFiles(walk(path.join(__dirname, "..", "src", "routes")), compilerOptions);
|
const program = TJS.getProgramFromFiles(walk(path.join(__dirname, "..", "src", "routes")), compilerOptions);
|
||||||
const generator = TJS.buildGenerator(program, settings);
|
const generator = TJS.buildGenerator(program, settings);
|
||||||
if (!generator || !program) return;
|
if (!generator || !program) return;
|
||||||
|
|
||||||
const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x));
|
const schemas = generator.getUserSymbols().filter((x) => (x.endsWith("Schema") || x.endsWith("Response")) && !Excluded.includes(x));
|
||||||
console.log(schemas);
|
console.log(schemas);
|
||||||
|
|
||||||
var definitions: any = {};
|
var definitions = {};
|
||||||
|
|
||||||
for (const name of schemas) {
|
for (const name of schemas) {
|
||||||
const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator);
|
const part = TJS.generateSchema(program, name, settings, [], generator);
|
||||||
if (!part) continue;
|
if (!part) continue;
|
||||||
|
|
||||||
definitions = { ...definitions, [name]: { ...part } };
|
definitions = { ...definitions, [name]: { ...part } };
|
||||||
@ -39,11 +50,10 @@ function main() {
|
|||||||
fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
|
fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
// #/definitions/
|
|
||||||
main();
|
main();
|
||||||
|
|
||||||
function walk(dir: string) {
|
function walk(dir) {
|
||||||
var results = [] as string[];
|
var results = [];
|
||||||
var list = fs.readdirSync(dir);
|
var list = fs.readdirSync(dir);
|
||||||
list.forEach(function (file) {
|
list.forEach(function (file) {
|
||||||
file = dir + "/" + file;
|
file = dir + "/" + file;
|
||||||
@ -1,10 +0,0 @@
|
|||||||
const tsConfigPaths = require("tsconfig-paths");
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
const cleanup = tsConfigPaths.register({
|
|
||||||
baseUrl: path.join(__dirname, ".."),
|
|
||||||
paths: {
|
|
||||||
"@fosscord/api": ["dist/index.js"],
|
|
||||||
"@fosscord/api/*": ["dist/*"]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@ -6,6 +6,8 @@ export function BodyParser(opts?: OptionsJson) {
|
|||||||
const jsonParser = bodyParser.json(opts);
|
const jsonParser = bodyParser.json(opts);
|
||||||
|
|
||||||
return (req: Request, res: Response, next: NextFunction) => {
|
return (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
if (!req.headers["content-type"]) req.headers["content-type"] = "application/json";
|
||||||
|
|
||||||
jsonParser(req, res, (err) => {
|
jsonParser(req, res, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
// TODO: different errors for body parser (request size limit, wrong body type, invalid body, ...)
|
// TODO: different errors for body parser (request size limit, wrong body type, invalid body, ...)
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { HTTPError } from "lambert-server";
|
|||||||
import { EntityNotFoundError } from "typeorm";
|
import { EntityNotFoundError } from "typeorm";
|
||||||
import { FieldError } from "@fosscord/api";
|
import { FieldError } from "@fosscord/api";
|
||||||
import { ApiError } from "@fosscord/util";
|
import { ApiError } from "@fosscord/util";
|
||||||
|
const EntityNotFoundErrorRegex = /"(\w+)"/;
|
||||||
|
|
||||||
export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) {
|
export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) {
|
||||||
if (!error) return next();
|
if (!error) return next();
|
||||||
@ -18,9 +19,9 @@ export function ErrorHandler(error: Error, req: Request, res: Response, next: Ne
|
|||||||
code = error.code;
|
code = error.code;
|
||||||
message = error.message;
|
message = error.message;
|
||||||
httpcode = error.httpStatus;
|
httpcode = error.httpStatus;
|
||||||
} else if (error instanceof EntityNotFoundError) {
|
} else if (error.name === "EntityNotFoundError") {
|
||||||
message = `${(error as any).stringifyTarget || "Item"} could not be found`;
|
message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`;
|
||||||
code = 404;
|
code = httpcode = 404;
|
||||||
} else if (error instanceof FieldError) {
|
} else if (error instanceof FieldError) {
|
||||||
code = Number(error.code);
|
code = Number(error.code);
|
||||||
message = error.message;
|
message = error.message;
|
||||||
|
|||||||
@ -107,7 +107,8 @@ export default function rateLimit(opts: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function initRateLimits(app: Router) {
|
export async function initRateLimits(app: Router) {
|
||||||
const { routes, global, ip, error } = Config.get().limits.rate;
|
const { routes, global, ip, error, disabled } = Config.get().limits.rate;
|
||||||
|
if (disabled) return;
|
||||||
await listenEvent(EventRateLimit, (event) => {
|
await listenEvent(EventRateLimit, (event) => {
|
||||||
Cache.set(event.channel_id as string, event.data);
|
Cache.set(event.channel_id as string, event.data);
|
||||||
event.acknowledge?.();
|
event.acknowledge?.();
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
import { route } from "@fosscord/api";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", async (req: Request, res: Response) => {
|
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||||
//TODO
|
//TODO
|
||||||
res.json([]).status(200);
|
res.json([]).status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { FieldErrors, route } from "@fosscord/api";
|
import { FieldErrors, route } from "@fosscord/api";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import jwt from "jsonwebtoken";
|
import { Config, User, generateToken, adjustEmail } from "@fosscord/util";
|
||||||
import { Config, User } from "@fosscord/util";
|
|
||||||
import { adjustEmail } from "./register";
|
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
export default router;
|
export default router;
|
||||||
@ -68,25 +66,6 @@ router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Respo
|
|||||||
res.json({ token, settings: user.settings });
|
res.json({ token, settings: user.settings });
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function generateToken(id: string) {
|
|
||||||
const iat = Math.floor(Date.now() / 1000);
|
|
||||||
const algorithm = "HS256";
|
|
||||||
|
|
||||||
return new Promise((res, rej) => {
|
|
||||||
jwt.sign(
|
|
||||||
{ id: id, iat },
|
|
||||||
Config.get().security.jwtSecret,
|
|
||||||
{
|
|
||||||
algorithm
|
|
||||||
},
|
|
||||||
(err, token) => {
|
|
||||||
if (err) return rej(err);
|
|
||||||
return res(token);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* POST /auth/login
|
* POST /auth/login
|
||||||
* @argument { login: "email@gmail.com", password: "cleartextpassword", undelete: false, captcha_key: null, login_source: null, gift_code_sku_id: null, }
|
* @argument { login: "email@gmail.com", password: "cleartextpassword", undelete: false, captcha_key: null, login_source: null, gift_code_sku_id: null, }
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { trimSpecial, User, Snowflake, Config, defaultSettings, Member, Invite } from "@fosscord/util";
|
import { trimSpecial, User, Snowflake, Config, defaultSettings, generateToken, Invite, adjustEmail } from "@fosscord/util";
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import { EMAIL_REGEX, FieldErrors, route } from "@fosscord/api";
|
import { FieldErrors, route, getIpAdress, IPAnalysis, isProxy } from "@fosscord/api";
|
||||||
import "missing-native-js-functions";
|
import "missing-native-js-functions";
|
||||||
import { generateToken } from "./login";
|
|
||||||
import { getIpAdress, IPAnalysis, isProxy } from "@fosscord/api";
|
|
||||||
import { HTTPError } from "lambert-server";
|
import { HTTPError } from "lambert-server";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
@ -228,24 +226,6 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re
|
|||||||
return res.json({ token: await generateToken(user.id) });
|
return res.json({ token: await generateToken(user.id) });
|
||||||
});
|
});
|
||||||
|
|
||||||
export function adjustEmail(email: string): string | undefined {
|
|
||||||
if (!email) return email;
|
|
||||||
// body parser already checked if it is a valid email
|
|
||||||
const parts = <RegExpMatchArray>email.match(EMAIL_REGEX);
|
|
||||||
// @ts-ignore
|
|
||||||
if (!parts || parts.length < 5) return undefined;
|
|
||||||
const domain = parts[5];
|
|
||||||
const user = parts[1];
|
|
||||||
|
|
||||||
// TODO: check accounts with uncommon email domains
|
|
||||||
if (domain === "gmail.com" || domain === "googlemail.com") {
|
|
||||||
// replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator
|
|
||||||
return user.replace(/[.]|(\+.*)/g, "") + "@gmail.com";
|
|
||||||
}
|
|
||||||
|
|
||||||
return email;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
import { Channel, ChannelRecipientAddEvent, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util";
|
import { Channel, ChannelRecipientAddEvent, ChannelType, DiscordApiErrors, DmChannelDTO, emitEvent, PublicUserProjection, Recipient, User } from "@fosscord/util";
|
||||||
|
import { route } from "@fosscord/api"
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.put("/:user_id", async (req: Request, res: Response) => {
|
router.put("/:user_id", route({}), async (req: Request, res: Response) => {
|
||||||
const { channel_id, user_id } = req.params;
|
const { channel_id, user_id } = req.params;
|
||||||
const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
|
const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ router.put("/:user_id", async (req: Request, res: Response) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.delete("/:user_id", async (req: Request, res: Response) => {
|
router.delete("/:user_id", route({}), async (req: Request, res: Response) => {
|
||||||
const { channel_id, user_id } = req.params;
|
const { channel_id, user_id } = req.params;
|
||||||
const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
|
const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] });
|
||||||
if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id)))
|
if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id)))
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { route } from "@fosscord/api";
|
|||||||
import { ChannelModifySchema } from "../../channels/#channel_id";
|
import { ChannelModifySchema } from "../../channels/#channel_id";
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.get("/", async (req: Request, res: Response) => {
|
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||||
const { guild_id } = req.params;
|
const { guild_id } = req.params;
|
||||||
const channels = await Channel.find({ guild_id });
|
const channels = await Channel.find({ guild_id });
|
||||||
|
|
||||||
|
|||||||
@ -4,14 +4,14 @@ import { Request, Response, Router } from "express";
|
|||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
router.delete("/:member_id/roles/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
|
router.delete("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
|
||||||
const { guild_id, role_id, member_id } = req.params;
|
const { guild_id, role_id, member_id } = req.params;
|
||||||
|
|
||||||
await Member.removeRole(member_id, guild_id, role_id);
|
await Member.removeRole(member_id, guild_id, role_id);
|
||||||
res.sendStatus(204);
|
res.sendStatus(204);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.put("/:member_id/roles/:role_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
|
router.put("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => {
|
||||||
const { guild_id, role_id, member_id } = req.params;
|
const { guild_id, role_id, member_id } = req.params;
|
||||||
|
|
||||||
await Member.addRole(member_id, guild_id, role_id);
|
await Member.addRole(member_id, guild_id, role_id);
|
||||||
|
|||||||
@ -29,7 +29,7 @@ export type RolePositionUpdateSchema = {
|
|||||||
position: number;
|
position: number;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
router.get("/", async (req: Request, res: Response) => {
|
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||||
const guild_id = req.params.guild_id;
|
const guild_id = req.params.guild_id;
|
||||||
|
|
||||||
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
await Member.IsInGuildOrFail(req.user_id, guild_id);
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
import { route } from "@fosscord/api";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", async (req: Request, res: Response) => {
|
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||||
//TODO
|
//TODO
|
||||||
res.json([]).status(200);
|
res.json([]).status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
import { route } from "@fosscord/api";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", async (req: Request, res: Response) => {
|
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||||
//TODO
|
//TODO
|
||||||
res.json({
|
res.json({
|
||||||
id: "",
|
id: "",
|
||||||
@ -15,4 +16,4 @@ router.get("/", async (req: Request, res: Response) => {
|
|||||||
}).status(200);
|
}).status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
import { route } from "@fosscord/api";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", async (req: Request, res: Response) => {
|
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||||
//TODO
|
//TODO
|
||||||
res.json({ sticker_packs: [] }).status(200);
|
res.json({ sticker_packs: [] }).status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
import { route } from "@fosscord/api";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/applications/:id", async (req: Request, res: Response) => {
|
router.get("/applications/:id", route({}), async (req: Request, res: Response) => {
|
||||||
//TODO
|
//TODO
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
res.json([]).status(200);
|
res.json([]).status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
import { route } from "@fosscord/api";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/skus/:id", async (req: Request, res: Response) => {
|
router.get("/skus/:id", route({}), async (req: Request, res: Response) => {
|
||||||
//TODO
|
//TODO
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
res.json([]).status(200);
|
res.json([]).status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -11,7 +11,7 @@ export interface UserProfileResponse {
|
|||||||
premium_since?: Date;
|
premium_since?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get("/", route({ response: { body: "UserProfileResponse" } }), async (req: Request, res: Response) => {
|
router.get("/", route({ test: { response: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => {
|
||||||
if (req.params.id === "@me") req.params.id = req.user_id;
|
if (req.params.id === "@me") req.params.id = req.user_id;
|
||||||
const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] });
|
const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] });
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
import { route } from "@fosscord/api";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", async (req: Request, res: Response) => {
|
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||||
//TODO
|
//TODO
|
||||||
res.json([]).status(200);
|
res.json([]).status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
import { route } from "@fosscord/api";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", async (req: Request, res: Response) => {
|
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||||
//TODO
|
//TODO
|
||||||
res.json({ "country_code": "US" }).status(200);
|
res.json({ country_code: "US" }).status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { Request, Response, Router } from "express";
|
import { Request, Response, Router } from "express";
|
||||||
|
import { route } from "@fosscord/api";
|
||||||
|
|
||||||
const router: Router = Router();
|
const router: Router = Router();
|
||||||
|
|
||||||
router.get("/", async (req: Request, res: Response) => {
|
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||||
//TODO
|
//TODO
|
||||||
res.json([]).status(200);
|
res.json([]).status(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@ -22,7 +22,7 @@ export interface UserModifySchema {
|
|||||||
code?: string;
|
code?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get("/", async (req: Request, res: Response) => {
|
router.get("/", route({}), async (req: Request, res: Response) => {
|
||||||
res.json(await User.findOne({ select: PrivateUserProjection, where: { id: req.user_id } }));
|
res.json(await User.findOne({ select: PrivateUserProjection, where: { id: req.user_id } }));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
import { Request } from "express";
|
import { Request } from "express";
|
||||||
import { ntob } from "./Base64";
|
import { ntob } from "./Base64";
|
||||||
import { FieldErrors } from "./FieldError";
|
import { FieldErrors } from "./FieldError";
|
||||||
export const EMAIL_REGEX =
|
|
||||||
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
|
||||||
|
|
||||||
export function checkLength(str: string, min: number, max: number, key: string, req: Request) {
|
export function checkLength(str: string, min: number, max: number, key: string, req: Request) {
|
||||||
if (str.length < min || str.length > max) {
|
if (str.length < min || str.length > max) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util";
|
import { DiscordApiErrors, EVENT, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util";
|
||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
@ -29,17 +29,16 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type RouteSchema = string; // typescript interface name
|
export type RouteResponse = { status?: number; body?: `${string}Response`; headers?: Record<string, string> };
|
||||||
export type RouteResponse = { status?: number; body?: RouteSchema; headers?: Record<string, string> };
|
|
||||||
|
|
||||||
export interface RouteOptions {
|
export interface RouteOptions {
|
||||||
permission?: PermissionResolvable;
|
permission?: PermissionResolvable;
|
||||||
body?: RouteSchema;
|
body?: `${string}Schema`; // typescript interface name
|
||||||
response?: RouteResponse;
|
test?: {
|
||||||
example?: {
|
response?: RouteResponse;
|
||||||
body?: any;
|
body?: any;
|
||||||
path?: string;
|
path?: string;
|
||||||
event?: EventData;
|
event?: EVENT | EVENT[];
|
||||||
headers?: Record<string, string>;
|
headers?: Record<string, string>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,2 +0,0 @@
|
|||||||
// TODO: check every route based on route() paramters: https://github.com/fosscord/fosscord-server/issues/308
|
|
||||||
// TODO: check every route with different database engine
|
|
||||||
136
api/tests/routes.test.ts
Normal file
136
api/tests/routes.test.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
// TODO: check every route based on route() parameters: https://github.com/fosscord/fosscord-server/issues/308
|
||||||
|
// TODO: check every route with different database engine
|
||||||
|
|
||||||
|
import getRouteDescriptions from "../jest/getRouteDescriptions";
|
||||||
|
import { join } from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import Ajv from "ajv";
|
||||||
|
import addFormats from "ajv-formats";
|
||||||
|
import fetch from "node-fetch";
|
||||||
|
import { Event, User, events } from "@fosscord/util";
|
||||||
|
|
||||||
|
const SchemaPath = join(__dirname, "..", "assets", "schemas.json");
|
||||||
|
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
|
||||||
|
export const ajv = new Ajv({
|
||||||
|
allErrors: true,
|
||||||
|
parseDate: true,
|
||||||
|
allowDate: true,
|
||||||
|
schemas,
|
||||||
|
messages: true,
|
||||||
|
strict: true,
|
||||||
|
strictRequired: true,
|
||||||
|
coerceTypes: true
|
||||||
|
});
|
||||||
|
addFormats(ajv);
|
||||||
|
|
||||||
|
var token: string;
|
||||||
|
var user: User;
|
||||||
|
beforeAll(async (done) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch("http://localhost:3001/api/auth/register", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
fingerprint: "805826570869932034.wR8vi8lGlFBJerErO9LG5NViJFw",
|
||||||
|
email: "test@example.com",
|
||||||
|
username: "tester",
|
||||||
|
password: "wtp9gep9gw",
|
||||||
|
invite: null,
|
||||||
|
consent: true,
|
||||||
|
date_of_birth: "2000-01-01",
|
||||||
|
gift_code_sku_id: null,
|
||||||
|
captcha_key: null
|
||||||
|
}),
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const json = await response.json();
|
||||||
|
token = json.token;
|
||||||
|
user = await (
|
||||||
|
await fetch(`http://localhost:3001/api/users/@me`, {
|
||||||
|
headers: { authorization: token }
|
||||||
|
})
|
||||||
|
).json();
|
||||||
|
|
||||||
|
done();
|
||||||
|
} catch (error) {
|
||||||
|
done(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = events.emit;
|
||||||
|
events.emit = (event: string | symbol, ...args: any[]) => {
|
||||||
|
events.emit("event", args[0]);
|
||||||
|
return emit(event, ...args);
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("Automatic unit tests with route description middleware", () => {
|
||||||
|
const routes = getRouteDescriptions();
|
||||||
|
|
||||||
|
routes.forEach((route, pathAndMethod) => {
|
||||||
|
const [path, method] = pathAndMethod.split("|");
|
||||||
|
|
||||||
|
test(`${method.toUpperCase()} ${path}`, async (done) => {
|
||||||
|
if (!route.test) {
|
||||||
|
console.log(`${(route as any).file}\nrouter.${method} is missing the test property`);
|
||||||
|
return done();
|
||||||
|
}
|
||||||
|
const urlPath = path.replace(":id", user.id) || route.test?.path;
|
||||||
|
var validate: any;
|
||||||
|
if (route.test.body) {
|
||||||
|
validate = ajv.getSchema(route.test.body);
|
||||||
|
if (!validate) return done(new Error(`Response schema ${route.test.body} not found`));
|
||||||
|
}
|
||||||
|
|
||||||
|
var body = "";
|
||||||
|
let eventEmitted = Promise.resolve();
|
||||||
|
|
||||||
|
if (route.test.event) {
|
||||||
|
if (!Array.isArray(route.test.event)) route.test.event = [route.test.event];
|
||||||
|
|
||||||
|
eventEmitted = new Promise((resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => reject, 1000);
|
||||||
|
const received = [];
|
||||||
|
|
||||||
|
events.on("event", (event: Event) => {
|
||||||
|
if (!route.test.event.includes(event.event)) return;
|
||||||
|
|
||||||
|
received.push(event.event);
|
||||||
|
if (received.length === route.test.event.length) resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`http://localhost:3001/api${urlPath}`, {
|
||||||
|
method: method.toUpperCase(),
|
||||||
|
body: JSON.stringify(route.test.body),
|
||||||
|
headers: { ...route.test.headers, authorization: token }
|
||||||
|
});
|
||||||
|
|
||||||
|
body = await response.text();
|
||||||
|
|
||||||
|
expect(response.status, body).toBe(route.test.response.status || 200);
|
||||||
|
|
||||||
|
// TODO: check headers
|
||||||
|
// TODO: expect event
|
||||||
|
|
||||||
|
if (validate) {
|
||||||
|
body = JSON.parse(body);
|
||||||
|
const valid = validate(body);
|
||||||
|
if (!valid) return done(validate.errors);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return done(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await eventEmitted;
|
||||||
|
} catch (error) {
|
||||||
|
return done(new Error(`Event ${route.test.event} was not emitted`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -66,8 +66,9 @@
|
|||||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@fosscord/api": ["src/index.ts"],
|
"@fosscord/api": ["src/index"],
|
||||||
"@fosscord/api/*": ["src/*"]
|
"@fosscord/api/*": ["src/*"]
|
||||||
}
|
},
|
||||||
|
"plugins": [{ "transform": "@zerollup/ts-transform-paths" }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
bundle/package-lock.json
generated
BIN
bundle/package-lock.json
generated
Binary file not shown.
@ -4,15 +4,16 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "src/start.js",
|
"main": "src/start.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "cd ../util && npm i && cd ../api && npm i && cd ../cdn && npm i && cd ../gateway && npm i",
|
"setup": "cd ../util && npm --production=false i && cd ../api && npm --production=false i && cd ../cdn && npm --production=false i && cd ../gateway && npm --production=false i && npm install && npm run start",
|
||||||
"build": "npm run build:util && npm run build:api && npm run build:cdn && npm run build:gateway && npm run build:bundle",
|
"build": "npm run build:util && npm run build:api && npm run build:cdn && npm run build:gateway && npm run build:bundle",
|
||||||
|
"postinstall": "ts-patch install -s",
|
||||||
"build:bundle": "npx tsc -b .",
|
"build:bundle": "npx tsc -b .",
|
||||||
"build:util": "cd ../util/ && npm run build",
|
"build:util": "cd ../util/ && npm run build",
|
||||||
"build:api": "cd ../api/ && npm run build",
|
"build:api": "cd ../api/ && npm run build",
|
||||||
"build:cdn": "cd ../cdn/ && npm run build",
|
"build:cdn": "cd ../cdn/ && npm run build",
|
||||||
"build:gateway": "cd ../gateway/ && npm run build",
|
"build:gateway": "cd ../gateway/ && npm run build",
|
||||||
"start": "npm run build && npm run start:bundle",
|
"start": "npm run build && npm run start:bundle",
|
||||||
"start:bundle": "node -r ./tsconfig-paths-bootstrap.js dist/start.js",
|
"start:bundle": "node dist/start.js",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
@ -44,6 +45,7 @@
|
|||||||
"@types/ws": "^7.4.0",
|
"@types/ws": "^7.4.0",
|
||||||
"@zerollup/ts-transform-paths": "^1.7.18",
|
"@zerollup/ts-transform-paths": "^1.7.18",
|
||||||
"ts-node": "^10.2.1",
|
"ts-node": "^10.2.1",
|
||||||
|
"ts-patch": "^1.4.4",
|
||||||
"typescript": "^4.3.5"
|
"typescript": "^4.3.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
const tsConfigPaths = require("tsconfig-paths");
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
const cleanup = tsConfigPaths.register({
|
|
||||||
baseUrl: path.join(__dirname, "node_modules", "@fosscord"),
|
|
||||||
paths: {
|
|
||||||
"@fosscord/api": ["api/dist/index.js"],
|
|
||||||
"@fosscord/api/*": ["api/dist/*"],
|
|
||||||
"@fosscord/gateway": ["gateway/dist/index.js"],
|
|
||||||
"@fosscord/gateway/*": ["gateway/dist/*"],
|
|
||||||
"@fosscord/cdn": ["cdn/dist/index.js"],
|
|
||||||
"@fosscord/cdn/*": ["cdn/dist/*"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
BIN
cdn/package-lock.json
generated
BIN
cdn/package-lock.json
generated
Binary file not shown.
@ -1,25 +1,26 @@
|
|||||||
{
|
{
|
||||||
"name": "@fosscord/cdn",
|
"name": "@fosscord/cdn",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "cdn for discord clone",
|
"description": "cdn for fosscord",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"postinstall": "ts-patch install -s",
|
||||||
"test": "npm run build && jest --coverage ./tests",
|
"test": "npm run build && jest --coverage ./tests",
|
||||||
"build": "npx tsc -b .",
|
"build": "npx tsc -b .",
|
||||||
"start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start.js"
|
"start": "npm run build && node dist/start.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/discord-open-source/discord-cdn.git"
|
"url": "git+https://github.com/fosscord/fosscord-server.git"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/discord-open-source/discord-cdn/issues"
|
"url": "https://github.com/fosscord/fosscord-server/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/discord-open-source/discord-cdn#readme",
|
"homepage": "https://github.com/fosscord/fosscord-server#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/amqplib": "^0.8.1",
|
"@types/amqplib": "^0.8.1",
|
||||||
"@types/body-parser": "^1.19.0",
|
"@types/body-parser": "^1.19.0",
|
||||||
@ -34,7 +35,9 @@
|
|||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^14.17.0",
|
"@types/node": "^14.17.0",
|
||||||
"@types/node-fetch": "^2.5.7",
|
"@types/node-fetch": "^2.5.7",
|
||||||
"@types/uuid": "^8.3.0"
|
"@types/uuid": "^8.3.0",
|
||||||
|
"@zerollup/ts-transform-paths": "^1.7.18",
|
||||||
|
"ts-patch": "^1.4.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fosscord/util": "file:../util",
|
"@fosscord/util": "file:../util",
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
const tsConfigPaths = require("tsconfig-paths");
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
const cleanup = tsConfigPaths.register({
|
|
||||||
baseUrl: path.join(__dirname, ".."),
|
|
||||||
paths: {
|
|
||||||
"@fosscord/cdn": ["dist/index.js"],
|
|
||||||
"@fosscord/cdn/*": ["dist/*"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -68,8 +68,9 @@
|
|||||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@fosscord/cdn/": ["src/index.ts"],
|
"@fosscord/cdn/": ["src/index"],
|
||||||
"@fosscord/cdn/*": ["src/*"]
|
"@fosscord/cdn/*": ["src/*"]
|
||||||
}
|
},
|
||||||
|
"plugins": [{ "transform": "@zerollup/ts-transform-paths" }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
gateway/package-lock.json
generated
BIN
gateway/package-lock.json
generated
Binary file not shown.
@ -4,8 +4,9 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"postinstall": "ts-patch install -s",
|
||||||
"test": "echo \"Error: no test specified\" && exit 1",
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
"start": "npm run build && node -r ./scripts/tsconfig-paths-bootstrap.js dist/start.js",
|
"start": "npm run build && node dist/start.js",
|
||||||
"build": "npx tsc -b .",
|
"build": "npx tsc -b .",
|
||||||
"dev": "tsnd --respawn src/start.ts"
|
"dev": "tsnd --respawn src/start.ts"
|
||||||
},
|
},
|
||||||
@ -22,7 +23,9 @@
|
|||||||
"@types/node-fetch": "^2.5.12",
|
"@types/node-fetch": "^2.5.12",
|
||||||
"@types/uuid": "^8.3.0",
|
"@types/uuid": "^8.3.0",
|
||||||
"@types/ws": "^7.4.0",
|
"@types/ws": "^7.4.0",
|
||||||
|
"@zerollup/ts-transform-paths": "^1.7.18",
|
||||||
"ts-node-dev": "^1.1.6",
|
"ts-node-dev": "^1.1.6",
|
||||||
|
"ts-patch": "^1.4.4",
|
||||||
"typescript": "^4.2.3"
|
"typescript": "^4.2.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
const tsConfigPaths = require("tsconfig-paths");
|
|
||||||
const path = require("path");
|
|
||||||
|
|
||||||
const cleanup = tsConfigPaths.register({
|
|
||||||
baseUrl: path.join(__dirname, ".."),
|
|
||||||
paths: {
|
|
||||||
"@fosscord/gateway": ["dist/index.js"],
|
|
||||||
"@fosscord/gateway/*": ["dist/*"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
import { WebSocket } from "@fosscord/gateway";
|
||||||
import { Message } from "./Message";
|
import { Message } from "./Message";
|
||||||
import { Session } from "@fosscord/util";
|
import { Session } from "@fosscord/util";
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import WS from "ws";
|
import WS from "ws";
|
||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
import {
|
||||||
|
setHeartbeat,
|
||||||
|
Send,
|
||||||
|
CLOSECODES,
|
||||||
|
OPCODES,
|
||||||
|
WebSocket,
|
||||||
|
} from "@fosscord/gateway";
|
||||||
import { IncomingMessage } from "http";
|
import { IncomingMessage } from "http";
|
||||||
import { Close } from "./Close";
|
import { Close } from "./Close";
|
||||||
import { Message } from "./Message";
|
import { Message } from "./Message";
|
||||||
import { setHeartbeat } from "@fosscord/gateway/util/setHeartbeat";
|
|
||||||
import { Send } from "@fosscord/gateway/util/Send";
|
|
||||||
import { CLOSECODES, OPCODES } from "@fosscord/gateway/util/Constants";
|
|
||||||
import { createDeflate } from "zlib";
|
import { createDeflate } from "zlib";
|
||||||
import { URL } from "url";
|
import { URL } from "url";
|
||||||
import { Session } from "@fosscord/util";
|
import { Session } from "@fosscord/util";
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
import { WebSocket, Payload, CLOSECODES, OPCODES } from "@fosscord/gateway";
|
||||||
var erlpack: any;
|
var erlpack: any;
|
||||||
try {
|
try {
|
||||||
erlpack = require("@yukikaze-bot/erlpack");
|
erlpack = require("@yukikaze-bot/erlpack");
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
import OPCodeHandlers from "../opcodes";
|
import OPCodeHandlers from "../opcodes";
|
||||||
import { Payload, CLOSECODES, OPCODES } from "@fosscord/gateway/util/Constants";
|
|
||||||
import { instanceOf, Tuple } from "lambert-server";
|
import { instanceOf, Tuple } from "lambert-server";
|
||||||
import { check } from "../opcodes/instanceOf";
|
import { check } from "../opcodes/instanceOf";
|
||||||
import WS from "ws";
|
import WS from "ws";
|
||||||
|
|||||||
@ -9,12 +9,9 @@ import {
|
|||||||
ListenEventOpts,
|
ListenEventOpts,
|
||||||
Member,
|
Member,
|
||||||
} from "@fosscord/util";
|
} from "@fosscord/util";
|
||||||
import { OPCODES } from "@fosscord/gateway/util/Constants";
|
import { OPCODES, WebSocket, Send } from "@fosscord/gateway";
|
||||||
import { Send } from "@fosscord/gateway/util/Send";
|
|
||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
|
||||||
import "missing-native-js-functions";
|
import "missing-native-js-functions";
|
||||||
import { Channel as AMQChannel } from "amqplib";
|
import { Channel as AMQChannel } from "amqplib";
|
||||||
import { In, Like } from "typeorm";
|
|
||||||
import { Recipient } from "@fosscord/util";
|
import { Recipient } from "@fosscord/util";
|
||||||
|
|
||||||
// TODO: close connection on Invalidated Token
|
// TODO: close connection on Invalidated Token
|
||||||
@ -116,7 +113,7 @@ async function consume(this: WebSocket, opts: EventOpts) {
|
|||||||
.has("VIEW_CHANNEL")
|
.has("VIEW_CHANNEL")
|
||||||
)
|
)
|
||||||
return;
|
return;
|
||||||
//No break needed here, we need to call the listenEvent function below
|
//No break needed here, we need to call the listenEvent function below
|
||||||
case "GUILD_CREATE":
|
case "GUILD_CREATE":
|
||||||
this.events[id] = await listenEvent(id, consumer, listenOpts);
|
this.events[id] = await listenEvent(id, consumer, listenOpts);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants";
|
import { Payload, Send, setHeartbeat, WebSocket } from "@fosscord/gateway";
|
||||||
import { Send } from "@fosscord/gateway/util/Send";
|
|
||||||
import { setHeartbeat } from "@fosscord/gateway/util/setHeartbeat";
|
|
||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
|
||||||
|
|
||||||
export async function onHeartbeat(this: WebSocket, data: Payload) {
|
export async function onHeartbeat(this: WebSocket, data: Payload) {
|
||||||
// TODO: validate payload
|
// TODO: validate payload
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import { CLOSECODES, Payload, OPCODES } from "@fosscord/gateway/util/Constants";
|
import {
|
||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
WebSocket,
|
||||||
|
CLOSECODES,
|
||||||
|
Payload,
|
||||||
|
OPCODES,
|
||||||
|
genSessionId,
|
||||||
|
} from "@fosscord/gateway";
|
||||||
import {
|
import {
|
||||||
Channel,
|
Channel,
|
||||||
checkToken,
|
checkToken,
|
||||||
@ -24,7 +29,6 @@ import { Send } from "@fosscord/gateway/util/Send";
|
|||||||
const experiments: any = [];
|
const experiments: any = [];
|
||||||
import { check } from "./instanceOf";
|
import { check } from "./instanceOf";
|
||||||
import { Recipient } from "@fosscord/util";
|
import { Recipient } from "@fosscord/util";
|
||||||
import { genSessionId } from "@fosscord/gateway/util/SessionUtils";
|
|
||||||
|
|
||||||
// TODO: bot sharding
|
// TODO: bot sharding
|
||||||
// TODO: check priviliged intents
|
// TODO: check priviliged intents
|
||||||
@ -98,7 +102,9 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
//TODO is this needed? check if users in group dm that are not friends are sent in the READY event
|
//TODO is this needed? check if users in group dm that are not friends are sent in the READY event
|
||||||
//users = users.concat(x.channel.recipients);
|
//users = users.concat(x.channel.recipients);
|
||||||
if (x.channel.isDm()) {
|
if (x.channel.isDm()) {
|
||||||
x.channel.recipients = x.channel.recipients!.filter((x) => x.id !== this.user_id);
|
x.channel.recipients = x.channel.recipients!.filter(
|
||||||
|
(x) => x.id !== this.user_id
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return x.channel;
|
return x.channel;
|
||||||
});
|
});
|
||||||
@ -109,7 +115,7 @@ export async function onIdentify(this: WebSocket, data: Payload) {
|
|||||||
if (!user) return this.close(CLOSECODES.Authentication_failed);
|
if (!user) return this.close(CLOSECODES.Authentication_failed);
|
||||||
|
|
||||||
for (let relation of user.relationships) {
|
for (let relation of user.relationships) {
|
||||||
const related_user = relation.to
|
const related_user = relation.to;
|
||||||
const public_related_user = {
|
const public_related_user = {
|
||||||
username: related_user.username,
|
username: related_user.username,
|
||||||
discriminator: related_user.discriminator,
|
discriminator: related_user.discriminator,
|
||||||
|
|||||||
@ -2,13 +2,10 @@ import {
|
|||||||
getPermission,
|
getPermission,
|
||||||
Member,
|
Member,
|
||||||
PublicMemberProjection,
|
PublicMemberProjection,
|
||||||
PublicUserProjection,
|
|
||||||
Role,
|
Role,
|
||||||
} from "@fosscord/util";
|
} from "@fosscord/util";
|
||||||
import { LazyRequest } from "../schema/LazyRequest";
|
import { LazyRequest } from "../schema/LazyRequest";
|
||||||
import { OPCODES, Payload } from "@fosscord/gateway/util/Constants";
|
import { WebSocket, Send, OPCODES, Payload } from "@fosscord/gateway";
|
||||||
import { Send } from "@fosscord/gateway/util/Send";
|
|
||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
|
||||||
import { check } from "./instanceOf";
|
import { check } from "./instanceOf";
|
||||||
import "missing-native-js-functions";
|
import "missing-native-js-functions";
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants";
|
import { WebSocket, Payload } from "@fosscord/gateway";
|
||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
|
||||||
|
|
||||||
export function onPresenceUpdate(this: WebSocket, data: Payload) {
|
export function onPresenceUpdate(this: WebSocket, data: Payload) {
|
||||||
// return this.close(CLOSECODES.Unknown_error);
|
// return this.close(CLOSECODES.Unknown_error);
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants";
|
import { Payload, WebSocket } from "@fosscord/gateway";
|
||||||
|
|
||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
|
||||||
|
|
||||||
export function onRequestGuildMembers(this: WebSocket, data: Payload) {
|
export function onRequestGuildMembers(this: WebSocket, data: Payload) {
|
||||||
// return this.close(CLOSECODES.Unknown_error);
|
// return this.close(CLOSECODES.Unknown_error);
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
import { CLOSECODES, Payload } from "@fosscord/gateway/util/Constants";
|
import { WebSocket, Payload, Send } from "@fosscord/gateway";
|
||||||
import { Send } from "@fosscord/gateway/util/Send";
|
|
||||||
|
|
||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
|
||||||
|
|
||||||
export async function onResume(this: WebSocket, data: Payload) {
|
export async function onResume(this: WebSocket, data: Payload) {
|
||||||
console.log("Got Resume -> cancel not implemented");
|
console.log("Got Resume -> cancel not implemented");
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { VoiceStateUpdateSchema } from "../schema/VoiceStateUpdateSchema";
|
import { VoiceStateUpdateSchema } from "../schema/VoiceStateUpdateSchema";
|
||||||
import { Payload } from "@fosscord/gateway/util/Constants";
|
import { Payload, WebSocket, genVoiceToken } from "@fosscord/gateway";
|
||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
|
||||||
import { check } from "./instanceOf";
|
import { check } from "./instanceOf";
|
||||||
import {
|
import {
|
||||||
Config,
|
Config,
|
||||||
@ -12,7 +11,6 @@ import {
|
|||||||
VoiceState,
|
VoiceState,
|
||||||
VoiceStateUpdateEvent,
|
VoiceStateUpdateEvent,
|
||||||
} from "@fosscord/util";
|
} from "@fosscord/util";
|
||||||
import { genVoiceToken } from "@fosscord/gateway/util/SessionUtils";
|
|
||||||
// TODO: check if a voice server is setup
|
// TODO: check if a voice server is setup
|
||||||
// Notice: Bot users respect the voice channel's user limit, if set. When the voice channel is full, you will not receive the Voice State Update or Voice Server Update events in response to your own Voice State Update. Having MANAGE_CHANNELS permission bypasses this limit and allows you to join regardless of the channel being full or not.
|
// Notice: Bot users respect the voice channel's user limit, if set. When the voice channel is full, you will not receive the Voice State Update or Voice Server Update events in response to your own Voice State Update. Having MANAGE_CHANNELS permission bypasses this limit and allows you to join regardless of the channel being full or not.
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Payload } from "@fosscord/gateway/util/Constants";
|
import { WebSocket, Payload } from "@fosscord/gateway";
|
||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
|
||||||
import { onHeartbeat } from "./Heartbeat";
|
import { onHeartbeat } from "./Heartbeat";
|
||||||
import { onIdentify } from "./Identify";
|
import { onIdentify } from "./Identify";
|
||||||
import { onLazyRequest } from "./LazyRequest";
|
import { onLazyRequest } from "./LazyRequest";
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { instanceOf } from "lambert-server";
|
import { instanceOf } from "lambert-server";
|
||||||
import { CLOSECODES } from "@fosscord/gateway/util/Constants";
|
import { WebSocket, CLOSECODES } from "@fosscord/gateway";
|
||||||
import WebSocket from "@fosscord/gateway/util/WebSocket";
|
|
||||||
|
|
||||||
export function check(this: WebSocket, schema: any, data: any) {
|
export function check(this: WebSocket, schema: any, data: any) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -2,9 +2,7 @@ var erlpack: any;
|
|||||||
try {
|
try {
|
||||||
erlpack = require("@yukikaze-bot/erlpack");
|
erlpack = require("@yukikaze-bot/erlpack");
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
import { Payload } from "@fosscord/gateway/util/Constants";
|
import { Payload, WebSocket } from "@fosscord/gateway";
|
||||||
|
|
||||||
import WebSocket from "./WebSocket";
|
|
||||||
|
|
||||||
export async function Send(socket: WebSocket, data: Payload) {
|
export async function Send(socket: WebSocket, data: Payload) {
|
||||||
let buffer: Buffer | string;
|
let buffer: Buffer | string;
|
||||||
@ -20,7 +18,7 @@ export async function Send(socket: WebSocket, data: Payload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
socket.send(buffer, (err) => {
|
socket.send(buffer, (err: any) => {
|
||||||
if (err) return rej(err);
|
if (err) return rej(err);
|
||||||
return res(null);
|
return res(null);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import WS from "ws";
|
|||||||
import { Deflate } from "zlib";
|
import { Deflate } from "zlib";
|
||||||
import { Channel } from "amqplib";
|
import { Channel } from "amqplib";
|
||||||
|
|
||||||
interface WebSocket extends WS {
|
export interface WebSocket extends WS {
|
||||||
version: number;
|
version: number;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
session_id: string;
|
session_id: string;
|
||||||
@ -19,5 +19,3 @@ interface WebSocket extends WS {
|
|||||||
permissions: Record<string, Permissions>;
|
permissions: Record<string, Permissions>;
|
||||||
events: Record<string, Function>;
|
events: Record<string, Function>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WebSocket;
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { CLOSECODES } from "./Constants";
|
import { CLOSECODES } from "./Constants";
|
||||||
import WebSocket from "./WebSocket";
|
import { WebSocket } from "./WebSocket";
|
||||||
|
|
||||||
// TODO: make heartbeat timeout configurable
|
// TODO: make heartbeat timeout configurable
|
||||||
export function setHeartbeat(socket: WebSocket) {
|
export function setHeartbeat(socket: WebSocket) {
|
||||||
|
|||||||
@ -70,8 +70,10 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"@fosscord/gateway": ["src/index"],
|
||||||
"@fosscord/gateway/*": ["src/*"],
|
"@fosscord/gateway/*": ["src/*"],
|
||||||
"@fosscord/util/*": ["../util/src/*"]
|
"@fosscord/util/*": ["../util/src/*"]
|
||||||
}
|
},
|
||||||
|
"plugins": [{ "transform": "@zerollup/ts-transform-paths" }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,19 +12,19 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/fosscord/fosscord-server-util.git"
|
"url": "git+https://github.com/fosscord/fosscord-server.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"discord",
|
"discord",
|
||||||
"fosscord",
|
"fosscord",
|
||||||
"fosscord-server-util",
|
"fosscord-server",
|
||||||
"discord open source",
|
"discord open source",
|
||||||
"discord-open-source"
|
"discord-open-source"
|
||||||
],
|
],
|
||||||
"author": "Fosscord",
|
"author": "Fosscord",
|
||||||
"license": "GPLV3",
|
"license": "GPLV3",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/fosscord/fosscord-server-util/issues"
|
"url": "https://github.com/fosscord/fosscord-server/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://docs.fosscord.com/",
|
"homepage": "https://docs.fosscord.com/",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -77,6 +77,7 @@ export interface ConfigValue {
|
|||||||
maxWebhooks: number;
|
maxWebhooks: number;
|
||||||
};
|
};
|
||||||
rate: {
|
rate: {
|
||||||
|
disabled: boolean;
|
||||||
ip: Omit<RateLimitOptions, "bot_count">;
|
ip: Omit<RateLimitOptions, "bot_count">;
|
||||||
global: RateLimitOptions;
|
global: RateLimitOptions;
|
||||||
error: RateLimitOptions;
|
error: RateLimitOptions;
|
||||||
@ -188,6 +189,7 @@ export const DefaultConfigOptions: ConfigValue = {
|
|||||||
maxWebhooks: 10,
|
maxWebhooks: 10,
|
||||||
},
|
},
|
||||||
rate: {
|
rate: {
|
||||||
|
disabled: true,
|
||||||
ip: {
|
ip: {
|
||||||
count: 500,
|
count: 500,
|
||||||
window: 5,
|
window: 5,
|
||||||
|
|||||||
@ -161,15 +161,13 @@ export class User extends BaseClass {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static async getPublicUser(user_id: string, opts?: FindOneOptions<User>) {
|
static async getPublicUser(user_id: string, opts?: FindOneOptions<User>) {
|
||||||
const user = await User.findOne(
|
return await User.findOneOrFail(
|
||||||
{ id: user_id },
|
{ id: user_id },
|
||||||
{
|
{
|
||||||
...opts,
|
...opts,
|
||||||
select: [...PublicUserProjection, ...(opts?.select || [])],
|
select: [...PublicUserProjection, ...(opts?.select || [])],
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (!user) throw new HTTPError("User not found", 404);
|
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export const Config = {
|
|||||||
get: function get() {
|
get: function get() {
|
||||||
return config.value as ConfigValue;
|
return config.value as ConfigValue;
|
||||||
},
|
},
|
||||||
set: function set(val: any) {
|
set: function set(val: Partial<ConfigValue>) {
|
||||||
if (!config) return;
|
if (!config) return;
|
||||||
config.value = val.merge(config?.value || {});
|
config.value = val.merge(config?.value || {});
|
||||||
return config.save();
|
return config.save();
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import path from "path";
|
||||||
import "reflect-metadata";
|
import "reflect-metadata";
|
||||||
import { Connection, createConnection, ValueTransformer } from "typeorm";
|
import { Connection, createConnection, ValueTransformer } from "typeorm";
|
||||||
import * as Models from "../entities";
|
import * as Models from "../entities";
|
||||||
@ -15,7 +16,7 @@ export function initDatabase() {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
promise = createConnection({
|
promise = createConnection({
|
||||||
type: "sqlite",
|
type: "sqlite",
|
||||||
database: "database.db",
|
database: path.join(process.cwd(), "database.db"),
|
||||||
// type: "postgres",
|
// type: "postgres",
|
||||||
// url: "postgres://fosscord:wb94SmuURM2Syv&@localhost/fosscord",
|
// url: "postgres://fosscord:wb94SmuURM2Syv&@localhost/fosscord",
|
||||||
//
|
//
|
||||||
|
|||||||
20
util/src/util/Email.ts
Normal file
20
util/src/util/Email.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export const EMAIL_REGEX =
|
||||||
|
/^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
|
||||||
|
export function adjustEmail(email: string): string | undefined {
|
||||||
|
if (!email) return email;
|
||||||
|
// body parser already checked if it is a valid email
|
||||||
|
const parts = <RegExpMatchArray>email.match(EMAIL_REGEX);
|
||||||
|
// @ts-ignore
|
||||||
|
if (!parts || parts.length < 5) return undefined;
|
||||||
|
const domain = parts[5];
|
||||||
|
const user = parts[1];
|
||||||
|
|
||||||
|
// TODO: check accounts with uncommon email domains
|
||||||
|
if (domain === "gmail.com" || domain === "googlemail.com") {
|
||||||
|
// replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator
|
||||||
|
return user.replace(/[.]|(\+.*)/g, "") + "@gmail.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
return email;
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ import { Channel } from "amqplib";
|
|||||||
import { RabbitMQ } from "./RabbitMQ";
|
import { RabbitMQ } from "./RabbitMQ";
|
||||||
import EventEmitter from "events";
|
import EventEmitter from "events";
|
||||||
import { EVENT, Event } from "../interfaces";
|
import { EVENT, Event } from "../interfaces";
|
||||||
const events = new EventEmitter();
|
export const events = new EventEmitter();
|
||||||
|
|
||||||
export async function emitEvent(payload: Omit<Event, "created_at">) {
|
export async function emitEvent(payload: Omit<Event, "created_at">) {
|
||||||
const id = (payload.channel_id || payload.user_id || payload.guild_id) as string;
|
const id = (payload.channel_id || payload.user_id || payload.guild_id) as string;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import jwt, { VerifyOptions } from "jsonwebtoken";
|
import jwt, { VerifyOptions } from "jsonwebtoken";
|
||||||
|
import { Config } from "./Config";
|
||||||
import { User } from "../entities";
|
import { User } from "../entities";
|
||||||
|
|
||||||
export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] };
|
export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] };
|
||||||
@ -21,3 +22,22 @@ export function checkToken(token: string, jwtSecret: string): Promise<any> {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function generateToken(id: string) {
|
||||||
|
const iat = Math.floor(Date.now() / 1000);
|
||||||
|
const algorithm = "HS256";
|
||||||
|
|
||||||
|
return new Promise((res, rej) => {
|
||||||
|
jwt.sign(
|
||||||
|
{ id: id, iat },
|
||||||
|
Config.get().security.jwtSecret,
|
||||||
|
{
|
||||||
|
algorithm,
|
||||||
|
},
|
||||||
|
(err, token) => {
|
||||||
|
if (err) return rej(err);
|
||||||
|
return res(token);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -1,11 +1,12 @@
|
|||||||
export * from "./ApiError";
|
export * from "./ApiError";
|
||||||
export * from "./BitField";
|
export * from "./BitField";
|
||||||
export * from "./checkToken";
|
export * from "./Token";
|
||||||
export * from "./cdn";
|
export * from "./cdn";
|
||||||
export * from "./Config";
|
export * from "./Config";
|
||||||
export * from "./Constants";
|
export * from "./Constants";
|
||||||
export * from "./Database";
|
export * from "./Database";
|
||||||
export * from "./Event";
|
export * from "./Event";
|
||||||
|
export * from "./Email";
|
||||||
export * from "./Intents";
|
export * from "./Intents";
|
||||||
export * from "./MessageFlags";
|
export * from "./MessageFlags";
|
||||||
export * from "./Permissions";
|
export * from "./Permissions";
|
||||||
|
|||||||
@ -68,12 +68,6 @@
|
|||||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true
|
||||||
"plugins": [
|
|
||||||
{
|
|
||||||
"transform": "ts-transform-json-schema",
|
|
||||||
"type": "program"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user