generalize eslint

This commit is contained in:
Igor Hrenowitsch Propisnov 2024-05-22 21:45:16 +02:00
parent 45cc2cd476
commit 16773c3ca3
33 changed files with 2225 additions and 239 deletions

210
.eslintrc Normal file
View File

@ -0,0 +1,210 @@
{
"root": true,
"plugins": ["import", "prettier", "@stylistic/eslint-plugin-ts", "@stylistic/eslint-plugin", "sort-class-members", "unused-imports"],
"overrides": [
{
"files": ["*.ts"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier",
"plugin:prettier/recommended"
],
"rules": {
"@stylistic/ts/lines-between-class-members": [
"error",
{
"enforce": [
{ "blankLine": "always", "prev": "*", "next": "method" },
{ "blankLine": "always", "prev": "method", "next": "*" },
{ "blankLine": "never", "prev": "field", "next": "field" }
]
}
],
"@stylistic/ts/block-spacing": ["error"],
"@stylistic/ts/brace-style": "off",
"@stylistic/ts/key-spacing": ["error", { "afterColon": true }],
"@stylistic/ts/keyword-spacing": ["error", { "before": true }],
"@stylistic/ts/no-extra-parens": ["error", "all", {
"nestedBinaryExpressions": false,
"ternaryOperandBinaryExpressions": false
}],
"@stylistic/ts/no-extra-semi": ["error"],
"@stylistic/ts/object-curly-spacing": ["error", "always"],
"@stylistic/ts/quotes": ["error", "single"],
"@stylistic/ts/semi": ["error", "always"],
"@stylistic/ts/space-before-blocks": ["error"],
"@stylistic/ts/space-before-function-paren": ["error", {"anonymous": "always", "named": "never", "asyncArrow": "always"}],
"@stylistic/ts/space-infix-ops": ["error"],
// "@stylistic/ts/max-statements-per-line": ["error", { "max": 1 }],
// "@stylistic/ts/multiline-ternary": ["error", "always"],
// "@stylistic/ts/newline-per-chained-call": ["error", { "ignoreChainWithDepth": 2 }],
// "@stylistic/ts/no-confusing-arrow": ["error"],
// "@stylistic/ts/no-floating-decimal": ["error"],
// "@stylistic/ts/no-mixed-operators": ["error"],
// "@stylistic/ts/no-mixed-spaces-and-tabs": ["error"],/
// "@stylistic/ts/no-multi-spaces": ["error"],
// "@stylistic/ts/no-multiple-empty-lines": ["error", { "max": 2 }],
// "@stylistic/ts/no-tabs": ["error", { "allowIndentationTabs": true }],
// "@stylistic/ts/no-whitespace-before-property": ["error"],
// "@stylistic/ts/nonblock-statement-body-position": ["error", "below"],
// "@stylistic/ts/object-curly-newline": ["error", "always"],
// "@stylistic/ts/object-property-newline": ["error"],
// "@stylistic/ts/one-var-declaration-per-line": ["error", "always"],
// "@stylistic/ts/operator-linebreak": ["error", "before"],
// "@stylistic/ts/padded-blocks": ["error", "never"],
// "@stylistic/ts/rest-spread-spacing": ["error", "never"],
// "@stylistic/ts/semi-spacing": ["error"],
// "@stylistic/ts/semi-style": ["error", "last"],
// "@stylistic/ts/space-in-parens": ["error", "never"],
// "@stylistic/ts/space-unary-ops": ["error"],
// "@stylistic/ts/template-curly-spacing": ["error"],
// "@stylistic/ts/template-tag-spacing": ["error"],
// "@stylistic/ts/wrap-regex": ["error"],
"no-console": ["warn", { "allow": ["warn", "error"] }],
"no-debugger": "error",
"no-var": ["error"],
"eqeqeq": ["error", "always"],
"no-eval": "error",
"prefer-const": ["error", { "destructuring": "all", "ignoreReadBeforeAssign": true }],
"prettier/prettier": ["error", { "printWidth": 80 }],
"no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
"@typescript-eslint/explicit-member-accessibility": [
"error",
{
"accessibility": "explicit"
}
],
"@typescript-eslint/explicit-function-return-type": [
"error",
{
"allowExpressions": false,
"allowTypedFunctionExpressions": true,
"allowHigherOrderFunctions": false,
"allowDirectConstAssertionInArrowFunctions": false,
"allowConciseArrowFunctionExpressionsStartingWithVoid": false
}
],
"@typescript-eslint/member-ordering": [
"error",
{
"default": [
"public-static-field",
"protected-static-field",
"private-static-field",
"public-instance-field",
"protected-instance-field",
"private-instance-field",
"public-constructor",
"protected-constructor",
"private-constructor",
"public-static-method",
"protected-static-method",
"private-static-method",
"public-instance-method",
"protected-instance-method",
"private-instance-method"
]
}
],
// https://github.com/bryanrsmith/eslint-plugin-sort-class-members -> Read Docs and replace @typescript-eslint/member-ordering
// "sort-class-members/sort-class-members": [
// 2,
// {
// "order": [
// "[public-properties]",
// "[protected-properties]",
// "[private-properties]",
// "everything-else"
// ],
// "groups": {
// "public-properties": [{ "type": "property", "accessibility": "public" }],
// "protected-properties": [{ "type": "property", "accessibility": "protected" }],
// "private-properties": [{ "type": "property", "accessibility": "private" }]
// }
// }
// ],
"padding-line-between-statements": [
"error",
{ "blankLine": "always", "prev": "const", "next": "*" },
{ "blankLine": "always", "prev": "let", "next": "*" },
{ "blankLine": "always", "prev": "var", "next": "*" },
{ "blankLine": "any", "prev": ["const", "let", "var"], "next": ["const", "let", "var"] }
],
"@typescript-eslint/no-unused-vars": [
"error",
{
"vars": "all",
"args": "after-used",
"ignoreRestSiblings": false
}
],
"@typescript-eslint/typedef": [
"error",
{
"arrayDestructuring": true,
"arrowParameter": false,
"memberVariableDeclaration": true,
"objectDestructuring": true,
"parameter": true,
"propertyDeclaration": true,
"variableDeclaration": false,
"variableDeclarationIgnoreFunction": true
}
],
"import/order": [
"error",
{
"groups": [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index"
],
"pathGroups": [
{
"pattern": "@angular/**",
"group": "external",
"position": "before"
},
{
"pattern": "@app/**",
"group": "internal",
"position": "before"
},
{
"pattern": "@env/**",
"group": "internal",
"position": "before"
}
],
"pathGroupsExcludedImportTypes": ["builtin"],
"newlines-between": "always",
"alphabetize": {
"order": "asc",
"caseInsensitive": true
}
}
]
}
},
{
"files": ["*.dto.ts", "*.entity.ts"],
"rules": {
"@stylistic/ts/lines-between-class-members": "off"
}
}
]
}

