From ea3fbc5caa2b31988c6af87892db682f63a176dd Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 4 Apr 2021 17:39:11 +0200 Subject: [PATCH 01/28] added config service --- api/nodemon.json | 3 ++ api/package.json | 14 ++++++- api/src/config/dto.ts | 11 ++++++ api/src/config/index.ts | 1 + api/src/config/service.ts | 39 +++++++++++++++++++ yarn.lock | 80 ++++++++++++++++++++++++++++++++++++++- 6 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 api/nodemon.json create mode 100644 api/src/config/dto.ts create mode 100644 api/src/config/service.ts diff --git a/api/nodemon.json b/api/nodemon.json new file mode 100644 index 000000000..12b11701e --- /dev/null +++ b/api/nodemon.json @@ -0,0 +1,3 @@ +{ + "watch": ["dist", ".env"] +} diff --git a/api/package.json b/api/package.json index 4f199e58f..443857683 100644 --- a/api/package.json +++ b/api/package.json @@ -39,15 +39,25 @@ "dependencies": { "@dzcode.io/common": "1.0.0", "axios": "^0.21.1", - "body-parser": "^1.19.0", + "class-transformer": "^0.4.0", + "class-validator": "^0.13.1", "cors": "^2.8.5", + "dotenv": "^8.2.0", "express": "^4.17.1", - "morgan": "^1.10.0" + "express-rate-limit": "^5.2.6", + "express-winston": "^4.1.0", + "helmet": "^4.4.1", + "morgan": "^1.10.0", + "reflect-metadata": "^0.1.13", + "typedi": "^0.10.0", + "winston": "^3.3.3" }, "devDependencies": { "@types/body-parser": "^1.19.0", "@types/cors": "^2.8.9", + "@types/dotenv": "^8.2.0", "@types/express": "^4.17.9", + "@types/express-rate-limit": "^5.1.1", "@types/morgan": "^1.9.2", "@types/node": "^14.14.16", "eslint-config-prettier": "^7.1.0", diff --git a/api/src/config/dto.ts b/api/src/config/dto.ts new file mode 100644 index 000000000..251a6fe28 --- /dev/null +++ b/api/src/config/dto.ts @@ -0,0 +1,11 @@ +import { Environment } from "@dzcode.io/common/dist/types"; +import { Matches } from "class-validator"; + +const environment: Environment[] = ["development", "staging", "production"]; + +export class ENV { + PORT = 7070; + + @Matches("(" + environment.join(")|(") + ")") + ENV: Environment = "development"; +} diff --git a/api/src/config/index.ts b/api/src/config/index.ts index cd6b66c99..262d377d2 100644 --- a/api/src/config/index.ts +++ b/api/src/config/index.ts @@ -1,5 +1,6 @@ import { Environment } from "@dzcode.io/common/dist/types"; import { fsConfig } from "@dzcode.io/common/dist/config"; + export const fullstackConfig = fsConfig( (process.env as unknown) as Environment, ); diff --git a/api/src/config/service.ts b/api/src/config/service.ts new file mode 100644 index 000000000..20a4ea093 --- /dev/null +++ b/api/src/config/service.ts @@ -0,0 +1,39 @@ +import { ENV } from "./dto"; +import { Service } from "typedi"; +import { config } from "dotenv"; +import { plainToClass } from "class-transformer"; +import { validateSync } from "class-validator"; + +let _env: ENV; + +@Service() +export class ConfigService { + constructor() { + this.generateConfig(); + } + + public env = () => _env; + + private generateConfig = () => { + if (_env) return; + + const _config = config(); + const output = plainToClass(ENV, { + ...process.env, + ...(_config.parsed || {}), + }); + + const errors = validateSync(output); + + if (errors.length > 0) + throw new Error( + `⚠️ Errors in .env file in the following keys:${errors.reduce( + (pV, cV) => + (pV += "\n" + cV.property + " : " + JSON.stringify(cV.constraints)), + "", + )}`, + ); + + _env = output; + }; +} diff --git a/yarn.lock b/yarn.lock index 5a5293ebf..0090122a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1704,6 +1704,13 @@ resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.9.tgz#4bd1fcac72eca8d5bec93e76c7fdcbdc1bc2cd4a" integrity sha512-zurD1ibz21BRlAOIKP8yhrxlqKx6L9VCwkB5kMiP6nZAhoF5MvC7qS1qPA7nRcr1GJolfkQC7/EAL4hdYejLtg== +"@types/dotenv@^8.2.0": + version "8.2.0" + resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-8.2.0.tgz#5cd64710c3c98e82d9d15844375a33bf1b45d053" + integrity sha512-ylSC9GhfRH7m1EUXBXofhgx4lUWmFeQDINW5oLuS+gxWdfUeW4zJdeVTYVkexEW+e2VUvlZR2kGnGGipAWR7kw== + dependencies: + dotenv "*" + "@types/duplexify@^3.6.0": version "3.6.0" resolved "https://registry.yarnpkg.com/@types/duplexify/-/duplexify-3.6.0.tgz#dfc82b64bd3a2168f5bd26444af165bf0237dcd8" @@ -1737,6 +1744,13 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== +"@types/express-rate-limit@^5.1.1": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/express-rate-limit/-/express-rate-limit-5.1.1.tgz#e5b0239d18c1580e52ae56dce4248333302a1dc8" + integrity sha512-6oMYZBLlhxC5sdcRXXz528QyfGz3zTy9YdHwqlxLfgx5Cd3zwYaUjjPpJcaTtHmRefLi9P8kLBPz2wB7yz4JtQ== + dependencies: + "@types/express" "*" + "@types/express-serve-static-core@^4.17.18": version "4.17.18" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz#8371e260f40e0e1ca0c116a9afcd9426fa094c40" @@ -1746,7 +1760,7 @@ "@types/qs" "*" "@types/range-parser" "*" -"@types/express@^4.17.9": +"@types/express@*", "@types/express@^4.17.9": version "4.17.11" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.11.tgz#debe3caa6f8e5fcda96b47bd54e2f40c4ee59545" integrity sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg== @@ -2033,6 +2047,11 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== +"@types/validator@^13.1.3": + version "13.1.3" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.1.3.tgz#366b394aa3fbeed2392bf0a20ded606fa4a3d35e" + integrity sha512-DaOWN1zf7j+8nHhqXhIgNmS+ltAC53NXqGxYuBhWqWgqolRhddKzfZU814lkHQSTG0IUfQxU7Cg0gb8fFWo2mA== + "@types/webpack-sources@*": version "2.1.0" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-2.1.0.tgz#8882b0bd62d1e0ce62f183d0d01b72e6e82e8c10" @@ -3458,6 +3477,11 @@ cjson@^0.3.1: dependencies: json-parse-helpfulerror "^1.0.3" +class-transformer@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.4.0.tgz#b52144117b423c516afb44cc1c76dbad31c2165b" + integrity sha512-ETWD/H2TbWbKEi7m9N4Km5+cw1hNcqJSxlSYhsLsNjQzWWiZIYA1zafxpK9PwVfaZ6AqR5rrjPVUBGESm5tQUA== + class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -3468,6 +3492,15 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +class-validator@^0.13.1: + version "0.13.1" + resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.13.1.tgz#381b2001ee6b9e05afd133671fbdf760da7dec67" + integrity sha512-zWIeYFhUitvAHBwNhDdCRK09hWx+P0HUwFE8US8/CxFpMVzkUK8RJl7yOIE+BVu2lxyPNgeOaFv78tLE47jBIg== + dependencies: + "@types/validator" "^13.1.3" + libphonenumber-js "^1.9.7" + validator "^13.5.2" + clean-css@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78" @@ -4566,6 +4599,11 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" +dotenv@*, dotenv@^8.2.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" + integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== + dotenv@^6.1.0: version "6.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064" @@ -5174,6 +5212,19 @@ expect@^26.6.2: jest-message-util "^26.6.2" jest-regex-util "^26.0.0" +express-rate-limit@^5.2.6: + version "5.2.6" + resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-5.2.6.tgz#b454e1be8a252081bda58460e0a25bf43ee0f7b0" + integrity sha512-nE96xaxGfxiS5jP3tD3kIW1Jg9yQgX0rXCs3rCkZtmbWHEGyotwaezkLj7bnB41Z0uaOLM8W4AX6qHao4IZ2YA== + +express-winston@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/express-winston/-/express-winston-4.1.0.tgz#3fd3ecea55d50ff6aee49a66e1aaa3cba8b67c93" + integrity sha512-0DaIjvNADBzC/K4Qw3UwEQc8HRjbajTaP/M43rw0LJpZcQ7SQTPfxkLsnx3ABHEO7EFNQXTpqL0BZPiwkGV8hg== + dependencies: + chalk "^2.4.2" + lodash "^4.17.20" + express@^4.16.4, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -6178,6 +6229,11 @@ he@^1.2.0: resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== +helmet@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.4.1.tgz#a17e1444d81d7a83ddc6e6f9bc6e2055b994efe7" + integrity sha512-G8tp0wUMI7i8wkMk2xLcEvESg5PiCitFMYgGRc/PwULB0RVhTP5GFdxOwvJwp9XVha8CuS8mnhmE8I/8dx/pbw== + hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" @@ -7995,6 +8051,11 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +libphonenumber-js@^1.9.7: + version "1.9.15" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.9.15.tgz#d08c533009216c993382c833eaf1639217bd4350" + integrity sha512-gKgvXJmYX00mS+dsy7VUWAsnfKeAyEqGFmDBDor2mtHo08RkceNLQ6PUKS+6h7Tq1SK/4MP2Re8rJ3J0gHmfTA== + lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -10858,6 +10919,11 @@ redux@*, redux@^4.0.0, redux@^4.0.5: loose-envify "^1.4.0" symbol-observable "^1.2.0" +reflect-metadata@^0.1.13: + version "0.1.13" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" + integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== + refractor@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.3.0.tgz#42a7d58f14a1d1bcda52a8c66123601a71e5d47d" @@ -12543,6 +12609,11 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typedi@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/typedi/-/typedi-0.10.0.tgz#e8f9a5ee100b84addbdfb57fe90d8d9ed2a21dea" + integrity sha512-v3UJF8xm68BBj6AF4oQML3ikrfK2c9EmZUyLOfShpJuItAqVBHWP/KtpGinkSsIiP6EZyyb6Z3NXyW9dgS9X1w== + typescript@^4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" @@ -12797,6 +12868,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validator@^13.5.2: + version "13.5.2" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.5.2.tgz#c97ae63ed4224999fb6f42c91eaca9567fe69a46" + integrity sha512-mD45p0rvHVBlY2Zuy3F3ESIe1h5X58GPfAtslBjY7EtTqGquZTj+VX/J4RnHWN8FKq0C9WRVt1oWAcytWRuYLQ== + value-equal@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" @@ -13097,7 +13173,7 @@ winston-transport@^4.4.0: readable-stream "^2.3.7" triple-beam "^1.2.0" -winston@^3.0.0: +winston@^3.0.0, winston@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/winston/-/winston-3.3.3.tgz#ae6172042cafb29786afa3d09c8ff833ab7c9170" integrity sha512-oEXTISQnC8VlSAKf1KYSSd7J6IWuRPQqDdo8eoRNaYKLvwSb5+79Z3Yi1lrl6KDpU6/VWaxpakDAtb1oQ4n9aw== From 068b6138d884c7206d34f0dd7879dbb4f2de9c61 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 4 Apr 2021 17:39:40 +0200 Subject: [PATCH 02/28] refactored loaders folder --- api/src/index.ts | 17 +++++++++------- api/src/loaders/controller.ts | 6 ++++++ api/src/loaders/express-loader.ts | 18 ----------------- api/src/loaders/index.ts | 16 ++++++++++----- api/src/loaders/logger.ts | 21 ++++++++++++++++++++ api/src/loaders/parser.ts | 8 ++++++++ api/src/loaders/security.ts | 33 +++++++++++++++++++++++++++++++ 7 files changed, 89 insertions(+), 30 deletions(-) create mode 100644 api/src/loaders/controller.ts delete mode 100644 api/src/loaders/express-loader.ts create mode 100644 api/src/loaders/logger.ts create mode 100644 api/src/loaders/parser.ts create mode 100644 api/src/loaders/security.ts diff --git a/api/src/index.ts b/api/src/index.ts index 5cd96cfe4..b872b4974 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -1,16 +1,19 @@ +import "reflect-metadata"; + +import { ConfigService } from "./config/service"; +import { Container } from "typedi"; import express from "express"; -import { fullstackConfig } from "./config"; -import loader from "./loaders"; -import routes from "./routes"; +import { rootLoader } from "./loaders"; +// create express app const app = express(); -const port = process.env.PORT || fullstackConfig.api.port; -loader.init({ app }); +// add loaders +rootLoader({ app }); -app.use(routes); +// start the app +const port = Container.get(ConfigService).env().PORT; -// Start the server app.listen(port, () => console.log(`Api server listening at http://localhost:${port}`), ); diff --git a/api/src/loaders/controller.ts b/api/src/loaders/controller.ts new file mode 100644 index 000000000..29a30b0f1 --- /dev/null +++ b/api/src/loaders/controller.ts @@ -0,0 +1,6 @@ +import { Loader } from "."; +import router from "../routes"; + +export const controllerLoader: Loader = ({ app }) => { + app.use(router); +}; diff --git a/api/src/loaders/express-loader.ts b/api/src/loaders/express-loader.ts deleted file mode 100644 index 51c16f22c..000000000 --- a/api/src/loaders/express-loader.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Application } from "express"; -import bodyParser from "body-parser"; -import cors from "cors"; -import morgan from "morgan"; - -export default function expressLoader({ app }: { app: Application }): void { - app.use( - cors({ - allowedHeaders: - process.env.NODE_ENV === "development" - ? ["http://localhost:8080"] - : ["https://www.dzcode.io", "https://stage.dzcode.io"], - origin: true, - }), - ); - app.use(morgan("dev")); - app.use(bodyParser.json()); -} diff --git a/api/src/loaders/index.ts b/api/src/loaders/index.ts index a42ec66e3..29b431284 100644 --- a/api/src/loaders/index.ts +++ b/api/src/loaders/index.ts @@ -1,8 +1,14 @@ import { Application } from "express"; -import expressLoader from "./express-loader"; +import { controllerLoader } from "./controller"; +import { loggerLoader } from "./logger"; +import { parserLoader } from "./parser"; +import { securityLoader } from "./security"; -export default { - init({ app }: { app: Application }): void { - expressLoader({ app }); - }, +export const rootLoader: Loader = ({ app }) => { + loggerLoader({ app }); + securityLoader({ app }); + parserLoader({ app }); + controllerLoader({ app }); }; + +export type Loader = ({ app }: { app: Application }) => void; diff --git a/api/src/loaders/logger.ts b/api/src/loaders/logger.ts new file mode 100644 index 000000000..5bbe2a5bd --- /dev/null +++ b/api/src/loaders/logger.ts @@ -0,0 +1,21 @@ +import { Loader } from "."; +import expressWinston from "express-winston"; +import winston from "winston"; + +export const loggerLoader: Loader = ({ app }) => { + app.use( + expressWinston.logger({ + transports: [new winston.transports.Console()], + format: winston.format.combine( + winston.format.colorize(), + winston.format.json(), + ), + meta: true, // optional: control whether you want to log the meta data about the request (default to true) + msg: "HTTP {{req.method}} {{req.url}}", // optional: customize the default logging message. E.g. "{{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}" + expressFormat: true, // Use the default Express/morgan request formatting. Enabling this will override any msg if true. Will only output colors with colorize set to true + ignoreRoute: () => { + return false; + }, // optional: allows to skip some log messages based on request and/or response + }), + ); +}; diff --git a/api/src/loaders/parser.ts b/api/src/loaders/parser.ts new file mode 100644 index 000000000..225823f88 --- /dev/null +++ b/api/src/loaders/parser.ts @@ -0,0 +1,8 @@ +import { json, urlencoded } from "express"; + +import { Loader } from "."; + +export const parserLoader: Loader = ({ app }) => { + app.use(json()); + app.use(urlencoded()); +}; diff --git a/api/src/loaders/security.ts b/api/src/loaders/security.ts new file mode 100644 index 000000000..c34ffbb63 --- /dev/null +++ b/api/src/loaders/security.ts @@ -0,0 +1,33 @@ +import { ConfigService } from "../config/service"; +import { Container } from "typedi"; +import { Loader } from "."; +import cors from "cors"; +import helmet from "helmet"; +import rateLimit from "express-rate-limit"; + +const env = Container.get(ConfigService).env().ENV; + +export const securityLoader: Loader = ({ app }) => { + app.use( + cors({ + allowedHeaders: + env === "development" + ? ["http://localhost:8080"] + : env === "staging" + ? ["https://stage.dzcode.io"] + : env === "production" + ? ["https://www.dzcode.io"] + : [], + origin: true, + }), + ); + + app.use(helmet()); + + app.use( + rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // limit each IP to 100 requests per windowMs + }), + ); +}; From e0c2a6dd41249048324631430f02ac830a4260b7 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 4 Apr 2021 17:44:16 +0200 Subject: [PATCH 03/28] added docs for api folder --- .gitignore | 1 + api/README.md | 9 +++++++++ 2 files changed, 10 insertions(+) create mode 100644 api/README.md diff --git a/.gitignore b/.gitignore index d07a0a67f..dac664ab8 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ node_modules yarn-error.log .idea/ +.env diff --git a/api/README.md b/api/README.md new file mode 100644 index 000000000..e833c4d2b --- /dev/null +++ b/api/README.md @@ -0,0 +1,9 @@ +# API code + +## Folder structure + +The app is split into modules, each module can have a service and/or a controller + +## Dependency injection + +We use typedi for dependency injection, please check their docs. From 32cf8d6a2d68cf953e5ae0f431222bbbe197d223 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 4 Apr 2021 19:31:57 +0200 Subject: [PATCH 04/28] moved old code under app --- api/package.json | 4 ++-- api/src/{ => app}/controllers/contributors/index.ts | 0 api/src/{ => app}/controllers/etc/index.ts | 0 api/src/{ => app}/controllers/github/index.ts | 0 api/src/{ => app}/index.ts | 2 +- api/src/{ => app}/loaders/controller.ts | 0 api/src/{ => app}/loaders/index.ts | 0 api/src/{ => app}/loaders/logger.ts | 0 api/src/{ => app}/loaders/parser.ts | 0 api/src/{ => app}/loaders/security.ts | 2 +- api/src/{ => app}/routes/api/contributors/index.ts | 0 api/src/{ => app}/routes/api/github/index.ts | 0 api/src/{ => app}/routes/api/index.ts | 0 api/src/{ => app}/routes/index.ts | 0 api/src/{ => app}/services/github/index.ts | 0 api/src/{ => app}/services/github/types.ts | 0 16 files changed, 4 insertions(+), 4 deletions(-) rename api/src/{ => app}/controllers/contributors/index.ts (100%) rename api/src/{ => app}/controllers/etc/index.ts (100%) rename api/src/{ => app}/controllers/github/index.ts (100%) rename api/src/{ => app}/index.ts (88%) rename api/src/{ => app}/loaders/controller.ts (100%) rename api/src/{ => app}/loaders/index.ts (100%) rename api/src/{ => app}/loaders/logger.ts (100%) rename api/src/{ => app}/loaders/parser.ts (100%) rename api/src/{ => app}/loaders/security.ts (93%) rename api/src/{ => app}/routes/api/contributors/index.ts (100%) rename api/src/{ => app}/routes/api/github/index.ts (100%) rename api/src/{ => app}/routes/api/index.ts (100%) rename api/src/{ => app}/routes/index.ts (100%) rename api/src/{ => app}/services/github/index.ts (100%) rename api/src/{ => app}/services/github/types.ts (100%) diff --git a/api/package.json b/api/package.json index 443857683..5b8c48676 100644 --- a/api/package.json +++ b/api/package.json @@ -15,8 +15,8 @@ "test:cov:watch": "jest src --coverage --watchAll", "build": "tsc", "build:watch": "tsc --watch", - "dev": "nodemon dist/index.js", - "start": "node dist/index.js", + "dev": "nodemon dist/app/index.js", + "start": "node dist/app/index.js", "deploy": "yarn build && rimraf ./vultr/build && node vultr/deploy.js production", "deploy:stg": "yarn build && rimraf ./vultr/build && node vultr/deploy.js staging" }, diff --git a/api/src/controllers/contributors/index.ts b/api/src/app/controllers/contributors/index.ts similarity index 100% rename from api/src/controllers/contributors/index.ts rename to api/src/app/controllers/contributors/index.ts diff --git a/api/src/controllers/etc/index.ts b/api/src/app/controllers/etc/index.ts similarity index 100% rename from api/src/controllers/etc/index.ts rename to api/src/app/controllers/etc/index.ts diff --git a/api/src/controllers/github/index.ts b/api/src/app/controllers/github/index.ts similarity index 100% rename from api/src/controllers/github/index.ts rename to api/src/app/controllers/github/index.ts diff --git a/api/src/index.ts b/api/src/app/index.ts similarity index 88% rename from api/src/index.ts rename to api/src/app/index.ts index b872b4974..e365fda40 100644 --- a/api/src/index.ts +++ b/api/src/app/index.ts @@ -1,6 +1,6 @@ import "reflect-metadata"; -import { ConfigService } from "./config/service"; +import { ConfigService } from "../config/service"; import { Container } from "typedi"; import express from "express"; import { rootLoader } from "./loaders"; diff --git a/api/src/loaders/controller.ts b/api/src/app/loaders/controller.ts similarity index 100% rename from api/src/loaders/controller.ts rename to api/src/app/loaders/controller.ts diff --git a/api/src/loaders/index.ts b/api/src/app/loaders/index.ts similarity index 100% rename from api/src/loaders/index.ts rename to api/src/app/loaders/index.ts diff --git a/api/src/loaders/logger.ts b/api/src/app/loaders/logger.ts similarity index 100% rename from api/src/loaders/logger.ts rename to api/src/app/loaders/logger.ts diff --git a/api/src/loaders/parser.ts b/api/src/app/loaders/parser.ts similarity index 100% rename from api/src/loaders/parser.ts rename to api/src/app/loaders/parser.ts diff --git a/api/src/loaders/security.ts b/api/src/app/loaders/security.ts similarity index 93% rename from api/src/loaders/security.ts rename to api/src/app/loaders/security.ts index c34ffbb63..ac2a94adf 100644 --- a/api/src/loaders/security.ts +++ b/api/src/app/loaders/security.ts @@ -1,4 +1,4 @@ -import { ConfigService } from "../config/service"; +import { ConfigService } from "../../config/service"; import { Container } from "typedi"; import { Loader } from "."; import cors from "cors"; diff --git a/api/src/routes/api/contributors/index.ts b/api/src/app/routes/api/contributors/index.ts similarity index 100% rename from api/src/routes/api/contributors/index.ts rename to api/src/app/routes/api/contributors/index.ts diff --git a/api/src/routes/api/github/index.ts b/api/src/app/routes/api/github/index.ts similarity index 100% rename from api/src/routes/api/github/index.ts rename to api/src/app/routes/api/github/index.ts diff --git a/api/src/routes/api/index.ts b/api/src/app/routes/api/index.ts similarity index 100% rename from api/src/routes/api/index.ts rename to api/src/app/routes/api/index.ts diff --git a/api/src/routes/index.ts b/api/src/app/routes/index.ts similarity index 100% rename from api/src/routes/index.ts rename to api/src/app/routes/index.ts diff --git a/api/src/services/github/index.ts b/api/src/app/services/github/index.ts similarity index 100% rename from api/src/services/github/index.ts rename to api/src/app/services/github/index.ts diff --git a/api/src/services/github/types.ts b/api/src/app/services/github/types.ts similarity index 100% rename from api/src/services/github/types.ts rename to api/src/app/services/github/types.ts From bd494a763fab80203a6adbf83736f904cec9b4c0 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 4 Apr 2021 19:32:23 +0200 Subject: [PATCH 05/28] removed redoundant config/index --- api/src/config/index.spec.ts | 5 ----- api/src/config/index.ts | 6 ------ 2 files changed, 11 deletions(-) delete mode 100644 api/src/config/index.spec.ts delete mode 100644 api/src/config/index.ts diff --git a/api/src/config/index.spec.ts b/api/src/config/index.spec.ts deleted file mode 100644 index c7abc7ad5..000000000 --- a/api/src/config/index.spec.ts +++ /dev/null @@ -1,5 +0,0 @@ -describe("Mock test", () => { - test("expect 1+1 to be 2", () => { - expect(1 + 1).toBe(2); - }); -}); diff --git a/api/src/config/index.ts b/api/src/config/index.ts deleted file mode 100644 index 262d377d2..000000000 --- a/api/src/config/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Environment } from "@dzcode.io/common/dist/types"; -import { fsConfig } from "@dzcode.io/common/dist/config"; - -export const fullstackConfig = fsConfig( - (process.env as unknown) as Environment, -); From 52d968221a8a597de5289e2d7d99cff75bfe1c1a Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 4 Apr 2021 22:45:49 +0200 Subject: [PATCH 06/28] used routing-controllers & created a controller --- api/package.json | 1 + api/src/app/index.ts | 26 ++- api/src/app/loaders/controller.ts | 6 - api/src/app/loaders/index.ts | 12 +- api/src/app/types/index.ts | 5 + api/src/contributor/controller.ts | 20 ++ api/src/contributor/type.ts | 6 + yarn.lock | 366 ++++++++++++++++++++++++++++-- 8 files changed, 398 insertions(+), 44 deletions(-) delete mode 100644 api/src/app/loaders/controller.ts create mode 100644 api/src/app/types/index.ts create mode 100644 api/src/contributor/controller.ts create mode 100644 api/src/contributor/type.ts diff --git a/api/package.json b/api/package.json index 5b8c48676..ec58ecb8a 100644 --- a/api/package.json +++ b/api/package.json @@ -49,6 +49,7 @@ "helmet": "^4.4.1", "morgan": "^1.10.0", "reflect-metadata": "^0.1.13", + "routing-controllers": "^0.9.0", "typedi": "^0.10.0", "winston": "^3.3.3" }, diff --git a/api/src/app/index.ts b/api/src/app/index.ts index e365fda40..4f9a152de 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -1,19 +1,23 @@ import "reflect-metadata"; +import { createExpressServer, useContainer } from "routing-controllers"; + +import { Application } from "express"; import { ConfigService } from "../config/service"; -import { Container } from "typedi"; -import express from "express"; -import { rootLoader } from "./loaders"; +import Container from "typedi"; +import { ContributorController } from "../contributor/controller"; -// create express app -const app = express(); +// Use typedi container +useContainer(Container); -// add loaders -rootLoader({ app }); +// Create the app +const app: Application = createExpressServer({ + controllers: [ContributorController], +}); -// start the app const port = Container.get(ConfigService).env().PORT; -app.listen(port, () => - console.log(`Api server listening at http://localhost:${port}`), -); +// Start it +app.listen(port, () => { + console.log("Server listening on port: " + port); +}); diff --git a/api/src/app/loaders/controller.ts b/api/src/app/loaders/controller.ts deleted file mode 100644 index 29a30b0f1..000000000 --- a/api/src/app/loaders/controller.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Loader } from "."; -import router from "../routes"; - -export const controllerLoader: Loader = ({ app }) => { - app.use(router); -}; diff --git a/api/src/app/loaders/index.ts b/api/src/app/loaders/index.ts index 29b431284..d2b32fc1e 100644 --- a/api/src/app/loaders/index.ts +++ b/api/src/app/loaders/index.ts @@ -1,14 +1,12 @@ import { Application } from "express"; -import { controllerLoader } from "./controller"; import { loggerLoader } from "./logger"; import { parserLoader } from "./parser"; import { securityLoader } from "./security"; -export const rootLoader: Loader = ({ app }) => { - loggerLoader({ app }); - securityLoader({ app }); - parserLoader({ app }); - controllerLoader({ app }); +export const rootLoader: Loader = (params) => { + loggerLoader(params); + securityLoader(params); + parserLoader(params); }; -export type Loader = ({ app }: { app: Application }) => void; +export type Loader = (params: { app: Application }) => void; diff --git a/api/src/app/types/index.ts b/api/src/app/types/index.ts new file mode 100644 index 000000000..6aefa8215 --- /dev/null +++ b/api/src/app/types/index.ts @@ -0,0 +1,5 @@ +export interface GeneralResponse { + code?: number; + msg?: string; + debug?: Record; +} diff --git a/api/src/contributor/controller.ts b/api/src/contributor/controller.ts new file mode 100644 index 000000000..e2d7d4c44 --- /dev/null +++ b/api/src/contributor/controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get, QueryParam } from "routing-controllers"; + +import { GetContributorsResponse } from "./type"; +import { GithubService } from "../github/service"; +import { Service } from "typedi"; + +@Service() +@Controller("/Contributors") +export class ContributorController { + constructor(private readonly githubService: GithubService) {} + + @Get("/") + public async getContributor( + @QueryParam("path") path: string, + ): Promise { + return { + contributors: [], + }; + } +} diff --git a/api/src/contributor/type.ts b/api/src/contributor/type.ts new file mode 100644 index 000000000..64fe99edd --- /dev/null +++ b/api/src/contributor/type.ts @@ -0,0 +1,6 @@ +import { GeneralResponse } from "../app/types"; +import { GithubUser } from "@dzcode.io/common/dist/types"; + +export interface GetContributorsResponse extends GeneralResponse { + contributors?: GithubUser[]; +} diff --git a/yarn.lock b/yarn.lock index 0090122a8..f1a9f2018 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2329,7 +2329,7 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: +accepts@^1.3.5, accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== @@ -2493,6 +2493,11 @@ ansicolors@~0.3.2: resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" integrity sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk= +any-promise@^1.1.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" + integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= + anymatch@^1.3.0: version "1.3.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" @@ -2517,6 +2522,16 @@ anymatch@^3.0.3, anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" +append-field@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/append-field/-/append-field-0.1.0.tgz#6ddc58fa083c7bc545d3c5995b2830cc2366d44a" + integrity sha1-bdxY+gg8e8VF08WZWygwzCNm1Eo= + +append-field@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" + integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY= + aproba@^1.0.3: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" @@ -3190,6 +3205,14 @@ buffers@~0.1.1: resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= +busboy@^0.2.11: + version "0.2.14" + resolved "https://registry.yarnpkg.com/busboy/-/busboy-0.2.14.tgz#6c2a622efcf47c57bbbe1e2a9c37ad36c7925453" + integrity sha1-bCpiLvz0fFe7vh4qnDetNseSVFM= + dependencies: + dicer "0.2.5" + readable-stream "1.1.x" + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -3215,6 +3238,14 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cache-content-type@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-content-type/-/cache-content-type-1.0.1.tgz#035cde2b08ee2129f4a8315ea8f00a00dba1453c" + integrity sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA== + dependencies: + mime-types "^2.1.18" + ylru "^1.2.0" + cacheable-request@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" @@ -3630,6 +3661,16 @@ clsx@^1.0.4: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== +co-body@^6.0.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/co-body/-/co-body-6.1.0.tgz#d87a8efc3564f9bfe3aced8ef5cd04c7a8766547" + integrity sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ== + dependencies: + inflation "^2.0.0" + qs "^6.5.2" + raw-body "^2.3.3" + type-is "^1.6.16" + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -3812,6 +3853,16 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +concat-stream@^1.5.0, concat-stream@^1.5.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== + dependencies: + buffer-from "^1.0.0" + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + concurrently@^5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-5.3.0.tgz#7500de6410d043c912b2da27de3202cb489b1e7b" @@ -3867,7 +3918,7 @@ constantinople@^4.0.1: "@babel/parser" "^7.6.0" "@babel/types" "^7.6.1" -content-disposition@0.5.3: +content-disposition@0.5.3, content-disposition@~0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== @@ -3896,6 +3947,19 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== +cookie@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" + integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== + +cookies@~0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.8.0.tgz#1293ce4b391740a8406e3c9870e828c4b54f3f90" + integrity sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow== + dependencies: + depd "~2.0.0" + keygrip "~1.1.0" + copy-anything@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.1.tgz#2afbce6da684bdfcbec93752fa762819cb480d9a" @@ -3908,6 +3972,11 @@ copy-descriptor@^0.1.0: resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= +copy-to@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/copy-to/-/copy-to-2.0.1.tgz#2680fbb8068a48d08656b6098092bdafc906f4a5" + integrity sha1-JoD7uAaKSNCGVrYJgJK9r8kG9KU= + core-js-compat@^3.8.0: version "3.8.3" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.8.3.tgz#9123fb6b9cad30f0651332dc77deba48ef9b0b3f" @@ -4301,13 +4370,20 @@ debug@4, debug@4.3.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0: dependencies: ms "2.1.2" -debug@^3.1.1, debug@^3.2.6: +debug@^3.1.0, debug@^3.1.1, debug@^3.2.6: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" +debug@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -4347,6 +4423,11 @@ deep-equal@^1.0.1: object-keys "^1.1.1" regexp.prototype.flags "^1.2.0" +deep-equal@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + integrity sha1-9dJgKStmDghO/0zbyfCK0yR0SLU= + deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" @@ -4453,16 +4534,16 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= +depd@^2.0.0, depd@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" + integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== + depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= -depd@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" - integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== - destroy@^1.0.4, destroy@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" @@ -4478,6 +4559,14 @@ detect-node@^2.0.4: resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== +dicer@0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/dicer/-/dicer-0.2.5.tgz#5996c086bb33218c812c090bddc09cd12facb70f" + integrity sha1-WZbAhrszIYyBLAkL3cCc0S+stw8= + dependencies: + readable-stream "1.1.x" + streamsearch "0.1.2" + diff-sequences@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" @@ -4686,7 +4775,7 @@ enabled@2.0.x: resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== -encodeurl@~1.0.2: +encodeurl@^1.0.2, encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= @@ -4844,7 +4933,7 @@ escape-goat@^2.0.0: resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== -escape-html@~1.0.3: +escape-html@^1.0.3, escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= @@ -5217,6 +5306,20 @@ express-rate-limit@^5.2.6: resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-5.2.6.tgz#b454e1be8a252081bda58460e0a25bf43ee0f7b0" integrity sha512-nE96xaxGfxiS5jP3tD3kIW1Jg9yQgX0rXCs3rCkZtmbWHEGyotwaezkLj7bnB41Z0uaOLM8W4AX6qHao4IZ2YA== +express-session@^1.17.1: + version "1.17.1" + resolved "https://registry.yarnpkg.com/express-session/-/express-session-1.17.1.tgz#36ecbc7034566d38c8509885c044d461c11bf357" + integrity sha512-UbHwgqjxQZJiWRTMyhvWGvjBQduGCSBDhhZXYenziMFjxst5rMV+aJZ6hKPHZnPyHGsrqRICxtX8jtEbm/z36Q== + dependencies: + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~2.0.0" + on-headers "~1.0.2" + parseurl "~1.3.3" + safe-buffer "5.2.0" + uid-safe "~2.1.5" + express-winston@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/express-winston/-/express-winston-4.1.0.tgz#3fd3ecea55d50ff6aee49a66e1aaa3cba8b67c93" @@ -5708,7 +5811,7 @@ fragment-cache@^0.2.1: dependencies: map-cache "^0.2.2" -fresh@0.5.2: +fresh@0.5.2, fresh@~0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= @@ -6355,6 +6458,14 @@ htmlparser2@^3.10.1: inherits "^2.0.1" readable-stream "^3.1.1" +http-assert@^1.3.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.4.1.tgz#c5f725d677aa7e873ef736199b89686cceb37878" + integrity sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw== + dependencies: + deep-equal "~1.0.1" + http-errors "~1.7.2" + http-cache-semantics@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" @@ -6387,6 +6498,17 @@ http-errors@1.7.3, http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-errors@^1.3.1, http-errors@^1.6.3: + version "1.8.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.8.0.tgz#75d1bbe497e1044f51e4ee9e704a62f28d336507" + integrity sha512-4I8r0C5JDhT5VkvI47QktDW75rNlGVsUf/8hzjCC/wkWI/jdTRmBb9aI7erSG82r1bjKY3F6k28WnsVxB1C73A== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.2.0" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + http-errors@~1.6.2: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" @@ -6591,6 +6713,11 @@ indexes-of@^1.0.1: resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= +inflation@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/inflation/-/inflation-2.0.0.tgz#8b417e47c28f925a45133d914ca1fd389107f30f" + integrity sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8= + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -6905,6 +7032,11 @@ is-generator-fn@^2.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== +is-generator-function@^1.0.7: + version "1.0.8" + resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.8.tgz#dfb5c2b120e02b0a8d9d2c6806cd5621aa922f7b" + integrity sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ== + is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -7926,6 +8058,13 @@ jws@^4.0.0: jwa "^2.0.0" safe-buffer "^5.0.1" +keygrip@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226" + integrity sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ== + dependencies: + tsscmp "1.0.6" + keyv@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.1.0.tgz#ecc228486f69991e49e9476485a5be1e8fc5c4d9" @@ -7972,6 +8111,82 @@ klona@^2.0.4: resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0" integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA== +koa-bodyparser@^4.2.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/koa-bodyparser/-/koa-bodyparser-4.3.0.tgz#274c778555ff48fa221ee7f36a9fbdbace22759a" + integrity sha512-uyV8G29KAGwZc4q/0WUAjH+Tsmuv9ImfBUF2oZVyZtaeo0husInagyn/JH85xMSxM0hEk/mbCII5ubLDuqW/Rw== + dependencies: + co-body "^6.0.0" + copy-to "^2.0.1" + +koa-compose@^3.0.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-3.2.1.tgz#a85ccb40b7d986d8e5a345b3a1ace8eabcf54de7" + integrity sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec= + dependencies: + any-promise "^1.1.0" + +koa-compose@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/koa-compose/-/koa-compose-4.1.0.tgz#507306b9371901db41121c812e923d0d67d3e877" + integrity sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw== + +koa-convert@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/koa-convert/-/koa-convert-1.2.0.tgz#da40875df49de0539098d1700b50820cebcd21d0" + integrity sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA= + dependencies: + co "^4.6.0" + koa-compose "^3.0.0" + +koa-multer@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/koa-multer/-/koa-multer-1.0.2.tgz#d38f7ffd1db97b1aad33e7774732f000ebd67259" + integrity sha512-0kFzN4atVd+9oiG+4fYxQ9S2T3dPhKNvmhITIY606Qn9wLEmfhW0DhSpOzRYhddN//4rh/TCK95TMtflmFa5lA== + dependencies: + multer "1.3.0" + +koa-router@^7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/koa-router/-/koa-router-7.4.0.tgz#aee1f7adc02d5cb31d7d67465c9eacc825e8c5e0" + integrity sha512-IWhaDXeAnfDBEpWS6hkGdZ1ablgr6Q6pGdXCyK38RbzuH4LkUOpPqPw+3f8l8aTDrQmBQ7xJc0bs2yV4dzcO+g== + dependencies: + debug "^3.1.0" + http-errors "^1.3.1" + koa-compose "^3.0.0" + methods "^1.0.1" + path-to-regexp "^1.1.1" + urijs "^1.19.0" + +koa@^2.8.2: + version "2.13.1" + resolved "https://registry.yarnpkg.com/koa/-/koa-2.13.1.tgz#6275172875b27bcfe1d454356a5b6b9f5a9b1051" + integrity sha512-Lb2Dloc72auj5vK4X4qqL7B5jyDPQaZucc9sR/71byg7ryoD1NCaCm63CShk9ID9quQvDEi1bGR/iGjCG7As3w== + dependencies: + accepts "^1.3.5" + cache-content-type "^1.0.0" + content-disposition "~0.5.2" + content-type "^1.0.4" + cookies "~0.8.0" + debug "~3.1.0" + delegates "^1.0.0" + depd "^2.0.0" + destroy "^1.0.4" + encodeurl "^1.0.2" + escape-html "^1.0.3" + fresh "~0.5.2" + http-assert "^1.3.0" + http-errors "^1.6.3" + is-generator-function "^1.0.7" + koa-compose "^4.1.0" + koa-convert "^1.2.0" + on-finished "^2.3.0" + only "~0.0.2" + parseurl "^1.3.2" + statuses "^1.5.0" + type-is "^1.6.16" + vary "^1.1.2" + kuler@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/kuler/-/kuler-2.0.0.tgz#e2c570a3800388fb44407e851531c1d670b061b3" @@ -8538,7 +8753,7 @@ merge2@^1.3.0: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -methods@~1.1.2: +methods@^1.0.1, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -8741,6 +8956,34 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +multer@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.3.0.tgz#092b2670f6846fa4914965efc8cf94c20fec6cd2" + integrity sha1-CSsmcPaEb6SRSWXvyM+Uwg/sbNI= + dependencies: + append-field "^0.1.0" + busboy "^0.2.11" + concat-stream "^1.5.0" + mkdirp "^0.5.1" + object-assign "^3.0.0" + on-finished "^2.3.0" + type-is "^1.6.4" + xtend "^4.0.0" + +multer@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.2.tgz#2f1f4d12dbaeeba74cb37e623f234bf4d3d2057a" + integrity sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg== + dependencies: + append-field "^1.0.0" + busboy "^0.2.11" + concat-stream "^1.5.2" + mkdirp "^0.5.1" + object-assign "^4.1.1" + on-finished "^2.3.0" + type-is "^1.6.4" + xtend "^4.0.0" + multicast-dns-service-types@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" @@ -9031,6 +9274,11 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +object-assign@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" + integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I= + object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -9139,7 +9387,7 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -on-finished@^2.2.0, on-finished@~2.3.0: +on-finished@^2.2.0, on-finished@^2.3.0, on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= @@ -9179,6 +9427,11 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +only@~0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/only/-/only-0.0.2.tgz#2afde84d03e50b9a8edc444e30610a70295edfb4" + integrity sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q= + open@^6.3.0: version "6.4.0" resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9" @@ -9437,7 +9690,7 @@ parse5@5.1.1: resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178" integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug== -parseurl@~1.3.2, parseurl@~1.3.3: +parseurl@^1.3.2, parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== @@ -9500,7 +9753,7 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= -path-to-regexp@^1.7.0, path-to-regexp@^1.8.0: +path-to-regexp@^1.1.1, path-to-regexp@^1.7.0, path-to-regexp@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== @@ -10628,6 +10881,13 @@ qs@6.7.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@^6.5.2: + version "6.10.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" + integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== + dependencies: + side-channel "^1.0.4" + qs@^6.6.0: version "6.9.6" resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" @@ -10648,6 +10908,11 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== +random-bytes@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/random-bytes/-/random-bytes-1.0.0.tgz#4f68a1dc0ae58bd3fb95848c30324db75d64360b" + integrity sha1-T2ih3Arli9P7lYSMMDJNt11kNgs= + randomatic@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.1.tgz#b776efc59375984e36c537b2f51a1f0aff0da1ed" @@ -10842,7 +11107,7 @@ readable-stream@1.1.x: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.2.2, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -11260,6 +11525,25 @@ router@^1.3.1: setprototypeof "1.2.0" utils-merge "1.0.1" +routing-controllers@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/routing-controllers/-/routing-controllers-0.9.0.tgz#979016523db37832d4c9a23c33b2654a89a563de" + integrity sha512-OtARLKA6j8enNgGqi/hoRqBsTjVo2hbxc1+MeKi8mvelNn18+LXUdHpzY3z4GbCERBtaj8CwVjcsiQR+2w6ZFg== + dependencies: + cookie "^0.4.0" + express-session "^1.17.1" + glob "^7.1.4" + reflect-metadata "^0.1.13" + template-url "^1.0.0" + optionalDependencies: + body-parser "^1.19.0" + express "^4.17.1" + koa "^2.8.2" + koa-bodyparser "^4.2.1" + koa-multer "^1.0.2" + koa-router "^7.4.0" + multer "^1.4.2" + rsvp@^4.8.4, rsvp@^4.8.5: version "4.8.5" resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" @@ -11287,6 +11571,11 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.0.tgz#b74daec49b1148f88c64b68d49b1e815c1f2f519" + integrity sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg== + safe-buffer@>=5.1.0, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -11580,7 +11869,7 @@ shellwords@^0.1.1: resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== -side-channel@^1.0.2, side-channel@^1.0.3: +side-channel@^1.0.2, side-channel@^1.0.3, side-channel@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== @@ -11881,7 +12170,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: +"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.5.0, statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -11896,6 +12185,11 @@ stream-shift@^1.0.0: resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== +streamsearch@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" + integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= + string-argv@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da" @@ -12261,6 +12555,11 @@ tcp-port-used@^1.0.1: debug "4.3.1" is2 "^2.0.6" +template-url@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/template-url/-/template-url-1.0.0.tgz#d9456bee70cac6617b462a7b08db29fb813a0b09" + integrity sha1-2UVr7nDKxmF7Rip7CNsp+4E6Cwk= + term-size@^2.1.0: version "2.2.1" resolved "https://registry.yarnpkg.com/term-size/-/term-size-2.2.1.tgz#2a6a54840432c2fb6320fea0f415531e90189f54" @@ -12518,6 +12817,11 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== +tsscmp@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" + integrity sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA== + tsutils@^3.17.1: version "3.19.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.19.1.tgz#d8566e0c51c82f32f9c25a4d367cd62409a547a9" @@ -12584,7 +12888,7 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.17, type-is@~1.6.18: +type-is@^1.6.16, type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -12609,6 +12913,11 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= + typedi@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/typedi/-/typedi-0.10.0.tgz#e8f9a5ee100b84addbdfb57fe90d8d9ed2a21dea" @@ -12619,6 +12928,13 @@ typescript@^4.1.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== +uid-safe@~2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/uid-safe/-/uid-safe-2.1.5.tgz#2b3d5c7240e8fc2e58f8aa269e5ee49c0857bd3a" + integrity sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA== + dependencies: + random-bytes "~1.0.0" + undefsafe@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.3.tgz#6b166e7094ad46313b2202da7ecc2cd7cc6e7aae" @@ -12760,6 +13076,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +urijs@^1.19.0: + version "1.19.6" + resolved "https://registry.yarnpkg.com/urijs/-/urijs-1.19.6.tgz#51f8cb17ca16faefb20b9a31ac60f84aa2b7c870" + integrity sha512-eSXsXZ2jLvGWeLYlQA3Gh36BcjF+0amo92+wHPyN1mdR8Nxf75fuEuYTd9c0a+m/vhCjRK0ESlE9YNLW+E1VEw== + urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" @@ -12878,7 +13199,7 @@ value-equal@^1.0.1: resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== -vary@^1, vary@~1.1.2: +vary@^1, vary@^1.1.2, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= @@ -13366,6 +13687,11 @@ yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" +ylru@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ylru/-/ylru-1.2.1.tgz#f576b63341547989c1de7ba288760923b27fe84f" + integrity sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 9bc41c35396a7e0d786fa63376d9d63b8eed8181 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 4 Apr 2021 22:46:20 +0200 Subject: [PATCH 07/28] added github service --- api/src/app/services/github/index.ts | 2 +- api/src/github/service.ts | 32 ++++++++++++++++++++++ api/src/{app/services => }/github/types.ts | 6 ++++ 3 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 api/src/github/service.ts rename api/src/{app/services => }/github/types.ts (63%) diff --git a/api/src/app/services/github/index.ts b/api/src/app/services/github/index.ts index 8ff322d84..2546024b3 100644 --- a/api/src/app/services/github/index.ts +++ b/api/src/app/services/github/index.ts @@ -1,4 +1,4 @@ -import { ListContributorsResponse } from "./types"; +import { ListContributorsResponse } from "../../../github/types"; import axios from "axios"; export const listOrganizationRepositories = async ({ diff --git a/api/src/github/service.ts b/api/src/github/service.ts new file mode 100644 index 000000000..63d493776 --- /dev/null +++ b/api/src/github/service.ts @@ -0,0 +1,32 @@ +import { GeneralGithubQuery, ListContributorsResponse } from "./types"; + +import { Service } from "typedi"; +import axios from "axios"; + +@Service() +export class GithubService { + public listContributors = async ({ + owner, + repo, + path, + }: GeneralGithubQuery) => { + const response = await axios.get( + `${this.apiURL}/repos/${owner}/${repo}/commits?path=${path}`, + // eslint-disable-next-line camelcase + { params: { state: "all", per_page: 10 } }, + ); + const contributors = response.data.map( + // eslint-disable-next-line camelcase + ({ committer: { login, avatar_url, html_url, type, id } }) => ({ + id, + login, + avatar_url, // eslint-disable-line camelcase + html_url, // eslint-disable-line camelcase + type, + }), + ); + return contributors; + }; + + private apiURL = "https://api.github.com"; +} diff --git a/api/src/app/services/github/types.ts b/api/src/github/types.ts similarity index 63% rename from api/src/app/services/github/types.ts rename to api/src/github/types.ts index 249dfc573..864d5deb0 100644 --- a/api/src/app/services/github/types.ts +++ b/api/src/github/types.ts @@ -4,3 +4,9 @@ export type ListContributorsResponse = Array<{ author: GithubUser; committer: GithubUser; }>; + +export interface GeneralGithubQuery { + owner: string; + repo: string; + path: string; +} From 55163a820eaeed77d4b5ccef6536588872a69161 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 4 Apr 2021 23:04:33 +0200 Subject: [PATCH 08/28] used Github service in Contributors controller --- api/src/contributor/controller.ts | 32 ++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/api/src/contributor/controller.ts b/api/src/contributor/controller.ts index e2d7d4c44..9724e8aeb 100644 --- a/api/src/contributor/controller.ts +++ b/api/src/contributor/controller.ts @@ -2,6 +2,7 @@ import { Controller, Get, QueryParam } from "routing-controllers"; import { GetContributorsResponse } from "./type"; import { GithubService } from "../github/service"; +import { GithubUser } from "@dzcode.io/common/dist/types"; import { Service } from "typedi"; @Service() @@ -13,8 +14,37 @@ export class ContributorController { public async getContributor( @QueryParam("path") path: string, ): Promise { + const responses = await Promise.all([ + // current place for data: + this.githubService.listContributors({ + owner: "dzcode-io", + repo: "dzcode.io", + path: `data/models/${path}`, + }), + // also check old place for data, to not lose contribution effort: + this.githubService.listContributors({ + owner: "dzcode-io", + repo: "dzcode.io", + path: `data/${path}`, + }), + ]); + + // filter and sort contributors: + const uniqUsernames: Record = {}; + const contributors = [...(responses[0] || []), ...(responses[1] || [])] + .reduce((pV, cV) => { + if (uniqUsernames[cV.login]) { + uniqUsernames[cV.login]++; + return pV; + } else { + uniqUsernames[cV.login] = 1; + return [...pV, cV]; + } + }, []) + .sort((a, b) => uniqUsernames[b.login] - uniqUsernames[a.login]); + return { - contributors: [], + contributors, }; } } From 9d3038a711610ef54bccfd689d163ce09a81f160 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Sun, 4 Apr 2021 23:08:08 +0200 Subject: [PATCH 09/28] loaded old endpoints & put new ones under /v2 --- api/src/app/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/api/src/app/index.ts b/api/src/app/index.ts index 4f9a152de..bfde946b0 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -6,6 +6,7 @@ import { Application } from "express"; import { ConfigService } from "../config/service"; import Container from "typedi"; import { ContributorController } from "../contributor/controller"; +import router from "./routes/api"; // Use typedi container useContainer(Container); @@ -13,8 +14,12 @@ useContainer(Container); // Create the app const app: Application = createExpressServer({ controllers: [ContributorController], + routePrefix: "v2", }); +// Load old code, temporarily until we migrate all endpoints +app.use("/", router); + const port = Container.get(ConfigService).env().PORT; // Start it From 882bb777fae16280cf65a2a94863319752566dd8 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Mon, 5 Apr 2021 17:29:31 +0200 Subject: [PATCH 10/28] added error handeler and refactored middlewares --- api/src/app/index.ts | 11 ++++++++ api/src/app/loaders/index.ts | 8 ++---- api/src/app/loaders/logger.ts | 21 --------------- api/src/app/loaders/security.ts | 33 ------------------------ api/src/app/middlewares/error.ts | 34 ++++++++++++++++++++++++ api/src/app/middlewares/logger.ts | 23 +++++++++++++++++ api/src/app/middlewares/security.ts | 40 +++++++++++++++++++++++++++++ api/src/app/routes/index.ts | 3 +++ 8 files changed, 113 insertions(+), 60 deletions(-) delete mode 100644 api/src/app/loaders/logger.ts delete mode 100644 api/src/app/loaders/security.ts create mode 100644 api/src/app/middlewares/error.ts create mode 100644 api/src/app/middlewares/logger.ts create mode 100644 api/src/app/middlewares/security.ts diff --git a/api/src/app/index.ts b/api/src/app/index.ts index bfde946b0..cb8696079 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -6,6 +6,9 @@ import { Application } from "express"; import { ConfigService } from "../config/service"; import Container from "typedi"; import { ContributorController } from "../contributor/controller"; +import { ErrorMiddleware } from "./middlewares/error"; +import { LoggerMiddleware } from "./middlewares/logger"; +import { SecurityMiddleware } from "./middlewares/security"; import router from "./routes/api"; // Use typedi container @@ -14,7 +17,15 @@ useContainer(Container); // Create the app const app: Application = createExpressServer({ controllers: [ContributorController], + middlewares: [ + // middlewares: + SecurityMiddleware, + ErrorMiddleware, + LoggerMiddleware, + ], routePrefix: "v2", + defaultErrorHandler: false, + cors: Container.get(SecurityMiddleware).cors(), }); // Load old code, temporarily until we migrate all endpoints diff --git a/api/src/app/loaders/index.ts b/api/src/app/loaders/index.ts index d2b32fc1e..b100a09ac 100644 --- a/api/src/app/loaders/index.ts +++ b/api/src/app/loaders/index.ts @@ -1,12 +1,8 @@ -import { Application } from "express"; -import { loggerLoader } from "./logger"; +import { Router } from "express"; import { parserLoader } from "./parser"; -import { securityLoader } from "./security"; export const rootLoader: Loader = (params) => { - loggerLoader(params); - securityLoader(params); parserLoader(params); }; -export type Loader = (params: { app: Application }) => void; +export type Loader = (params: { app: Router }) => void; diff --git a/api/src/app/loaders/logger.ts b/api/src/app/loaders/logger.ts deleted file mode 100644 index 5bbe2a5bd..000000000 --- a/api/src/app/loaders/logger.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Loader } from "."; -import expressWinston from "express-winston"; -import winston from "winston"; - -export const loggerLoader: Loader = ({ app }) => { - app.use( - expressWinston.logger({ - transports: [new winston.transports.Console()], - format: winston.format.combine( - winston.format.colorize(), - winston.format.json(), - ), - meta: true, // optional: control whether you want to log the meta data about the request (default to true) - msg: "HTTP {{req.method}} {{req.url}}", // optional: customize the default logging message. E.g. "{{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}" - expressFormat: true, // Use the default Express/morgan request formatting. Enabling this will override any msg if true. Will only output colors with colorize set to true - ignoreRoute: () => { - return false; - }, // optional: allows to skip some log messages based on request and/or response - }), - ); -}; diff --git a/api/src/app/loaders/security.ts b/api/src/app/loaders/security.ts deleted file mode 100644 index ac2a94adf..000000000 --- a/api/src/app/loaders/security.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { ConfigService } from "../../config/service"; -import { Container } from "typedi"; -import { Loader } from "."; -import cors from "cors"; -import helmet from "helmet"; -import rateLimit from "express-rate-limit"; - -const env = Container.get(ConfigService).env().ENV; - -export const securityLoader: Loader = ({ app }) => { - app.use( - cors({ - allowedHeaders: - env === "development" - ? ["http://localhost:8080"] - : env === "staging" - ? ["https://stage.dzcode.io"] - : env === "production" - ? ["https://www.dzcode.io"] - : [], - origin: true, - }), - ); - - app.use(helmet()); - - app.use( - rateLimit({ - windowMs: 15 * 60 * 1000, // 15 minutes - max: 100, // limit each IP to 100 requests per windowMs - }), - ); -}; diff --git a/api/src/app/middlewares/error.ts b/api/src/app/middlewares/error.ts new file mode 100644 index 000000000..6fc460aac --- /dev/null +++ b/api/src/app/middlewares/error.ts @@ -0,0 +1,34 @@ +import { + ExpressErrorMiddlewareInterface, + Middleware, +} from "routing-controllers"; + +import { ErrorRequestHandler } from "express"; +import { GeneralResponse } from "../types"; +import { Service } from "typedi"; + +@Service() +@Middleware({ type: "after" }) +export class ErrorMiddleware implements ExpressErrorMiddlewareInterface { + error: ErrorRequestHandler = ( + err, + req, + res, + next, + ) => { + // Logs error + console.log("🚩 Internal Server Error"); + console.log(err); + + // Skip if headers are already sent + if (res.headersSent) { + return next(err); + } + + // return a general error response + res.status(500).json({ + code: 500, + msg: err.message, + }); + }; +} diff --git a/api/src/app/middlewares/logger.ts b/api/src/app/middlewares/logger.ts new file mode 100644 index 000000000..93115dddf --- /dev/null +++ b/api/src/app/middlewares/logger.ts @@ -0,0 +1,23 @@ +import { ExpressMiddlewareInterface, Middleware } from "routing-controllers"; + +import { Service } from "typedi"; +import expressWinston from "express-winston"; +import winston from "winston"; + +@Service() +@Middleware({ type: "before" }) +export class LoggerMiddleware implements ExpressMiddlewareInterface { + use = expressWinston.logger({ + transports: [new winston.transports.Console()], + format: winston.format.combine( + winston.format.colorize(), + winston.format.json(), + ), + meta: true, // optional: control whether you want to log the meta data about the request (default to true) + msg: "HTTP {{req.method}} {{req.url}}", // optional: customize the default logging message. E.g. "{{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}" + expressFormat: true, // Use the default Express/morgan request formatting. Enabling this will override any msg if true. Will only output colors with colorize set to true + ignoreRoute: () => { + return false; + }, // optional: allows to skip some log messages based on request and/or response + }); +} diff --git a/api/src/app/middlewares/security.ts b/api/src/app/middlewares/security.ts new file mode 100644 index 000000000..dcb257b96 --- /dev/null +++ b/api/src/app/middlewares/security.ts @@ -0,0 +1,40 @@ +import { ExpressMiddlewareInterface, Middleware } from "routing-controllers"; +import { RequestHandler, Router } from "express"; + +import { ConfigService } from "../../config/service"; +import { Service } from "typedi"; +import helmet from "helmet"; +import rateLimit from "express-rate-limit"; + +@Service() +@Middleware({ type: "before" }) +export class SecurityMiddleware implements ExpressMiddlewareInterface { + constructor(private configService: ConfigService) { + this.router.use(helmet()); + + this.router.use( + rateLimit({ + windowMs: 15 * 60 * 1000, // 15 minutes + max: 100, // limit each IP to 100 requests per windowMs + }), + ); + } + + private router = Router(); + + use: RequestHandler = this.router; + + public cors = () => { + const env = this.configService.env().ENV; + return { + origin: + env === "development" + ? ["http://localhost:8080"] + : env === "staging" + ? ["https://stage.dzcode.io"] + : env === "production" + ? ["https://www.dzcode.io"] + : true, + }; + }; +} diff --git a/api/src/app/routes/index.ts b/api/src/app/routes/index.ts index a1d16b04d..4dc8b9425 100644 --- a/api/src/app/routes/index.ts +++ b/api/src/app/routes/index.ts @@ -1,9 +1,12 @@ import { Request, Response, Router } from "express"; import api from "./api"; +import { rootLoader } from "../loaders"; const router: Router = Router(); +rootLoader({ app: router }); + // API routes router.use("/", api); From 9fbcff49930a741bb2fa6fe1b5c40d64a9832bce Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Mon, 5 Apr 2021 17:33:49 +0200 Subject: [PATCH 11/28] renamed vultr to oracle-cloud --- api/.eslintignore | 2 +- api/.gitignore | 2 +- api/.prettierignore | 2 +- api/{vultr => oracle-cloud}/Dockerfile | 0 api/{vultr => oracle-cloud}/deploy.js | 24 ++++++++++++------- .../docker-compose.yml | 0 api/package.json | 4 ++-- 7 files changed, 21 insertions(+), 13 deletions(-) rename api/{vultr => oracle-cloud}/Dockerfile (100%) rename api/{vultr => oracle-cloud}/deploy.js (72%) rename api/{vultr => oracle-cloud}/docker-compose.yml (100%) diff --git a/api/.eslintignore b/api/.eslintignore index 8745b4230..3bf297cd4 100644 --- a/api/.eslintignore +++ b/api/.eslintignore @@ -1,7 +1,7 @@ node_modules/ coverage dist -vultr/build +oracle-cloud/build *.* !*.tsx !*.ts diff --git a/api/.gitignore b/api/.gitignore index 7d2b3a678..b7e755384 100644 --- a/api/.gitignore +++ b/api/.gitignore @@ -1,6 +1,6 @@ dist coverage -vultr/build +oracle-cloud/build # logs **.tsbuildinfo diff --git a/api/.prettierignore b/api/.prettierignore index 712dde4fa..6c494aae7 100644 --- a/api/.prettierignore +++ b/api/.prettierignore @@ -1,7 +1,7 @@ node_modules/ coverage dist -vultr/build +oracle-cloud/build .* *.tsbuildinfo diff --git a/api/vultr/Dockerfile b/api/oracle-cloud/Dockerfile similarity index 100% rename from api/vultr/Dockerfile rename to api/oracle-cloud/Dockerfile diff --git a/api/vultr/deploy.js b/api/oracle-cloud/deploy.js similarity index 72% rename from api/vultr/deploy.js rename to api/oracle-cloud/deploy.js index 4c9577675..3ab6f94d6 100644 --- a/api/vultr/deploy.js +++ b/api/oracle-cloud/deploy.js @@ -5,13 +5,19 @@ const cp = require("child_process"); // Coping files console.log("⚙️ Preparing files ..."); -fse.copySync("../package.json", "./vultr/build/package.json"); -fse.copySync("../common/package.json", "./vultr/build/common/package.json"); -fse.copySync("../common/dist", "./vultr/build/common/dist"); -fse.copySync("./package.json", "./vultr/build/api/package.json"); -fse.copySync("./dist", "./vultr/build/api/dist"); -fse.copySync("./vultr/docker-compose.yml", "./vultr/build/docker-compose.yml"); -fse.copySync("./vultr/Dockerfile", "./vultr/build/Dockerfile"); +fse.copySync("../package.json", "./oracle-cloud/build/package.json"); +fse.copySync( + "../common/package.json", + "./oracle-cloud/build/common/package.json", +); +fse.copySync("../common/dist", "./oracle-cloud/build/common/dist"); +fse.copySync("./package.json", "./oracle-cloud/build/api/package.json"); +fse.copySync("./dist", "./oracle-cloud/build/api/dist"); +fse.copySync( + "./oracle-cloud/docker-compose.yml", + "./oracle-cloud/build/docker-compose.yml", +); +fse.copySync("./oracle-cloud/Dockerfile", "./oracle-cloud/build/Dockerfile"); console.log("✅ files copied\n"); // Deploying with ssh @@ -48,7 +54,9 @@ logs = cp.execSync(sshPrefix + '"rm -f -r ' + appPath + '"'); logs = cp.execSync(sshPrefix + '"mkdir ' + appPath + '"'); console.log("⤴️ Uploading new code ..."); -logs = cp.execSync("rsync -r vultr/build/* " + sshServer + ":" + appPath); +logs = cp.execSync( + "rsync -r oracle-cloud/build/* " + sshServer + ":" + appPath, +); console.log("✅ New code uploaded."); console.log("\n⚙️ Starting up the app"); diff --git a/api/vultr/docker-compose.yml b/api/oracle-cloud/docker-compose.yml similarity index 100% rename from api/vultr/docker-compose.yml rename to api/oracle-cloud/docker-compose.yml diff --git a/api/package.json b/api/package.json index ec58ecb8a..290486ab4 100644 --- a/api/package.json +++ b/api/package.json @@ -17,8 +17,8 @@ "build:watch": "tsc --watch", "dev": "nodemon dist/app/index.js", "start": "node dist/app/index.js", - "deploy": "yarn build && rimraf ./vultr/build && node vultr/deploy.js production", - "deploy:stg": "yarn build && rimraf ./vultr/build && node vultr/deploy.js staging" + "deploy": "yarn build && rimraf ./oracle-cloud/build && node oracle-cloud/deploy.js production", + "deploy:stg": "yarn build && rimraf ./oracle-cloud/build && node oracle-cloud/deploy.js staging" }, "repository": { "type": "git", From e05704360747c9daff183fe541bd1daed5fcb1f3 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Mon, 5 Apr 2021 18:50:06 +0200 Subject: [PATCH 12/28] added 100% unit test coverage for GithubService --- api/jest.config.js | 23 ++++++++++++++++ api/src/github/service.spec.ts | 50 ++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 api/jest.config.js create mode 100644 api/src/github/service.spec.ts diff --git a/api/jest.config.js b/api/jest.config.js new file mode 100644 index 000000000..0d823a9a1 --- /dev/null +++ b/api/jest.config.js @@ -0,0 +1,23 @@ +module.exports = { + testEnvironment: "node", + collectCoverageFrom: ["src/**/*.ts"], + // coverageThreshold: { + // global: { + // branches: 80, + // functions: 80, + // lines: 80, + // statements: 80, + // }, + // }, + transform: { + "^.+\\.(ts)?$": "ts-jest", + }, + coveragePathIgnorePatterns: [ + "node_modules", + "dist", + // temporarily until we migrate all endpoints to v2 + "src/app/controllers", + "src/app/routes", + "src/app/services", + ], +}; diff --git a/api/src/github/service.spec.ts b/api/src/github/service.spec.ts new file mode 100644 index 000000000..bbda85f80 --- /dev/null +++ b/api/src/github/service.spec.ts @@ -0,0 +1,50 @@ +import { GeneralGithubQuery, ListContributorsResponse } from "./types"; + +import Axios from "axios"; +import { GithubService } from "./service"; +import { GithubUser } from "@dzcode.io/common/dist/types"; + +jest.mock("axios"); +const mockedAxios = Axios as jest.Mocked; + +describe("GithubService", () => { + const githubQuery: GeneralGithubQuery = { + owner: "test-owner", + repo: "test-repo", + path: "test/path", + }; + const githubUserMock: GithubUser = { + avatar_url: "avatar_url", // eslint-disable-line camelcase + html_url: "html_url", // eslint-disable-line camelcase + id: "id", + login: "login", + type: "type", + }; + const contributorsMock: ListContributorsResponse = [ + { + author: githubUserMock, + committer: githubUserMock, + }, + ]; + + it("should throw error when api call fails", async () => { + mockedAxios.get.mockRejectedValue({}); + const githubService = new GithubService(); + let errorThrown = false; + try { + await githubService.listContributors(githubQuery); + } catch (error) { + errorThrown = true; + } + expect(errorThrown).toBe(true); + expect(mockedAxios.get).toBeCalled(); + }); + + it("should return list of contributors when api call succeed", async () => { + mockedAxios.get.mockResolvedValue({ data: contributorsMock }); + const githubService = new GithubService(); + const contributors = await githubService.listContributors(githubQuery); + + expect(contributors).toMatchObject([githubUserMock]); + }); +}); From af635d070dcf685019eb916b7b793cbbbde5cd1f Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Mon, 5 Apr 2021 20:13:14 +0200 Subject: [PATCH 13/28] 100% unit test coverage for ContributorController --- api/package.json | 1 + api/src/contributor/controller.spec.ts | 52 ++++++++++++++++++++++++++ api/src/contributor/controller.ts | 2 +- api/src/github/service.spec.ts | 16 ++------ api/test/mocks.ts | 25 +++++++++++++ yarn.lock | 12 ++++++ 6 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 api/src/contributor/controller.spec.ts create mode 100644 api/test/mocks.ts diff --git a/api/package.json b/api/package.json index 290486ab4..0afd27259 100644 --- a/api/package.json +++ b/api/package.json @@ -63,6 +63,7 @@ "@types/node": "^14.14.16", "eslint-config-prettier": "^7.1.0", "fs-extra": "^9.0.1", + "jest-mock-extended": "^1.0.13", "lint-staged": "^10.5.3", "nodemon": "^2.0.6", "typescript": "^4.1.3" diff --git a/api/src/contributor/controller.spec.ts b/api/src/contributor/controller.spec.ts new file mode 100644 index 000000000..45126b251 --- /dev/null +++ b/api/src/contributor/controller.spec.ts @@ -0,0 +1,52 @@ +import { + githubUserMock, + githubUserMock2, + githubUserMock3, +} from "../../test/mocks"; + +import { ContributorController } from "./controller"; +import { GetContributorsResponse } from "./type"; +import { GithubService } from "../github/service"; +import { mock } from "jest-mock-extended"; + +describe("ContributorController", () => { + const mockedGithubServiceInstance = mock(); + const contributorsMock = [githubUserMock2, githubUserMock, githubUserMock3]; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it("should throw error when GithubService fails", async () => { + mockedGithubServiceInstance.listContributors.mockRejectedValue({ + meg: "service down", + }); + + const contributorController = new ContributorController( + mockedGithubServiceInstance, + ); + let errorThrown = false; + try { + await contributorController.getContributor("/"); + } catch (error) { + errorThrown = error; + } + expect(errorThrown).toMatchObject({ meg: "service down" }); + }); + + it("should return list of contributors when GithubService succeed", async () => { + mockedGithubServiceInstance.listContributors.mockResolvedValue( + contributorsMock, + ); + + const contributorController = new ContributorController( + mockedGithubServiceInstance, + ); + + const contributors = await contributorController.getContributor("/"); + + expect(contributors).toMatchObject({ + contributors: contributorsMock, + }); + }); +}); diff --git a/api/src/contributor/controller.ts b/api/src/contributor/controller.ts index 9724e8aeb..cec04f532 100644 --- a/api/src/contributor/controller.ts +++ b/api/src/contributor/controller.ts @@ -31,7 +31,7 @@ export class ContributorController { // filter and sort contributors: const uniqUsernames: Record = {}; - const contributors = [...(responses[0] || []), ...(responses[1] || [])] + const contributors = [...responses[0], ...responses[1]] .reduce((pV, cV) => { if (uniqUsernames[cV.login]) { uniqUsernames[cV.login]++; diff --git a/api/src/github/service.spec.ts b/api/src/github/service.spec.ts index bbda85f80..979146435 100644 --- a/api/src/github/service.spec.ts +++ b/api/src/github/service.spec.ts @@ -2,7 +2,7 @@ import { GeneralGithubQuery, ListContributorsResponse } from "./types"; import Axios from "axios"; import { GithubService } from "./service"; -import { GithubUser } from "@dzcode.io/common/dist/types"; +import { githubUserMock } from "../../test/mocks"; jest.mock("axios"); const mockedAxios = Axios as jest.Mocked; @@ -13,13 +13,6 @@ describe("GithubService", () => { repo: "test-repo", path: "test/path", }; - const githubUserMock: GithubUser = { - avatar_url: "avatar_url", // eslint-disable-line camelcase - html_url: "html_url", // eslint-disable-line camelcase - id: "id", - login: "login", - type: "type", - }; const contributorsMock: ListContributorsResponse = [ { author: githubUserMock, @@ -28,16 +21,15 @@ describe("GithubService", () => { ]; it("should throw error when api call fails", async () => { - mockedAxios.get.mockRejectedValue({}); + mockedAxios.get.mockRejectedValue({ meg: "service down" }); const githubService = new GithubService(); let errorThrown = false; try { await githubService.listContributors(githubQuery); } catch (error) { - errorThrown = true; + errorThrown = error; } - expect(errorThrown).toBe(true); - expect(mockedAxios.get).toBeCalled(); + expect(errorThrown).toMatchObject({ meg: "service down" }); }); it("should return list of contributors when api call succeed", async () => { diff --git a/api/test/mocks.ts b/api/test/mocks.ts new file mode 100644 index 000000000..e313dbf59 --- /dev/null +++ b/api/test/mocks.ts @@ -0,0 +1,25 @@ +import { GithubUser } from "@dzcode.io/common/dist/types"; + +export const githubUserMock: GithubUser = { + avatar_url: "avatar_url", // eslint-disable-line camelcase + html_url: "html_url", // eslint-disable-line camelcase + id: "id", + login: "login", + type: "type", +}; + +export const githubUserMock2: GithubUser = { + avatar_url: "avatar_url2", // eslint-disable-line camelcase + html_url: "html_url2", // eslint-disable-line camelcase + id: "id2", + login: "login2", + type: "type2", +}; + +export const githubUserMock3: GithubUser = { + avatar_url: "avatar_url3", // eslint-disable-line camelcase + html_url: "html_url3", // eslint-disable-line camelcase + id: "id3", + login: "login3", + type: "type3", +}; diff --git a/yarn.lock b/yarn.lock index f1a9f2018..7c0cb038c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7529,6 +7529,13 @@ jest-message-util@^26.6.2: slash "^3.0.0" stack-utils "^2.0.2" +jest-mock-extended@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/jest-mock-extended/-/jest-mock-extended-1.0.13.tgz#07d7b58ea45a4bad8d95ff8ae30ca8f6171c9289" + integrity sha512-fs621RgUK9Z6frJWDA75Gb9Vpm1d9HbpuAWjIu12hZTNls+VVdlJl/43xrqz70CYUbJPnfkiTfLwue9mkKC1ww== + dependencies: + ts-essentials "^4.0.0" + jest-mock@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" @@ -12790,6 +12797,11 @@ triple-beam@^1.2.0, triple-beam@^1.3.0: resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== +ts-essentials@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-4.0.0.tgz#506c42b270bbd0465574b90416533175b09205ab" + integrity sha512-uQJX+SRY9mtbKU+g9kl5Fi7AEMofPCvHfJkQlaygpPmHPZrtgaBqbWFOYyiA47RhnSwwnXdepUJrgqUYxoUyhQ== + ts-jest@^26.4.4: version "26.4.4" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.4.4.tgz#61f13fb21ab400853c532270e52cc0ed7e502c49" From 9f29ff7805602252c04c3467100589f5f6396479 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Mon, 5 Apr 2021 20:21:48 +0200 Subject: [PATCH 14/28] ignoring test files --- api/tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/tsconfig.json b/api/tsconfig.json index d05370bd5..08a84ab52 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -17,5 +17,6 @@ "outDir": "dist", "rootDir": "src" }, - "include": ["src"] + "include": ["src"], + "exclude": ["**/*.spec.ts"] } From debbe8a9905d3a66f14ea4321784051d93599640 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Mon, 5 Apr 2021 21:30:48 +0200 Subject: [PATCH 15/28] ignored covering some dirs & fixed build issue --- api/jest.config.js | 2 ++ api/tsconfig.json | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/api/jest.config.js b/api/jest.config.js index 0d823a9a1..2989c25d7 100644 --- a/api/jest.config.js +++ b/api/jest.config.js @@ -15,9 +15,11 @@ module.exports = { coveragePathIgnorePatterns: [ "node_modules", "dist", + "src/app/index.ts", // temporarily until we migrate all endpoints to v2 "src/app/controllers", "src/app/routes", "src/app/services", + "src/app/loaders", ], }; diff --git a/api/tsconfig.json b/api/tsconfig.json index 08a84ab52..9ef959b4d 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -14,9 +14,8 @@ "target": "es2017", "sourceMap": true, "declaration": true, - "outDir": "dist", - "rootDir": "src" + "outDir": "dist" }, - "include": ["src"], + "include": ["src/**/*"], "exclude": ["**/*.spec.ts"] } From 6f94fdbaf9f8c94d36c2b83ad192789e0cf056a3 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Wed, 7 Apr 2021 19:34:47 +0200 Subject: [PATCH 16/28] 100% unit test coverage for ConfigService --- api/src/config/service.spec.ts | 23 +++++++++++++++++++++++ api/src/config/service.ts | 2 -- 2 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 api/src/config/service.spec.ts diff --git a/api/src/config/service.spec.ts b/api/src/config/service.spec.ts new file mode 100644 index 000000000..7eca7c79d --- /dev/null +++ b/api/src/config/service.spec.ts @@ -0,0 +1,23 @@ +import { ConfigService } from "./service"; +import dotenv from "dotenv"; + +jest.mock("dotenv"); +const mockedDotenv = dotenv as jest.Mocked; + +describe("ConfigService", () => { + it("should throw error when .env has invalid key value pair", async () => { + mockedDotenv.config.mockReturnValue({ parsed: { ENV: "testing" } }); + + expect(() => new ConfigService()).toThrowError( + `⚠️ Errors in .env file in the following keys:\nENV : {\"matches\":\"ENV must match (development)|(staging)|(production) regular expression\"}`, + ); + }); + + it("should return default envs when .env is empty or doesn't exists", async () => { + mockedDotenv.config.mockReturnValue({ error: new Error("test-error") }); + + const configService = new ConfigService(); + expect(configService).toBeInstanceOf(ConfigService); + expect(configService.env()).toMatchObject({ ENV: "development" }); + }); +}); diff --git a/api/src/config/service.ts b/api/src/config/service.ts index 20a4ea093..3d226640a 100644 --- a/api/src/config/service.ts +++ b/api/src/config/service.ts @@ -15,8 +15,6 @@ export class ConfigService { public env = () => _env; private generateConfig = () => { - if (_env) return; - const _config = config(); const output = plainToClass(ENV, { ...process.env, From f6d20a893f4e7c7d31d01032d2c8d89d97c7f1f0 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Wed, 7 Apr 2021 20:31:02 +0200 Subject: [PATCH 17/28] added swagger UI docs --- api/package.json | 10 +++-- api/src/app/index.ts | 19 ++++++--- api/src/app/middlewares/docs.ts | 36 +++++++++++++++++ yarn.lock | 69 +++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 api/src/app/middlewares/docs.ts diff --git a/api/package.json b/api/package.json index 0afd27259..6c10bfeea 100644 --- a/api/package.json +++ b/api/package.json @@ -1,6 +1,6 @@ { "name": "@dzcode.io/api", - "version": "1.0.0", + "version": "2.0.0", "description": "dzCode.io api code", "scripts": { "lint:check": "yarn prettier:check && yarn eslint:check", @@ -10,9 +10,9 @@ "prettier:check": "prettier \"**/*.*\" --check --ignore-path ./.prettierignore", "prettier:fix": "prettier \"**/*.*\" --write --ignore-path ./.prettierignore", "test": "jest src", - "test:watch": "jest src --watchAll", + "test:watch": "jest --watchAll", "test:cov": "jest src --coverage", - "test:cov:watch": "jest src --coverage --watchAll", + "test:cov:watch": "jest --coverage --watchAll", "build": "tsc", "build:watch": "tsc --watch", "dev": "nodemon dist/app/index.js", @@ -41,6 +41,7 @@ "axios": "^0.21.1", "class-transformer": "^0.4.0", "class-validator": "^0.13.1", + "class-validator-jsonschema": "^3.0.1", "cors": "^2.8.5", "dotenv": "^8.2.0", "express": "^4.17.1", @@ -50,6 +51,8 @@ "morgan": "^1.10.0", "reflect-metadata": "^0.1.13", "routing-controllers": "^0.9.0", + "routing-controllers-openapi": "^3.0.0", + "swagger-ui-express": "^4.1.6", "typedi": "^0.10.0", "winston": "^3.3.3" }, @@ -61,6 +64,7 @@ "@types/express-rate-limit": "^5.1.1", "@types/morgan": "^1.9.2", "@types/node": "^14.14.16", + "@types/swagger-ui-express": "^4.1.2", "eslint-config-prettier": "^7.1.0", "fs-extra": "^9.0.1", "jest-mock-extended": "^1.0.13", diff --git a/api/src/app/index.ts b/api/src/app/index.ts index cb8696079..4dbc9e468 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -6,6 +6,7 @@ import { Application } from "express"; import { ConfigService } from "../config/service"; import Container from "typedi"; import { ContributorController } from "../contributor/controller"; +import { DocsMiddleware } from "./middlewares/docs"; import { ErrorMiddleware } from "./middlewares/error"; import { LoggerMiddleware } from "./middlewares/logger"; import { SecurityMiddleware } from "./middlewares/security"; @@ -14,21 +15,26 @@ import router from "./routes/api"; // Use typedi container useContainer(Container); -// Create the app -const app: Application = createExpressServer({ +// Env var +const env = Container.get(ConfigService).env().ENV; + +// Create the app: +export const routingControllersOptions = { controllers: [ContributorController], middlewares: [ // middlewares: SecurityMiddleware, ErrorMiddleware, LoggerMiddleware, + DocsMiddleware, ], - routePrefix: "v2", + routePrefix: "/v2", defaultErrorHandler: false, cors: Container.get(SecurityMiddleware).cors(), -}); +}; +const app: Application = createExpressServer(routingControllersOptions); -// Load old code, temporarily until we migrate all endpoints +// Load old code to the app, temporarily until we migrate all endpoints app.use("/", router); const port = Container.get(ConfigService).env().PORT; @@ -36,4 +42,7 @@ const port = Container.get(ConfigService).env().PORT; // Start it app.listen(port, () => { console.log("Server listening on port: " + port); + if (env === "development") { + console.log(`API Docs: http://localhost:${port}/v2`); + } }); diff --git a/api/src/app/middlewares/docs.ts b/api/src/app/middlewares/docs.ts new file mode 100644 index 000000000..7609956aa --- /dev/null +++ b/api/src/app/middlewares/docs.ts @@ -0,0 +1,36 @@ +import { ExpressMiddlewareInterface, Middleware } from "routing-controllers"; +import { RequestHandler, Router } from "express"; +import { serve, setup } from "swagger-ui-express"; + +import { Service } from "typedi"; +import { getMetadataArgsStorage } from "routing-controllers"; +import { routingControllersOptions } from ".."; +import { routingControllersToSpec } from "routing-controllers-openapi"; +import { validationMetadatasToSchemas } from "class-validator-jsonschema"; + +@Service() +@Middleware({ type: "after" }) +export class DocsMiddleware implements ExpressMiddlewareInterface { + constructor() { + // Parse class-validator classes into JSON Schema: + const schemas = validationMetadatasToSchemas({ + refPointerPrefix: "#/components/schemas/", + }); + // Parse routing-controllers classes into OpenAPI spec: + const storage = getMetadataArgsStorage(); + const spec = routingControllersToSpec(storage, routingControllersOptions, { + components: { schemas }, + info: { + description: "swagger documentation for version 2 of dzcode.io API", + title: "dzcode.io API v2", + version: "2.0.0", + }, + }); + + this.router.use("/v2", serve, setup(spec)); + } + + private router = Router(); + + use: RequestHandler = this.router; +} diff --git a/yarn.lock b/yarn.lock index 7c0cb038c..6d229f735 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2030,6 +2030,14 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff" integrity sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw== +"@types/swagger-ui-express@^4.1.2": + version "4.1.2" + resolved "https://registry.yarnpkg.com/@types/swagger-ui-express/-/swagger-ui-express-4.1.2.tgz#cfc884904a104c3193f46f423d04ee0416be1ef4" + integrity sha512-t9teFTU8dKe69rX9EwL6OM2hbVquYdFM+sQ0REny4RalPlxAm+zyP04B12j4c7qEuDS6CnlwICywqWStPA3v4g== + dependencies: + "@types/express" "*" + "@types/serve-static" "*" + "@types/tapable@*", "@types/tapable@^1.0.5": version "1.0.6" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.6.tgz#a9ca4b70a18b270ccb2bc0aaafefd1d486b7ea74" @@ -3523,6 +3531,17 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" +class-validator-jsonschema@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/class-validator-jsonschema/-/class-validator-jsonschema-3.0.1.tgz#22e7ca733330663a1a2c68bc5f64355253524dd6" + integrity sha512-ynQ7adDhT+1VFNR7q5Q8bEZwZQxi0//81t2mlg+tf+lN711iw/XQvYY7xgyb5n9bh9mRP+VzyH00BNY9jVyVRQ== + dependencies: + lodash.groupby "^4.6.0" + lodash.merge "^4.6.2" + openapi3-ts "^2.0.0" + reflect-metadata "^0.1.13" + tslib "^2.0.3" + class-validator@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.13.1.tgz#381b2001ee6b9e05afd133671fbdf760da7dec67" @@ -8416,6 +8435,11 @@ lodash.get@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= +lodash.groupby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" + integrity sha1-Cwih3PaDl8OXhVwyOXg4Mt90A9E= + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -8482,6 +8506,11 @@ lodash.memoize@4.x, lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= +lodash.merge@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" + integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== + lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -9451,6 +9480,13 @@ openapi3-ts@^1.2.0: resolved "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-1.4.0.tgz#679d5a24be0efc36f5de4fc2c4b8513663e16f65" integrity sha512-8DmE2oKayvSkIR3XSZ4+pRliBsx19bSNeIzkTPswY8r4wvjX86bMxsORdqwAwMxE8PefOcSAT2auvi/0TZe9yA== +openapi3-ts@^2.0.0, openapi3-ts@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/openapi3-ts/-/openapi3-ts-2.0.1.tgz#b270aecea09e924f1886bc02a72608fca5a98d85" + integrity sha512-v6X3iwddhi276siej96jHGIqTx3wzVfMTmpGJEQDt7GPI7pI6sywItURLzpEci21SBRpPN/aOWSF5mVfFVNmcg== + dependencies: + yaml "^1.10.0" + opencollective-postinstall@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz#7a0fff978f6dbfa4d006238fbac98ed4198c3259" @@ -9767,6 +9803,11 @@ path-to-regexp@^1.1.1, path-to-regexp@^1.7.0, path-to-regexp@^1.8.0: dependencies: isarray "0.0.1" +path-to-regexp@^2.2.1: + version "2.4.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.4.0.tgz#35ce7f333d5616f1c1e1bfe266c3aba2e5b2e704" + integrity sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w== + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -11532,6 +11573,17 @@ router@^1.3.1: setprototypeof "1.2.0" utils-merge "1.0.1" +routing-controllers-openapi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/routing-controllers-openapi/-/routing-controllers-openapi-3.0.0.tgz#d5506b555e7e8b35cf32ff3cc19243c038a0800a" + integrity sha512-ZsXuQnKEy15ZO3P2rd3ctUdnUTx5cwIO2yGcCD6rEkoclT8ulxUvwHbiFvGU7KoxchttXbrg2j+Hl2Rs4gYmUA== + dependencies: + lodash "^4.17.15" + openapi3-ts "^2.0.1" + path-to-regexp "^2.2.1" + reflect-metadata "^0.1.13" + tslib "^2.1.0" + routing-controllers@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/routing-controllers/-/routing-controllers-0.9.0.tgz#979016523db37832d4c9a23c33b2654a89a563de" @@ -12483,6 +12535,18 @@ svgo@^1.0.0: unquote "~1.1.1" util.promisify "~1.0.0" +swagger-ui-dist@^3.18.1: + version "3.46.0" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.46.0.tgz#f08d2c9b4a2dce922ba363c598e4795b5ccf0b80" + integrity sha512-ueaZ45OHhHvGKmocvCkxFY8VCfbP5PgcxutoQxy9j8/VZeDoLDvg8FBf4SO6NxHhieNAdYPUd0O6G9FjJO2fqw== + +swagger-ui-express@^4.1.6: + version "4.1.6" + resolved "https://registry.yarnpkg.com/swagger-ui-express/-/swagger-ui-express-4.1.6.tgz#682294af3d5c70f74a1fa4d6a9b503a9ee55ea82" + integrity sha512-Xs2BGGudvDBtL7RXcYtNvHsFtP1DBFPMJFRxHe5ez/VG/rzVOEjazJOOSc/kSCyxreCTKfJrII6MJlL9a6t8vw== + dependencies: + swagger-ui-dist "^3.18.1" + symbol-observable@1.2.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" @@ -12829,6 +12893,11 @@ tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== +tslib@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" + integrity sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w== + tsscmp@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/tsscmp/-/tsscmp-1.0.6.tgz#85b99583ac3589ec4bfef825b5000aa911d605eb" From 9ac2d00de630d7e6b6c77db9c8eedfd2d425074b Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Wed, 7 Apr 2021 22:35:09 +0200 Subject: [PATCH 18/28] changed ENV to ENVDto --- api/src/app/middlewares/error.ts | 4 ++-- api/src/app/types/index.ts | 12 +++++++++++- api/src/config/dto.ts | 2 +- api/src/config/service.ts | 6 +++--- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/api/src/app/middlewares/error.ts b/api/src/app/middlewares/error.ts index 6fc460aac..d240b805d 100644 --- a/api/src/app/middlewares/error.ts +++ b/api/src/app/middlewares/error.ts @@ -4,13 +4,13 @@ import { } from "routing-controllers"; import { ErrorRequestHandler } from "express"; -import { GeneralResponse } from "../types"; +import { GeneralResponseDto } from "../types"; import { Service } from "typedi"; @Service() @Middleware({ type: "after" }) export class ErrorMiddleware implements ExpressErrorMiddlewareInterface { - error: ErrorRequestHandler = ( + error: ErrorRequestHandler = ( err, req, res, diff --git a/api/src/app/types/index.ts b/api/src/app/types/index.ts index 6aefa8215..2ad143283 100644 --- a/api/src/app/types/index.ts +++ b/api/src/app/types/index.ts @@ -1,5 +1,15 @@ -export interface GeneralResponse { +import { IsNumber, IsObject, IsOptional, IsString } from "class-validator"; + +export class GeneralResponseDto { + @IsNumber() + @IsOptional() code?: number; + + @IsString() + @IsOptional() msg?: string; + + @IsObject() + @IsOptional() debug?: Record; } diff --git a/api/src/config/dto.ts b/api/src/config/dto.ts index 251a6fe28..8101419bf 100644 --- a/api/src/config/dto.ts +++ b/api/src/config/dto.ts @@ -3,7 +3,7 @@ import { Matches } from "class-validator"; const environment: Environment[] = ["development", "staging", "production"]; -export class ENV { +export class ENVDto { PORT = 7070; @Matches("(" + environment.join(")|(") + ")") diff --git a/api/src/config/service.ts b/api/src/config/service.ts index 3d226640a..bd3f340fd 100644 --- a/api/src/config/service.ts +++ b/api/src/config/service.ts @@ -1,10 +1,10 @@ -import { ENV } from "./dto"; +import { ENVDto } from "./dto"; import { Service } from "typedi"; import { config } from "dotenv"; import { plainToClass } from "class-transformer"; import { validateSync } from "class-validator"; -let _env: ENV; +let _env: ENVDto; @Service() export class ConfigService { @@ -16,7 +16,7 @@ export class ConfigService { private generateConfig = () => { const _config = config(); - const output = plainToClass(ENV, { + const output = plainToClass(ENVDto, { ...process.env, ...(_config.parsed || {}), }); From 3a291070776ec82210b6e734b87190ff09ef10f0 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Wed, 7 Apr 2021 22:36:25 +0200 Subject: [PATCH 19/28] added OpenAPI docs to ContributorController --- api/src/app/middlewares/docs.ts | 3 +++ api/src/contributor/controller.spec.ts | 4 ++-- api/src/contributor/controller.ts | 12 +++++++----- api/src/contributor/type.ts | 6 ------ api/src/contributor/types.ts | 11 +++++++++++ api/src/github/dto.ts | 15 +++++++++++++++ 6 files changed, 38 insertions(+), 13 deletions(-) delete mode 100644 api/src/contributor/type.ts create mode 100644 api/src/contributor/types.ts create mode 100644 api/src/github/dto.ts diff --git a/api/src/app/middlewares/docs.ts b/api/src/app/middlewares/docs.ts index 7609956aa..7862c1b52 100644 --- a/api/src/app/middlewares/docs.ts +++ b/api/src/app/middlewares/docs.ts @@ -3,6 +3,8 @@ import { RequestHandler, Router } from "express"; import { serve, setup } from "swagger-ui-express"; import { Service } from "typedi"; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { defaultMetadataStorage } = require("class-transformer/cjs/storage"); import { getMetadataArgsStorage } from "routing-controllers"; import { routingControllersOptions } from ".."; import { routingControllersToSpec } from "routing-controllers-openapi"; @@ -15,6 +17,7 @@ export class DocsMiddleware implements ExpressMiddlewareInterface { // Parse class-validator classes into JSON Schema: const schemas = validationMetadatasToSchemas({ refPointerPrefix: "#/components/schemas/", + classTransformerMetadataStorage: defaultMetadataStorage }); // Parse routing-controllers classes into OpenAPI spec: const storage = getMetadataArgsStorage(); diff --git a/api/src/contributor/controller.spec.ts b/api/src/contributor/controller.spec.ts index 45126b251..81db3b8de 100644 --- a/api/src/contributor/controller.spec.ts +++ b/api/src/contributor/controller.spec.ts @@ -5,7 +5,7 @@ import { } from "../../test/mocks"; import { ContributorController } from "./controller"; -import { GetContributorsResponse } from "./type"; +import { GetContributorsResponseDto } from "./types"; import { GithubService } from "../github/service"; import { mock } from "jest-mock-extended"; @@ -45,7 +45,7 @@ describe("ContributorController", () => { const contributors = await contributorController.getContributor("/"); - expect(contributors).toMatchObject({ + expect(contributors).toMatchObject({ contributors: contributorsMock, }); }); diff --git a/api/src/contributor/controller.ts b/api/src/contributor/controller.ts index cec04f532..faaa6eb97 100644 --- a/api/src/contributor/controller.ts +++ b/api/src/contributor/controller.ts @@ -1,8 +1,8 @@ import { Controller, Get, QueryParam } from "routing-controllers"; - -import { GetContributorsResponse } from "./type"; +import { OpenAPI, ResponseSchema } from "routing-controllers-openapi"; +import { GetContributorsResponseDto } from "./types"; import { GithubService } from "../github/service"; -import { GithubUser } from "@dzcode.io/common/dist/types"; +import { GithubUserDto } from "../github/dto"; import { Service } from "typedi"; @Service() @@ -11,9 +11,11 @@ export class ContributorController { constructor(private readonly githubService: GithubService) {} @Get("/") + @OpenAPI({ summary: "Return a list of github users that contributed to data/[path] directory" }) + @ResponseSchema(GetContributorsResponseDto) public async getContributor( @QueryParam("path") path: string, - ): Promise { + ): Promise { const responses = await Promise.all([ // current place for data: this.githubService.listContributors({ @@ -32,7 +34,7 @@ export class ContributorController { // filter and sort contributors: const uniqUsernames: Record = {}; const contributors = [...responses[0], ...responses[1]] - .reduce((pV, cV) => { + .reduce((pV, cV) => { if (uniqUsernames[cV.login]) { uniqUsernames[cV.login]++; return pV; diff --git a/api/src/contributor/type.ts b/api/src/contributor/type.ts deleted file mode 100644 index 64fe99edd..000000000 --- a/api/src/contributor/type.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { GeneralResponse } from "../app/types"; -import { GithubUser } from "@dzcode.io/common/dist/types"; - -export interface GetContributorsResponse extends GeneralResponse { - contributors?: GithubUser[]; -} diff --git a/api/src/contributor/types.ts b/api/src/contributor/types.ts new file mode 100644 index 000000000..0dc1e67cd --- /dev/null +++ b/api/src/contributor/types.ts @@ -0,0 +1,11 @@ +import {} from "module"; +import { GeneralResponseDto } from "../app/types"; +import { GithubUserDto } from "../github/dto"; +import { Type } from "class-transformer"; +import { ValidateNested } from "class-validator"; + +export class GetContributorsResponseDto extends GeneralResponseDto { + @ValidateNested({ each: true }) + @Type(() => GithubUserDto) + contributors?: GithubUserDto[]; +} diff --git a/api/src/github/dto.ts b/api/src/github/dto.ts new file mode 100644 index 000000000..a6aee61ce --- /dev/null +++ b/api/src/github/dto.ts @@ -0,0 +1,15 @@ +import { GithubUser } from "@dzcode.io/common/dist/types"; +import { IsString } from "class-validator"; + +export class GithubUserDto implements GithubUser { + @IsString() + avatar_url!: string; // eslint-disable-line camelcase + @IsString() + html_url!: string; // eslint-disable-line camelcase + @IsString() + id!: string; + @IsString() + login!: string; + @IsString() + type!: string; +} From 989ee7fb3c091b37ce26c52dd62cfd1fc2b830bd Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Wed, 7 Apr 2021 22:44:37 +0200 Subject: [PATCH 20/28] updated api url & added all 3 local urls in readme --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 0b226ff91..58b331f59 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ The code for [dzcode.io](https://dzcode.io), a website for Algerian open-source You can find more about each folder by clicking on the folder name -| Folder | Coverage | Production URL | Staging URL | -| :------------------- | :----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------: | ---------------------------------------------------: | -| [./web](./web) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=web)](https://codecov.io/gh/dzcode-io/dzcode.io) | [dzcode.io](https://dzcode.io) | [stage.dzcode.io](https://stage.dzcode.io) | -| [./data](./data) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=data)](https://codecov.io/gh/dzcode-io/dzcode.io) | [data.dzcode.io](https://data.dzcode.io) | [data.stage.dzcode.io](https://data.stage.dzcode.io) | -| [./api](./api) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=api)](https://codecov.io/gh/dzcode-io/dzcode.io) | [api.dzcode.io](https://api.dzcode.io) | [api_stage.dzcode.io](https://api_stage.dzcode.io) | -| [./common](./common) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=common)](https://codecov.io/gh/dzcode-io/dzcode.io) | | | +| Folder | Coverage | Production URL | Staging URL | Local URL | +| :------------------- | :----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------: | -------------------------------------------------------: | --------------------------------------------: | +| [./web](./web) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=web)](https://codecov.io/gh/dzcode-io/dzcode.io) | [dzcode.io](https://dzcode.io) | [stage.dzcode.io](https://stage.dzcode.io) | [localhost:8080](http://localhost:8080) | +| [./data](./data) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=data)](https://codecov.io/gh/dzcode-io/dzcode.io) | [data.dzcode.io](https://data.dzcode.io) | [data.stage.dzcode.io](https://data.stage.dzcode.io) | [localhost:9090](http://localhost:9090) | +| [./api](./api) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=api)](https://codecov.io/gh/dzcode-io/dzcode.io) | [api.dzcode.io/v2](https://api.dzcode.io/v2) | [api_stage.dzcode.io/v2](https://api_stage.dzcode.io/v2) | [localhost:7070/v2](http://localhost:7070/v2) | +| [./common](./common) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=common)](https://codecov.io/gh/dzcode-io/dzcode.io) | | | | ## Table of Content @@ -66,14 +66,14 @@ yarn dev - For web server to - For data server go to -- For api server go to +- For api server go to ## Contributing To get started see [the contributing guidelines](https://github.com/dzcode-io/dzcode.io/blob/master/.github/CONTRIBUTING.md). - ### Before You Create a Pull Request + - If you already forked the repository, please make sure your fork is up-to-date, following [this simple steps](https://www.dzcode.io/Learn/Git_Basics/Syncing_An_Old_Forked_Repository_With_Upstream). - Please make sure your code follows the style guideline defined in this repo, for that simply run `yarn lint:fix` to ensure the conformity. This process should happen automatically whenever you commit your changes, but you can always do it manually when your Pull Request checks are failing due to linting errors. From 8427b9398dd2691a9d315d76caa6cc9d37c54205 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Wed, 7 Apr 2021 22:53:50 +0200 Subject: [PATCH 21/28] fixed linting issues --- api/.prettierignore | 5 +++++ api/src/app/middlewares/docs.ts | 2 +- api/src/contributor/controller.ts | 5 ++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/api/.prettierignore b/api/.prettierignore index 6c494aae7..ce88e607f 100644 --- a/api/.prettierignore +++ b/api/.prettierignore @@ -5,3 +5,8 @@ oracle-cloud/build .* *.tsbuildinfo +*.log +*.svg +*.jpeg +*.png +*.ico diff --git a/api/src/app/middlewares/docs.ts b/api/src/app/middlewares/docs.ts index 7862c1b52..7c31b87e9 100644 --- a/api/src/app/middlewares/docs.ts +++ b/api/src/app/middlewares/docs.ts @@ -17,7 +17,7 @@ export class DocsMiddleware implements ExpressMiddlewareInterface { // Parse class-validator classes into JSON Schema: const schemas = validationMetadatasToSchemas({ refPointerPrefix: "#/components/schemas/", - classTransformerMetadataStorage: defaultMetadataStorage + classTransformerMetadataStorage: defaultMetadataStorage, }); // Parse routing-controllers classes into OpenAPI spec: const storage = getMetadataArgsStorage(); diff --git a/api/src/contributor/controller.ts b/api/src/contributor/controller.ts index faaa6eb97..b436f960e 100644 --- a/api/src/contributor/controller.ts +++ b/api/src/contributor/controller.ts @@ -11,7 +11,10 @@ export class ContributorController { constructor(private readonly githubService: GithubService) {} @Get("/") - @OpenAPI({ summary: "Return a list of github users that contributed to data/[path] directory" }) + @OpenAPI({ + summary: + "Return a list of github users that contributed to data/[path] directory", + }) @ResponseSchema(GetContributorsResponseDto) public async getContributor( @QueryParam("path") path: string, From 143f18ace426f50b3b22635f50913f918b4c2908 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 8 Apr 2021 20:30:15 +0200 Subject: [PATCH 22/28] moved swagger docs to /v2/docs --- README.md | 14 +++++++------- api/src/app/middlewares/docs.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 58b331f59..501ae835b 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ The code for [dzcode.io](https://dzcode.io), a website for Algerian open-source You can find more about each folder by clicking on the folder name -| Folder | Coverage | Production URL | Staging URL | Local URL | -| :------------------- | :----------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------: | -------------------------------------------------------: | --------------------------------------------: | -| [./web](./web) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=web)](https://codecov.io/gh/dzcode-io/dzcode.io) | [dzcode.io](https://dzcode.io) | [stage.dzcode.io](https://stage.dzcode.io) | [localhost:8080](http://localhost:8080) | -| [./data](./data) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=data)](https://codecov.io/gh/dzcode-io/dzcode.io) | [data.dzcode.io](https://data.dzcode.io) | [data.stage.dzcode.io](https://data.stage.dzcode.io) | [localhost:9090](http://localhost:9090) | -| [./api](./api) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=api)](https://codecov.io/gh/dzcode-io/dzcode.io) | [api.dzcode.io/v2](https://api.dzcode.io/v2) | [api_stage.dzcode.io/v2](https://api_stage.dzcode.io/v2) | [localhost:7070/v2](http://localhost:7070/v2) | -| [./common](./common) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=common)](https://codecov.io/gh/dzcode-io/dzcode.io) | | | | +| Folder | Coverage | Production URL | Staging URL | Local URL | +| :------------------- | :----------------------------------------------------------------------------------------------------------------------------- | -----------------------------------------------------: | -----------------------------------------------------------------: | ------------------------------------------------------: | +| [./web](./web) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=web)](https://codecov.io/gh/dzcode-io/dzcode.io) | [dzcode.io](https://dzcode.io) | [stage.dzcode.io](https://stage.dzcode.io) | [localhost:8080](http://localhost:8080) | +| [./data](./data) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=data)](https://codecov.io/gh/dzcode-io/dzcode.io) | [data.dzcode.io](https://data.dzcode.io) | [data.stage.dzcode.io](https://data.stage.dzcode.io) | [localhost:9090](http://localhost:9090) | +| [./api](./api) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=api)](https://codecov.io/gh/dzcode-io/dzcode.io) | [api.dzcode.io/v2/docs](https://api.dzcode.io/v2/docs) | [api_stage.dzcode.io/v2/docs](https://api_stage.dzcode.io/v2/docs) | [localhost:7070/v2/docs](http://localhost:7070/v2/docs) | +| [./common](./common) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=common)](https://codecov.io/gh/dzcode-io/dzcode.io) | | | | ## Table of Content @@ -66,7 +66,7 @@ yarn dev - For web server to - For data server go to -- For api server go to +- For api server go to ## Contributing diff --git a/api/src/app/middlewares/docs.ts b/api/src/app/middlewares/docs.ts index 7c31b87e9..b5a696940 100644 --- a/api/src/app/middlewares/docs.ts +++ b/api/src/app/middlewares/docs.ts @@ -30,7 +30,7 @@ export class DocsMiddleware implements ExpressMiddlewareInterface { }, }); - this.router.use("/v2", serve, setup(spec)); + this.router.use("/v2/docs", serve, setup(spec)); } private router = Router(); From 470701b3a2d6b0fc191e0f34d227a5543d890520 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 8 Apr 2021 20:32:04 +0200 Subject: [PATCH 23/28] fixed husy not working after yarn install --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 9c60808c4..33d9ba509 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "lint:check": "concurrently \"cd api && yarn lint:check\" \"cd common && yarn lint:check\" \"cd data && yarn lint:check\" \"cd web && yarn lint:check\"", "lint:fix": "concurrently \"cd api && yarn lint:fix\" \"cd common && yarn lint:fix\" \"cd data && yarn lint:fix\" \"cd web && yarn lint:fix\"", "test": "concurrently \"cd api && yarn test\" \"cd common && yarn test\" \"cd data && yarn test\" \"cd web && yarn test\"", - "test:cov": "concurrently \"cd api && yarn test:cov\" \"cd common && yarn test:cov\" \"cd data && yarn test:cov\" \"cd web && yarn test:cov\"" + "test:cov": "concurrently \"cd api && yarn test:cov\" \"cd common && yarn test:cov\" \"cd data && yarn test:cov\" \"cd web && yarn test:cov\"", + "prepare": "husky-run install" }, "husky": { "hooks": { From b87ab051366241a40120546737548ea7b6cb6908 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 8 Apr 2021 21:19:14 +0200 Subject: [PATCH 24/28] added LoggerService --- api/src/logger/service.ts | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 api/src/logger/service.ts diff --git a/api/src/logger/service.ts b/api/src/logger/service.ts new file mode 100644 index 000000000..eb99dcb0c --- /dev/null +++ b/api/src/logger/service.ts @@ -0,0 +1,41 @@ +import { Service } from "typedi"; +import winston from "winston"; + +@Service() +export class LoggerService { + constructor() { + this.logger = winston.createLogger({ + level: "info", + format: winston.format.json(), + transports: [new winston.transports.Console({ format: winston.format.combine(winston.format.colorize(), winston.format.simple()) })], + }); + } + + public log(level: LogLevel, logInfo: LogObject) { + this.logger.log(level, logInfo.message, { ...logInfo, message: undefined }) + } + + public info(logInfo: LogObject) { + this.logger.log("info", logInfo.message, { ...logInfo, message: undefined }) + } + + public error(logInfo: LogObject) { + this.logger.log("error", logInfo.message, { ...logInfo, message: undefined }) + } + + public debug(logInfo: LogObject) { + this.logger.log("debug", logInfo.message, { ...logInfo, message: undefined }) + } + + public warn(logInfo: LogObject) { + this.logger.log("warn", logInfo.message, { ...logInfo, message: undefined }) + } + + private logger; +} + +export type LogLevel = "info" | "error" | "debug" | "warn" +type LogObject = { + message: string, + [key: string]: unknown +} From 9ee591a8eaa73835896b9a39ce8ea0e4db85e145 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 8 Apr 2021 21:30:32 +0200 Subject: [PATCH 25/28] used LoggerService instead of console --- api/package.json | 1 - api/src/app/index.ts | 17 +++++++---------- api/src/app/middlewares/error.ts | 11 ++++++----- api/src/app/middlewares/logger.ts | 30 +++++++++++++----------------- yarn.lock | 8 -------- 5 files changed, 26 insertions(+), 41 deletions(-) diff --git a/api/package.json b/api/package.json index 6c10bfeea..719151b56 100644 --- a/api/package.json +++ b/api/package.json @@ -46,7 +46,6 @@ "dotenv": "^8.2.0", "express": "^4.17.1", "express-rate-limit": "^5.2.6", - "express-winston": "^4.1.0", "helmet": "^4.4.1", "morgan": "^1.10.0", "reflect-metadata": "^0.1.13", diff --git a/api/src/app/index.ts b/api/src/app/index.ts index 4dbc9e468..0d182be81 100644 --- a/api/src/app/index.ts +++ b/api/src/app/index.ts @@ -1,7 +1,6 @@ import "reflect-metadata"; import { createExpressServer, useContainer } from "routing-controllers"; - import { Application } from "express"; import { ConfigService } from "../config/service"; import Container from "typedi"; @@ -9,15 +8,14 @@ import { ContributorController } from "../contributor/controller"; import { DocsMiddleware } from "./middlewares/docs"; import { ErrorMiddleware } from "./middlewares/error"; import { LoggerMiddleware } from "./middlewares/logger"; +import { LoggerService } from "../logger/service"; import { SecurityMiddleware } from "./middlewares/security"; +import { fsConfig } from "@dzcode.io/common/dist/config"; import router from "./routes/api"; // Use typedi container useContainer(Container); -// Env var -const env = Container.get(ConfigService).env().ENV; - // Create the app: export const routingControllersOptions = { controllers: [ContributorController], @@ -37,12 +35,11 @@ const app: Application = createExpressServer(routingControllersOptions); // Load old code to the app, temporarily until we migrate all endpoints app.use("/", router); -const port = Container.get(ConfigService).env().PORT; +const { ENV, PORT } = Container.get(ConfigService).env(); +const logger = Container.get(LoggerService); // Start it -app.listen(port, () => { - console.log("Server listening on port: " + port); - if (env === "development") { - console.log(`API Docs: http://localhost:${port}/v2`); - } +app.listen(PORT, () => { + const commonConfig = fsConfig(ENV); + logger.info({ message: `API Server up on: ${commonConfig.api.url}/v2/docs` }); }); diff --git a/api/src/app/middlewares/error.ts b/api/src/app/middlewares/error.ts index d240b805d..dd3b46aed 100644 --- a/api/src/app/middlewares/error.ts +++ b/api/src/app/middlewares/error.ts @@ -2,14 +2,16 @@ import { ExpressErrorMiddlewareInterface, Middleware, } from "routing-controllers"; - import { ErrorRequestHandler } from "express"; import { GeneralResponseDto } from "../types"; +import { LoggerService } from "../../logger/service"; import { Service } from "typedi"; @Service() @Middleware({ type: "after" }) export class ErrorMiddleware implements ExpressErrorMiddlewareInterface { + constructor(private loggerService: LoggerService) { } + error: ErrorRequestHandler = ( err, req, @@ -17,8 +19,7 @@ export class ErrorMiddleware implements ExpressErrorMiddlewareInterface { next, ) => { // Logs error - console.log("🚩 Internal Server Error"); - console.log(err); + this.loggerService.error({ message: "Internal Server Error", error: err?.message }) // Skip if headers are already sent if (res.headersSent) { @@ -26,9 +27,9 @@ export class ErrorMiddleware implements ExpressErrorMiddlewareInterface { } // return a general error response - res.status(500).json({ + return res.status(500).json({ code: 500, - msg: err.message, + msg: err?.message, }); }; } diff --git a/api/src/app/middlewares/logger.ts b/api/src/app/middlewares/logger.ts index 93115dddf..135e6e87d 100644 --- a/api/src/app/middlewares/logger.ts +++ b/api/src/app/middlewares/logger.ts @@ -1,23 +1,19 @@ import { ExpressMiddlewareInterface, Middleware } from "routing-controllers"; - +import { LogLevel, LoggerService } from "../../logger/service"; +import { RequestHandler } from "express"; import { Service } from "typedi"; -import expressWinston from "express-winston"; -import winston from "winston"; @Service() -@Middleware({ type: "before" }) +@Middleware({ type: "after" }) export class LoggerMiddleware implements ExpressMiddlewareInterface { - use = expressWinston.logger({ - transports: [new winston.transports.Console()], - format: winston.format.combine( - winston.format.colorize(), - winston.format.json(), - ), - meta: true, // optional: control whether you want to log the meta data about the request (default to true) - msg: "HTTP {{req.method}} {{req.url}}", // optional: customize the default logging message. E.g. "{{res.statusCode}} {{req.method}} {{res.responseTime}}ms {{req.url}}" - expressFormat: true, // Use the default Express/morgan request formatting. Enabling this will override any msg if true. Will only output colors with colorize set to true - ignoreRoute: () => { - return false; - }, // optional: allows to skip some log messages based on request and/or response - }); + constructor(private loggerService: LoggerService) { } + + use: RequestHandler = (req, res, next) => { + let logLevel: LogLevel = "info"; + const { statusCode } = res; + if (statusCode < 100 && statusCode >= 400) { logLevel = "error" } + + this.loggerService.log(logLevel, { message: `${res.statusCode} ${req.method} ${req.url}` }) + next(); + } } diff --git a/yarn.lock b/yarn.lock index 6d229f735..c03b6e411 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5339,14 +5339,6 @@ express-session@^1.17.1: safe-buffer "5.2.0" uid-safe "~2.1.5" -express-winston@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/express-winston/-/express-winston-4.1.0.tgz#3fd3ecea55d50ff6aee49a66e1aaa3cba8b67c93" - integrity sha512-0DaIjvNADBzC/K4Qw3UwEQc8HRjbajTaP/M43rw0LJpZcQ7SQTPfxkLsnx3ABHEO7EFNQXTpqL0BZPiwkGV8hg== - dependencies: - chalk "^2.4.2" - lodash "^4.17.20" - express@^4.16.4, express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" From 65f0c9c9544931d7c0aef1881ca80a811dc4a605 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 8 Apr 2021 21:34:16 +0200 Subject: [PATCH 26/28] fixed linting issues --- api/src/app/middlewares/error.ts | 7 ++++-- api/src/app/middlewares/logger.ts | 12 ++++++---- api/src/logger/service.ts | 39 +++++++++++++++++++++++-------- 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/api/src/app/middlewares/error.ts b/api/src/app/middlewares/error.ts index dd3b46aed..af257d904 100644 --- a/api/src/app/middlewares/error.ts +++ b/api/src/app/middlewares/error.ts @@ -10,7 +10,7 @@ import { Service } from "typedi"; @Service() @Middleware({ type: "after" }) export class ErrorMiddleware implements ExpressErrorMiddlewareInterface { - constructor(private loggerService: LoggerService) { } + constructor(private loggerService: LoggerService) {} error: ErrorRequestHandler = ( err, @@ -19,7 +19,10 @@ export class ErrorMiddleware implements ExpressErrorMiddlewareInterface { next, ) => { // Logs error - this.loggerService.error({ message: "Internal Server Error", error: err?.message }) + this.loggerService.error({ + message: "Internal Server Error", + error: err?.message, + }); // Skip if headers are already sent if (res.headersSent) { diff --git a/api/src/app/middlewares/logger.ts b/api/src/app/middlewares/logger.ts index 135e6e87d..4042d33b3 100644 --- a/api/src/app/middlewares/logger.ts +++ b/api/src/app/middlewares/logger.ts @@ -6,14 +6,18 @@ import { Service } from "typedi"; @Service() @Middleware({ type: "after" }) export class LoggerMiddleware implements ExpressMiddlewareInterface { - constructor(private loggerService: LoggerService) { } + constructor(private loggerService: LoggerService) {} use: RequestHandler = (req, res, next) => { let logLevel: LogLevel = "info"; const { statusCode } = res; - if (statusCode < 100 && statusCode >= 400) { logLevel = "error" } + if (statusCode < 100 && statusCode >= 400) { + logLevel = "error"; + } - this.loggerService.log(logLevel, { message: `${res.statusCode} ${req.method} ${req.url}` }) + this.loggerService.log(logLevel, { + message: `${res.statusCode} ${req.method} ${req.url}`, + }); next(); - } + }; } diff --git a/api/src/logger/service.ts b/api/src/logger/service.ts index eb99dcb0c..cb21545c1 100644 --- a/api/src/logger/service.ts +++ b/api/src/logger/service.ts @@ -7,35 +7,54 @@ export class LoggerService { this.logger = winston.createLogger({ level: "info", format: winston.format.json(), - transports: [new winston.transports.Console({ format: winston.format.combine(winston.format.colorize(), winston.format.simple()) })], + transports: [ + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.simple(), + ), + }), + ], }); } public log(level: LogLevel, logInfo: LogObject) { - this.logger.log(level, logInfo.message, { ...logInfo, message: undefined }) + this.logger.log(level, logInfo.message, { ...logInfo, message: undefined }); } public info(logInfo: LogObject) { - this.logger.log("info", logInfo.message, { ...logInfo, message: undefined }) + this.logger.log("info", logInfo.message, { + ...logInfo, + message: undefined, + }); } public error(logInfo: LogObject) { - this.logger.log("error", logInfo.message, { ...logInfo, message: undefined }) + this.logger.log("error", logInfo.message, { + ...logInfo, + message: undefined, + }); } public debug(logInfo: LogObject) { - this.logger.log("debug", logInfo.message, { ...logInfo, message: undefined }) + this.logger.log("debug", logInfo.message, { + ...logInfo, + message: undefined, + }); } public warn(logInfo: LogObject) { - this.logger.log("warn", logInfo.message, { ...logInfo, message: undefined }) + this.logger.log("warn", logInfo.message, { + ...logInfo, + message: undefined, + }); } private logger; } -export type LogLevel = "info" | "error" | "debug" | "warn" +export type LogLevel = "info" | "error" | "debug" | "warn"; type LogObject = { - message: string, - [key: string]: unknown -} + message: string; + [key: string]: unknown; +}; From 44ec2d0448f5c93c3266674b034c585d5d8131c8 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 8 Apr 2021 22:06:02 +0200 Subject: [PATCH 27/28] added readme docs for API folder --- api/README.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/api/README.md b/api/README.md index e833c4d2b..f0f58fcb4 100644 --- a/api/README.md +++ b/api/README.md @@ -2,8 +2,22 @@ ## Folder structure -The app is split into modules, each module can have a service and/or a controller +The app is split into modules, each module (any folder directly under `api/src`) can have service(s) and/or controller(s) + +- example of a service is [./src/github/service.ts](./src/github/service.ts) +- example of a controller is [./contributor/controller.ts](./contributor/controller.ts) + +There is still temporary old code inside: + +- [./src/app/controllers](./src/app/controllers) +- [./src/app/loaders](./src/app/loaders) +- [./src/app/routes](./src/app/routes) +- [./src/app/services](./src/app/services) ## Dependency injection We use typedi for dependency injection, please check their docs. + +We use it in both services and controllers. + +**Note:** if the readme is still unclear, please create a PR with your suggested changes/additions From 1a2b2cb3a140a167282640c6ae7142b2f461b900 Mon Sep 17 00:00:00 2001 From: Zakaria Mansouri Date: Thu, 8 Apr 2021 22:41:15 +0200 Subject: [PATCH 28/28] removed redoundant ToC --- README.md | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/README.md b/README.md index 501ae835b..bdd22fdd8 100644 --- a/README.md +++ b/README.md @@ -15,19 +15,6 @@ You can find more about each folder by clicking on the folder name | [./api](./api) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=api)](https://codecov.io/gh/dzcode-io/dzcode.io) | [api.dzcode.io/v2/docs](https://api.dzcode.io/v2/docs) | [api_stage.dzcode.io/v2/docs](https://api_stage.dzcode.io/v2/docs) | [localhost:7070/v2/docs](http://localhost:7070/v2/docs) | | [./common](./common) | [![codecov](https://codecov.io/gh/dzcode-io/dzcode.io/graph/badge.svg?flag=common)](https://codecov.io/gh/dzcode-io/dzcode.io) | | | | -## Table of Content - -- [dzCode.io](#dzcodeio) - - [Meta](#meta) - - [Table of Content](#table-of-content) - - [Get Started](#get-started) - - [Perquisites](#perquisites) - - [Run it locally](#run-it-locally) - - [Contributing](#contributing) - - [Before You Create a Pull Request](#before-you-create-a-pull-request) - - [List Your Project or Add/Edit Article](#list-your-project-or-addedit-article) - - [License](#license) - ## Get Started ### Perquisites @@ -64,7 +51,7 @@ yarn yarn dev ``` -- For web server to +- For web server go to - For data server go to - For api server go to