✨ generate openapi documentation
This commit is contained in:
parent
eb2f447d96
commit
2a094c603a
File diff suppressed because it is too large
Load Diff
@ -1,90 +0,0 @@
|
|||||||
{
|
|
||||||
"UserProfileResponse": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"user": {
|
|
||||||
"$ref": "#/definitions/UserPublic"
|
|
||||||
},
|
|
||||||
"connected_accounts": {
|
|
||||||
"$ref": "#/definitions/PublicConnectedAccount"
|
|
||||||
},
|
|
||||||
"premium_guild_since": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "date-time"
|
|
||||||
},
|
|
||||||
"premium_since": {
|
|
||||||
"type": "string",
|
|
||||||
"format": "date-time"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false,
|
|
||||||
"required": [
|
|
||||||
"connected_accounts",
|
|
||||||
"user"
|
|
||||||
],
|
|
||||||
"definitions": {
|
|
||||||
"UserPublic": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"username": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"discriminator": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"public_flags": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"avatar": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"accent_color": {
|
|
||||||
"type": "integer"
|
|
||||||
},
|
|
||||||
"banner": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"bio": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"bot": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false,
|
|
||||||
"required": [
|
|
||||||
"bio",
|
|
||||||
"bot",
|
|
||||||
"discriminator",
|
|
||||||
"id",
|
|
||||||
"public_flags",
|
|
||||||
"username"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"PublicConnectedAccount": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"type": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"verifie": {
|
|
||||||
"type": "boolean"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"additionalProperties": false,
|
|
||||||
"required": [
|
|
||||||
"name",
|
|
||||||
"type",
|
|
||||||
"verifie"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,15 @@
|
|||||||
import { traverseDirectory } from "lambert-server";
|
const { traverseDirectory } = require("lambert-server");
|
||||||
import path from "path";
|
const path = require("path");
|
||||||
import express from "express";
|
const express = require("express");
|
||||||
import * as RouteUtility from "../dist/util/route";
|
const RouteUtility = require("../dist/util/route");
|
||||||
import { RouteOptions } from "../dist/util/route";
|
|
||||||
const Router = express.Router;
|
const Router = express.Router;
|
||||||
|
|
||||||
const routes = new Map<string, RouteUtility.RouteOptions>();
|
/**
|
||||||
|
* Some documentation.
|
||||||
|
*
|
||||||
|
* @type {Map<string, RouteUtility.RouteOptions>}
|
||||||
|
*/
|
||||||
|
const routes = new Map();
|
||||||
let currentPath = "";
|
let currentPath = "";
|
||||||
let currentFile = "";
|
let currentFile = "";
|
||||||
const methods = ["get", "post", "put", "delete", "patch"];
|
const methods = ["get", "post", "put", "delete", "patch"];
|
||||||
@ -13,13 +17,13 @@ const methods = ["get", "post", "put", "delete", "patch"];
|
|||||||
function registerPath(file, method, prefix, path, ...args) {
|
function registerPath(file, method, prefix, path, ...args) {
|
||||||
const urlPath = prefix + path;
|
const urlPath = prefix + path;
|
||||||
const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts");
|
const sourceFile = file.replace("/dist/", "/src/").replace(".js", ".ts");
|
||||||
const opts: RouteOptions = args.find((x) => typeof x === "object");
|
const opts = args.find((x) => typeof x === "object");
|
||||||
if (opts) {
|
if (opts) {
|
||||||
routes.set(urlPath + "|" + method, opts); // @ts-ignore
|
routes.set(urlPath + "|" + method, opts); // @ts-ignore
|
||||||
opts.file = sourceFile;
|
opts.file = sourceFile;
|
||||||
// console.log(method, urlPath, opts);
|
// console.log(method, urlPath, opts);
|
||||||
} else {
|
} else {
|
||||||
console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`, args);
|
console.log(`${sourceFile}\nrouter.${method}("${path}") is missing the "route()" description middleware\n`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +46,7 @@ express.Router = (opts) => {
|
|||||||
return router;
|
return router;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function getRouteDescriptions() {
|
module.exports = function getRouteDescriptions() {
|
||||||
const root = path.join(__dirname, "..", "dist", "routes", "/");
|
const root = path.join(__dirname, "..", "dist", "routes", "/");
|
||||||
traverseDirectory({ dirname: root, recursive: true }, (file) => {
|
traverseDirectory({ dirname: root, recursive: true }, (file) => {
|
||||||
currentFile = file;
|
currentFile = file;
|
||||||
@ -52,7 +56,11 @@ export default function getRouteDescriptions() {
|
|||||||
if (path.endsWith("/index")) path = path.slice(0, "/index".length * -1); // delete index from path
|
if (path.endsWith("/index")) path = path.slice(0, "/index".length * -1); // delete index from path
|
||||||
currentPath = path;
|
currentPath = path;
|
||||||
|
|
||||||
require(file);
|
try {
|
||||||
|
require(file);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("error loading file " + file, error);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return routes;
|
return routes;
|
||||||
}
|
};
|
@ -4,9 +4,9 @@ import path from "path";
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import * as TJS from "typescript-json-schema";
|
import * as TJS from "typescript-json-schema";
|
||||||
import "missing-native-js-functions";
|
import "missing-native-js-functions";
|
||||||
const schemaPath = path.join(__dirname, "..", "assets", "responses.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,10 +14,13 @@ const settings: TJS.PartialArgs = {
|
|||||||
noExtraProps: true,
|
noExtraProps: true,
|
||||||
defaultProps: false
|
defaultProps: false
|
||||||
};
|
};
|
||||||
const compilerOptions: TJS.CompilerOptions = {
|
const compilerOptions = {
|
||||||
strictNullChecks: true
|
strictNullChecks: true
|
||||||
};
|
};
|
||||||
const ExcludedSchemas = [
|
const Excluded = [
|
||||||
|
"DefaultSchema",
|
||||||
|
"Schema",
|
||||||
|
"EntitySchema",
|
||||||
"ServerResponse",
|
"ServerResponse",
|
||||||
"Http2ServerResponse",
|
"Http2ServerResponse",
|
||||||
"global.Express.Response",
|
"global.Express.Response",
|
||||||
@ -32,13 +35,13 @@ function main() {
|
|||||||
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("Response") && !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 } };
|
||||||
@ -47,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,60 +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 schemaPath = path.join(__dirname, "..", "assets", "schemas.json");
|
|
||||||
|
|
||||||
const settings: TJS.PartialArgs = {
|
|
||||||
required: true,
|
|
||||||
ignoreErrors: true,
|
|
||||||
excludePrivate: true,
|
|
||||||
defaultNumberType: "integer",
|
|
||||||
noExtraProps: true,
|
|
||||||
defaultProps: false
|
|
||||||
};
|
|
||||||
const compilerOptions: TJS.CompilerOptions = {
|
|
||||||
strictNullChecks: true
|
|
||||||
};
|
|
||||||
const ExcludedSchemas = ["DefaultSchema", "Schema", "EntitySchema"];
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
const program = TJS.getProgramFromFiles(walk(path.join(__dirname, "..", "src", "routes")), compilerOptions);
|
|
||||||
const generator = TJS.buildGenerator(program, settings);
|
|
||||||
if (!generator || !program) return;
|
|
||||||
|
|
||||||
const schemas = generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x));
|
|
||||||
console.log(schemas);
|
|
||||||
|
|
||||||
var definitions: any = {};
|
|
||||||
|
|
||||||
for (const name of schemas) {
|
|
||||||
const part = TJS.generateSchema(program, name, settings, [], generator as TJS.JsonSchemaGenerator);
|
|
||||||
if (!part) continue;
|
|
||||||
|
|
||||||
definitions = { ...definitions, [name]: { ...part } };
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(schemaPath, JSON.stringify(definitions, null, 4));
|
|
||||||
}
|
|
||||||
|
|
||||||
// #/definitions/
|
|
||||||
main();
|
|
||||||
|
|
||||||
function walk(dir: string) {
|
|
||||||
var results = [] as string[];
|
|
||||||
var list = fs.readdirSync(dir);
|
|
||||||
list.forEach(function (file) {
|
|
||||||
file = dir + "/" + file;
|
|
||||||
var stat = fs.statSync(file);
|
|
||||||
if (stat && stat.isDirectory()) {
|
|
||||||
/* Recurse into a subdirectory */
|
|
||||||
results = results.concat(walk(file));
|
|
||||||
} else {
|
|
||||||
if (!file.endsWith(".ts")) return;
|
|
||||||
results.push(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return results;
|
|
||||||
}
|
|
127
api/scripts/generate_openapi_schema.js
Normal file
127
api/scripts/generate_openapi_schema.js
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
// 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;
|
||||||
|
obj.responses = {
|
||||||
|
[status]: {
|
||||||
|
...(route.test.response.body
|
||||||
|
? {
|
||||||
|
description: obj.responses[status].description || "",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
$ref: `#/components/schemas/${route.test.response.body}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {})
|
||||||
|
}
|
||||||
|
}.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,92 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ExcludedSchemas = [
|
|
||||||
"DefaultSchema",
|
|
||||||
"Schema",
|
|
||||||
"EntitySchema",
|
|
||||||
"ServerResponse",
|
|
||||||
"Http2ServerResponse",
|
|
||||||
"global.Express.Response",
|
|
||||||
"Response",
|
|
||||||
"e.Response",
|
|
||||||
"request.Response",
|
|
||||||
"supertest.Response"
|
|
||||||
];
|
|
||||||
|
|
||||||
function apiSchemas() {
|
|
||||||
const program = TJS.getProgramFromFiles([path.join(__dirname, "..", "src", "schema", "index.ts")], compilerOptions);
|
|
||||||
const generator = TJS.buildGenerator(program, settings);
|
|
||||||
|
|
||||||
const schemas = generator
|
|
||||||
.getUserSymbols()
|
|
||||||
.filter((x) => x.endsWith("Response") && !ExcludedSchemas.includes(x))
|
|
||||||
.concat(generator.getUserSymbols().filter((x) => x.endsWith("Schema") && !ExcludedSchemas.includes(x)));
|
|
||||||
|
|
||||||
// @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();
|
|
@ -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)))
|
||||||
|
@ -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);
|
||||||
|
@ -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: "",
|
||||||
|
@ -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({ sticker_packs: [] }).status(200);
|
res.json({ sticker_packs: [] }).status(200);
|
||||||
});
|
});
|
||||||
|
@ -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) {
|
||||||
|
@ -9,7 +9,7 @@ import addFormats from "ajv-formats";
|
|||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import { User } from "@fosscord/util";
|
import { User } from "@fosscord/util";
|
||||||
|
|
||||||
const SchemaPath = join(__dirname, "..", "assets", "responses.json");
|
const SchemaPath = join(__dirname, "..", "assets", "schemas.json");
|
||||||
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
|
const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
|
||||||
export const ajv = new Ajv({
|
export const ajv = new Ajv({
|
||||||
allErrors: true,
|
allErrors: true,
|
||||||
@ -64,7 +64,7 @@ describe("Automatic unit tests with route description middleware", () => {
|
|||||||
routes.forEach((route, pathAndMethod) => {
|
routes.forEach((route, pathAndMethod) => {
|
||||||
const [path, method] = pathAndMethod.split("|");
|
const [path, method] = pathAndMethod.split("|");
|
||||||
|
|
||||||
test(path, async (done) => {
|
test(`${method.toUpperCase()} ${path}`, async (done) => {
|
||||||
if (!route.test) {
|
if (!route.test) {
|
||||||
console.log(`${(route as any).file}\nrouter.${method} is missing the test property`);
|
console.log(`${(route as any).file}\nrouter.${method} is missing the test property`);
|
||||||
return done();
|
return done();
|
||||||
|
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;
|
||||||
|
}
|
@ -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";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user