View File

@ -2,3 +2,5 @@ coverage
dist
node_modules
pnpm-lock.yaml
/frontend/pnpm-lock.yaml
/backend/pnpm-lock.yaml

8
backend/.eslintrc Normal file
View File

@ -0,0 +1,8 @@
{
"extends": ["./../.eslintrc"],
"parser": "@typescript-eslint/parser",
"env": {
"node": true,
"jest": true
}
}

View File

@ -1,25 +0,0 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

View File

@ -14,7 +14,7 @@
"ngVersion": "17.0.0",
"npmRepository": null,
"configurationPrefix": null,
"apiModulePrefix" : "TicketApi",
"apiModulePrefix": "TicketApi",
"providedIn": "any",
"fileNaming": "camelCase",
"paramNaming": "camelCase",

View File

@ -18,7 +18,9 @@
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"build:api": "pnpm openapi-generator-cli generate"
"prettier:fix": "prettier --write .",
"prettier:check": "prettier --check .",
"foramt": "pnpm lint && prettier:fix"
},
"dependencies": {
"@nestjs/common": "^10.0.0",
@ -32,10 +34,8 @@
"argon2": "^0.40.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"install": "^0.13.0",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"pg": "^8.11.5",
"reflect-metadata": "^0.2.0",
"rxjs": "^7.8.1",
@ -51,11 +51,8 @@
"@types/jest": "^29.5.2",
"@types/node": "^20.3.1",
"@types/supertest": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"jest": "^29.5.0",
"prettier": "^3.0.0",
"source-map-support": "^0.5.21",

View File

@ -38,18 +38,12 @@ dependencies:
class-validator:
specifier: ^0.14.1
version: 0.14.1
install:
specifier: ^0.13.0
version: 0.13.0
passport:
specifier: ^0.7.0
version: 0.7.0
passport-jwt:
specifier: ^4.0.1
version: 4.0.1
passport-local:
specifier: ^1.0.0
version: 1.0.0
pg:
specifier: ^8.11.5
version: 8.11.5
@ -91,21 +85,12 @@ devDependencies:
'@types/supertest':
specifier: ^6.0.0
version: 6.0.2
'@typescript-eslint/eslint-plugin':
specifier: ^6.0.0
version: 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.4.3)
'@typescript-eslint/parser':
specifier: ^6.0.0
version: 6.21.0(eslint@8.57.0)(typescript@5.4.3)
eslint:
specifier: ^8.42.0
version: 8.57.0
eslint-config-prettier:
specifier: ^9.0.0
version: 9.1.0(eslint@8.57.0)
eslint-plugin-prettier:
specifier: ^5.0.0
version: 5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5)
jest:
specifier: ^29.5.0
version: 29.7.0(@types/node@20.12.4)(ts-node@10.9.2)
@ -1192,11 +1177,6 @@ packages:
requiresBuild: true
optional: true
/@pkgr/core@0.1.1:
resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
dev: true
/@sinclair/typebox@0.27.8:
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
dev: true
@ -1382,10 +1362,6 @@ packages:
resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==}
dev: true
/@types/semver@7.5.8:
resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
dev: true
/@types/send@0.17.4:
resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==}
dependencies:
@ -1433,35 +1409,6 @@ packages:
'@types/yargs-parser': 21.0.3
dev: true
/@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.4.3):
resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
'@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
eslint: ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@eslint-community/regexpp': 4.10.0
'@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.3)
'@typescript-eslint/scope-manager': 6.21.0
'@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@5.4.3)
'@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.4.3)
'@typescript-eslint/visitor-keys': 6.21.0
debug: 4.3.4
eslint: 8.57.0
graphemer: 1.4.0
ignore: 5.3.1
natural-compare: 1.4.0
semver: 7.6.0
ts-api-utils: 1.3.0(typescript@5.4.3)
typescript: 5.4.3
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.4.3):
resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==}
engines: {node: ^16.0.0 || >=18.0.0}
@ -1491,26 +1438,6 @@ packages:
'@typescript-eslint/visitor-keys': 6.21.0
dev: true
/@typescript-eslint/type-utils@6.21.0(eslint@8.57.0)(typescript@5.4.3):
resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.4.3)
'@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.4.3)
debug: 4.3.4
eslint: 8.57.0
ts-api-utils: 1.3.0(typescript@5.4.3)
typescript: 5.4.3
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/types@6.21.0:
resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==}
engines: {node: ^16.0.0 || >=18.0.0}
@ -1538,25 +1465,6 @@ packages:
- supports-color
dev: true
/@typescript-eslint/utils@6.21.0(eslint@8.57.0)(typescript@5.4.3):
resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
dependencies:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@types/json-schema': 7.0.15
'@types/semver': 7.5.8
'@typescript-eslint/scope-manager': 6.21.0
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.4.3)
eslint: 8.57.0
semver: 7.6.0
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/@typescript-eslint/visitor-keys@6.21.0:
resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==}
engines: {node: ^16.0.0 || >=18.0.0}
@ -2559,36 +2467,6 @@ packages:
engines: {node: '>=12'}
dev: true
/eslint-config-prettier@9.1.0(eslint@8.57.0):
resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
hasBin: true
peerDependencies:
eslint: '>=7.0.0'
dependencies:
eslint: 8.57.0
dev: true
/eslint-plugin-prettier@5.1.3(eslint-config-prettier@9.1.0)(eslint@8.57.0)(prettier@3.2.5):
resolution: {integrity: sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==}
engines: {node: ^14.18.0 || >=16.0.0}
peerDependencies:
'@types/eslint': '>=8.0.0'
eslint: '>=8.0.0'
eslint-config-prettier: '*'
prettier: '>=3.0.0'
peerDependenciesMeta:
'@types/eslint':
optional: true
eslint-config-prettier:
optional: true
dependencies:
eslint: 8.57.0
eslint-config-prettier: 9.1.0(eslint@8.57.0)
prettier: 3.2.5
prettier-linter-helpers: 1.0.0
synckit: 0.8.8
dev: true
/eslint-scope@5.1.1:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
engines: {node: '>=8.0.0'}
@ -2792,10 +2670,6 @@ packages:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
dev: true
/fast-diff@1.3.0:
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
dev: true
/fast-glob@3.3.2:
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
engines: {node: '>=8.6.0'}
@ -3251,11 +3125,6 @@ packages:
wrap-ansi: 6.2.0
dev: true
/install@0.13.0:
resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==}
engines: {node: '>= 0.10'}
dev: false
/interpret@1.4.0:
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
engines: {node: '>= 0.10'}
@ -4406,13 +4275,6 @@ packages:
passport-strategy: 1.0.0
dev: false
/passport-local@1.0.0:
resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==}
engines: {node: '>= 0.4.0'}
dependencies:
passport-strategy: 1.0.0
dev: false
/passport-strategy@1.0.0:
resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==}
engines: {node: '>= 0.4.0'}
@ -4587,13 +4449,6 @@ packages:
engines: {node: '>= 0.8.0'}
dev: true
/prettier-linter-helpers@1.0.0:
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
engines: {node: '>=6.0.0'}
dependencies:
fast-diff: 1.3.0
dev: true
/prettier@3.2.5:
resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
engines: {node: '>=14'}
@ -5133,14 +4988,6 @@ packages:
engines: {node: '>=0.10'}
dev: true
/synckit@0.8.8:
resolution: {integrity: sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==}
engines: {node: ^14.18.0 || >=16.0.0}
dependencies:
'@pkgr/core': 0.1.1
tslib: 2.6.2
dev: true
/tapable@2.2.1:
resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==}
engines: {node: '>=6'}

View File

@ -1,4 +1,5 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';

View File

@ -1,12 +1,13 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
public constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
public getHello(): string {
return this.appService.getHello();
}
}

View File

@ -1,14 +1,15 @@
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { DatabaseModule } from './modules/database-module/database.module';
import { CorsMiddleware } from './middleware/cors-middleware/cors.middlware';
import { CspMiddleware } from './middleware/csp-middleware/csp.middleware';
import { SecurityHeadersMiddleware } from './middleware/security-middleware/security.middleware';
import { HttpsRedirectMiddleware } from './middleware/https-middlware/https-redirect.middleware';
import { SecurityHeadersMiddleware } from './middleware/security-middleware/security.middleware';
import { AuthModule } from './modules/auth-module/auth.module';
import { AccessTokenGuard } from './modules/auth-module/common/guards';
import { CorsMiddleware } from './middleware/cors-middleware/cors.middlware';
import { DatabaseModule } from './modules/database-module/database.module';
@Module({
imports: [
@ -22,7 +23,7 @@ import { CorsMiddleware } from './middleware/cors-middleware/cors.middlware';
providers: [AppService, { provide: 'APP_GUARD', useClass: AccessTokenGuard }],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
public configure(consumer: MiddlewareConsumer): void {
consumer
// TODO Redirect via Reverse Proxy all HTTP requests to HTTPS
.apply(

View File

@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
public getHello(): string {
return 'Hello World!';
}
}

View File

@ -9,20 +9,20 @@ import {
@Entity()
export class UserCredentials {
@PrimaryGeneratedColumn('uuid')
id: number;
public id: number;
@Column({ unique: true })
email: string;
public email: string;
@Column()
hash: string;
public hash: string;
@Column({ nullable: true })
hashedRt?: string;
public hashedRt?: string;
@CreateDateColumn()
createdAt: Date;
public createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
public updatedAt: Date;
}

View File

@ -1,11 +1,13 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ValidationPipe } from '@nestjs/common';
import * as fs from 'fs';
import { join } from 'path';
async function setupSwagger(app) {
import { INestApplication, ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function setupSwagger(app: INestApplication): Promise<void> {
const config = new DocumentBuilder()
.setTitle('Tickets API')
.setDescription('Description of the API')
@ -13,9 +15,11 @@ async function setupSwagger(app) {
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
const docsDir = join(process.cwd(), 'docs');
if (!fs.existsSync(docsDir)) {
fs.mkdirSync(docsDir);
}
@ -26,16 +30,17 @@ async function setupSwagger(app) {
);
}
async function setupPrefix(app) {
async function setupPrefix(app: INestApplication): Promise<void> {
app.setGlobalPrefix('api');
}
async function setupClassValidator(app) {
async function setupClassValidator(app: INestApplication): Promise<void> {
app.useGlobalPipes(new ValidationPipe());
}
async function bootstrap() {
async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule);
await setupSwagger(app);
await setupPrefix(app);
await setupClassValidator(app);

View File

@ -4,7 +4,7 @@ import { Request, Response, NextFunction } from 'express';
@Injectable()
export class CorsMiddleware implements NestMiddleware {
constructor(private readonly configService: ConfigService) {}
public constructor(private readonly configService: ConfigService) {}
public use(req: Request, res: Response, next: NextFunction): void {
if (this.configService.get<string>('NODE_ENV') === 'development') {

View File

@ -1,13 +1,14 @@
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { ConfigService } from '@nestjs/config';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class CspMiddleware implements NestMiddleware {
constructor(private readonly configService: ConfigService) {}
public constructor(private readonly configService: ConfigService) {}
public use(req: Request, res: Response, next: NextFunction): void {
const cspDirectives = this.configService.get<string>('CSP_DIRECTIVES');
if (cspDirectives) {
res.setHeader('Content-Security-Policy', cspDirectives);
}

View File

@ -4,12 +4,13 @@ import { NextFunction, Request, Response } from 'express';
@Injectable()
export class HttpsRedirectMiddleware implements NestMiddleware {
constructor(private readonly configService: ConfigService) {}
public constructor(private readonly configService: ConfigService) {}
public use(req: Request, res: Response, next: NextFunction) {
public use(req: Request, res: Response, next: NextFunction): void {
if (this.configService.get<string>('NODE_ENV') === 'production') {
if (req.protocol === 'http') {
const httpsUrl = `https://${req.headers.host}${req.url}`;
res.redirect(httpsUrl);
} else {
next();

View File

@ -1,10 +1,10 @@
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { ConfigService } from '@nestjs/config';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class SecurityHeadersMiddleware implements NestMiddleware {
constructor(private readonly configService: ConfigService) {}
public constructor(private readonly configService: ConfigService) {}
public use(req: Request, res: Response, next: NextFunction): void {
if (this.configService.get<string>('NODE_ENV') === 'production') {

View File

@ -1,13 +1,14 @@
import { Module } from '@nestjs/common';
import { AuthService } from './services/auth.service';
import { AuthController } from './controller/auth.controller';
import { JwtModule } from '@nestjs/jwt';
import { AccessTokenStrategy, RefreshTokenStrategy } from './strategies';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserCredentials } from 'src/entities/user-credentials.entity';
import { AuthController } from './controller/auth.controller';
import { UserRepository } from './repositories/user.repository';
import { AuthService } from './services/auth.service';
import { EncryptionService } from './services/encryption.service';
import { TokenManagementService } from './services/token-management.service';
import { AccessTokenStrategy, RefreshTokenStrategy } from './strategies';
@Module({
imports: [

View File

@ -5,6 +5,7 @@ export const GetCurrentUserId = createParamDecorator(
(_: undefined, context: ExecutionContext): number => {
const request = context.switchToHttp().getRequest();
const user = request.user as JwtPayload;
return user.sub;
}
);

View File

@ -7,6 +7,7 @@ export const GetCurrentUser = createParamDecorator(
context: ExecutionContext
) => {
const request = context.switchToHttp().getRequest();
if (!data) return request.user;
return request.user[data];
}

View File

@ -1,3 +1,4 @@
import { SetMetadata } from '@nestjs/common';
import { CustomDecorator, SetMetadata } from '@nestjs/common';
export const Public = () => SetMetadata('isPublic', true);
export const Public = (): CustomDecorator<string> =>
SetMetadata('isPublic', true);

View File

@ -1,11 +1,11 @@
import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { Reflector } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport';
import { Observable } from 'rxjs';
@Injectable()
export class AccessTokenGuard extends AuthGuard('jwt-access-token') {
constructor(private readonly reflector: Reflector) {
public constructor(private readonly reflector: Reflector) {
super();
}

View File

@ -1,7 +1,7 @@
import { AuthGuard } from '@nestjs/passport';
export class RefreshTokenGuard extends AuthGuard('jwt-refresh-token') {
constructor() {
public constructor() {
super();
}
}

View File

@ -6,16 +6,17 @@ import {
HttpStatus,
UseGuards,
} from '@nestjs/common';
import { AuthService } from '../services/auth.service';
import { TokensDto, UserCredentialsDto } from '../models/dto';
import { RefreshTokenGuard } from '../common/guards';
import { GetCurrentUser, GetCurrentUserId, Public } from '../common/decorators';
import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
import { GetCurrentUser, GetCurrentUserId, Public } from '../common/decorators';
import { RefreshTokenGuard } from '../common/guards';
import { TokensDto, UserCredentialsDto } from '../models/dto';
import { AuthService } from '../services/auth.service';
@ApiTags('Authentication')
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
public constructor(private readonly authService: AuthService) {}
@ApiCreatedResponse({
description: 'User signed up successfully',

View File

@ -5,7 +5,7 @@ import { Repository } from 'typeorm';
@Injectable()
export class UserRepository {
constructor(
public constructor(
@InjectRepository(UserCredentials)
private readonly repository: Repository<UserCredentials>
) {}
@ -15,6 +15,7 @@ export class UserRepository {
hash: string
): Promise<UserCredentials> {
const user = this.repository.create({ email, hash });
return this.repository.save(user);
}
@ -35,6 +36,7 @@ export class UserRepository {
hashedRt: string | null
): Promise<number> {
const result = await this.repository.update(userId, { hashedRt });
return result.affected ?? 0;
}
}

View File

@ -1,12 +1,14 @@
import { ForbiddenException, Injectable } from '@nestjs/common';
import { TokensDto, UserCredentialsDto } from '../models/dto';
import { EncryptionService } from './encryption.service';
import { UserRepository } from '../repositories/user.repository';
import { EncryptionService } from './encryption.service';
import { TokenManagementService } from './token-management.service';
@Injectable()
export class AuthService {
constructor(
public constructor(
private readonly userRepository: UserRepository,
private readonly tokenManagementService: TokenManagementService,
private readonly encryptionService: EncryptionService
@ -20,6 +22,7 @@ export class AuthService {
userCredentials.email,
passwordHashed
);
return this.generateAndPersistTokens(user.id, user.email);
}
@ -27,6 +30,7 @@ export class AuthService {
const user = await this.userRepository.findUserByEmail(
userCredentials.email
);
if (!user) {
throw new ForbiddenException('Access Denied');
}
@ -35,6 +39,7 @@ export class AuthService {
userCredentials.password,
user.hash
);
if (!passwordMatch) {
throw new ForbiddenException('Access Denied');
}
@ -47,6 +52,7 @@ export class AuthService {
refreshToken: string
): Promise<TokensDto> {
const user = await this.userRepository.findUserById(userId);
if (!user || !user.hashedRt) {
throw new ForbiddenException('Access Denied');
}
@ -55,6 +61,7 @@ export class AuthService {
refreshToken,
user.hashedRt
);
if (!refreshTokenMatch) {
throw new ForbiddenException('Access Denied');
}
@ -67,6 +74,7 @@ export class AuthService {
userId,
null
);
return affected > 0;
}
@ -81,6 +89,7 @@ export class AuthService {
const hashedRefreshToken = await this.encryptionService.hashData(
tokens.refresh_token
);
await this.userRepository.updateUserTokenHash(userId, hashedRefreshToken);
return tokens;
}

View File

@ -1,6 +1,7 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { TokensDto } from '../models/dto';
@Injectable()
@ -10,7 +11,7 @@ export class TokenManagementService {
private readonly JWT_SECRET_AT: string;
private readonly JWT_SECRET_RT: string;
constructor(
public constructor(
private readonly jwt: JwtService,
private readonly configService: ConfigService
) {
@ -30,6 +31,7 @@ export class TokenManagementService {
): Promise<TokensDto> {
const access_token: string = await this.createAccessToken(userId, email);
const refresh_token: string = await this.createRefreshToken(userId, email);
return { access_token, refresh_token };
}

View File

@ -1,7 +1,8 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { JwtPayload } from '../models/types';
@Injectable()
@ -9,7 +10,7 @@ export class AccessTokenStrategy extends PassportStrategy(
Strategy,
'jwt-access-token'
) {
constructor(private readonly configService: ConfigService) {
public constructor(private readonly configService: ConfigService) {
super(AccessTokenStrategy.getJwtConfig(configService));
}

View File

@ -1,15 +1,15 @@
import { Injectable, ForbiddenException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { Request } from 'express';
import { Strategy, ExtractJwt } from 'passport-jwt';
@Injectable()
export class RefreshTokenStrategy extends PassportStrategy(
Strategy,
'jwt-refresh-token'
) {
constructor(private readonly configService: ConfigService) {
public constructor(private readonly configService: ConfigService) {
super(RefreshTokenStrategy.createJwtStrategyOptions(configService));
}

View File

@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigService, ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { databaseConfigFactory } from './database-config';
@Module({

View File

@ -1,6 +1,7 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {

View File

@ -10,6 +10,9 @@
"install:all": "pnpm install && pnpm run install:frontend && pnpm run install:backend",
"install:frontend": "cd frontend && pnpm install",
"install:backend": "cd backend && pnpm install",
"format:frontend": "cd frontend && pnpm run format",
"format:backend": "cd backend && concurrently \"pnpm run lint\" \"pnpm run prettier:fix\"",
"format:all": "concurrently \"pnpm run format:frontend\" \"pnpm run format:backend\"",
"build:api": "openapi-generator-cli generate"
},
"keywords": [],
@ -17,6 +20,15 @@
"license": "ISC",
"devDependencies": {
"@openapitools/openapi-generator-cli": "^2.13.4",
"concurrently": "^8.2.2"
"@stylistic/eslint-plugin": "^2.1.0",
"@stylistic/eslint-plugin-ts": "^2.1.0",
"@typescript-eslint/eslint-plugin": "6.19.0",
"concurrently": "^8.2.2",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-sort-class-members": "^1.20.0",
"eslint-plugin-unused-imports": "^3.2.0",
"prettier": "^3.2.5"
}
}

File diff suppressed because it is too large Load Diff