diff --git a/.eslintrc.js b/.eslintrc.js index 259de13..5ab281c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -17,9 +17,66 @@ module.exports = { }, 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', + "curly": "error", + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", + "import/first": "error", + "import/newline-after-import": "error", + "import/no-duplicates": "error", + "eqeqeq": "warn", + "no-lonely-if": "error", + "no-multi-assign": "error", + "@typescript-eslint/no-shadow": "warn", + "no-useless-return": "error", + "no-useless-rename": "error", + "object-shorthand": "error", + "one-var-declaration-per-line": "error", + "prefer-object-spread": "error", + "no-unreachable-loop": "error", + "no-template-curly-in-string": "error", + "default-case-last": "error", + "no-array-constructor": "error", + "no-else-return": "error", + "no-negated-condition": "error", + "array-callback-return": "error", + "@typescript-eslint/consistent-type-definitions": "error", + "no-type-assertion/no-type-assertion": "warn", + "@typescript-eslint/ban-ts-comment": "error", + "@typescript-eslint/no-extraneous-class": "off", + "@typescript-eslint/prefer-nullish-coalescing": "off", + "@typescript-eslint/no-unsafe-assignment": "warn", + "@typescript-eslint/restrict-template-expressions": "warn", + "@typescript-eslint/no-unsafe-member-access": "warn", + "@typescript-eslint/no-unsafe-argument": "warn", + "@typescript-eslint/no-unsafe-return": "warn", + "@typescript-eslint/no-unsafe-call": "warn", + "@typescript-eslint/require-await": "warn", + "@typescript-eslint/restrict-plus-operands": "warn", + "@typescript-eslint/no-non-null-asserted-optional-chain": "warn", + "@typescript-eslint/no-misused-promises": [ + "warn", + { + "checksVoidReturn": { + "attributes": false + } + } + ], + "@typescript-eslint/await-thenable": "warn", + "@typescript-eslint/no-floating-promises": "warn", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-non-null-assertion": "warn", + "@typescript-eslint/no-unnecessary-condition": "warn", + "@typescript-eslint/non-nullable-type-assertion-style": "warn", + "@typescript-eslint/no-confusing-void-expression": "warn", + "@typescript-eslint/no-redundant-type-constituents": "warn", + "@typescript-eslint/no-invalid-void-type": "warn", + "@typescript-eslint/dot-notation": "warn", + "@typescript-eslint/prefer-optional-chain": "warn", + "@typescript-eslint/no-meaningless-void-operator": "warn", + "@typescript-eslint/no-unnecessary-type-arguments": "warn", + "@typescript-eslint/consistent-indexed-object-style": "warn", + "unused-imports/no-unused-imports": "error" + }, + }; diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..49c1745 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,126 @@ +{ + "env": { + "browser": true, + "es6": true, + "node": true, + "es2021": true + }, + "settings": { + "react": { + "version": "detect" + } + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/strict-type-checked", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/stylistic-type-checked", + "plugin:@typescript-eslint/strict", + "next/core-web-vitals", + "plugin:cypress/recommended", + "plugin:prettier/recommended", + "plugin:sonarjs/recommended" + ], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaFeatures": { + "jsx": true + }, + "ecmaVersion": 2018, + "sourceType": "module", + "project": "./tsconfig.json" + }, + "plugins": [ + "react", + "@typescript-eslint", + "prettier", + "simple-import-sort", + "no-type-assertion", + "sonarjs", + "import", + "unused-imports" + ], + "rules": { + "react/prop-types": "off", + "react/react-in-jsx-scope": "off", + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ], + "@typescript-eslint/explicit-function-return-type": [ + "warn", + { + "allowExpressions": true, + "allowTypedFunctionExpressions": true, + "allowHigherOrderFunctions": true + } + ], + "@next/next/no-img-element": "off", + "curly": "error", + "simple-import-sort/imports": "error", + "simple-import-sort/exports": "error", + "import/first": "error", + "import/newline-after-import": "error", + "import/no-duplicates": "error", + "eqeqeq": "warn", + "no-lonely-if": "error", + "no-multi-assign": "error", + "@typescript-eslint/no-shadow": "warn", + "no-useless-return": "error", + "no-useless-rename": "error", + "object-shorthand": "error", + "one-var-declaration-per-line": "error", + "prefer-object-spread": "error", + "no-unreachable-loop": "error", + "no-template-curly-in-string": "error", + "default-case-last": "error", + "no-array-constructor": "error", + "no-else-return": "error", + "no-negated-condition": "error", + "array-callback-return": "error", + "@typescript-eslint/consistent-type-definitions": "error", + "no-type-assertion/no-type-assertion": "warn", + "@typescript-eslint/ban-ts-comment": "error", + "@typescript-eslint/no-extraneous-class": "off", + "@typescript-eslint/prefer-nullish-coalescing": "off", + "@typescript-eslint/no-unsafe-assignment": "warn", + "@typescript-eslint/restrict-template-expressions": "warn", + "@typescript-eslint/no-unsafe-member-access": "warn", + "@typescript-eslint/no-unsafe-argument": "warn", + "@typescript-eslint/no-unsafe-return": "warn", + "@typescript-eslint/no-unsafe-call": "warn", + "@typescript-eslint/require-await": "warn", + "@typescript-eslint/restrict-plus-operands": "warn", + "@typescript-eslint/no-non-null-asserted-optional-chain": "warn", + "@typescript-eslint/no-misused-promises": [ + "warn", + { + "checksVoidReturn": { + "attributes": false + } + } + ], + "@typescript-eslint/await-thenable": "warn", + "@typescript-eslint/no-floating-promises": "warn", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-non-null-assertion": "warn", + "@typescript-eslint/no-unnecessary-condition": "warn", + "@typescript-eslint/non-nullable-type-assertion-style": "warn", + "@typescript-eslint/no-confusing-void-expression": "warn", + "@typescript-eslint/no-redundant-type-constituents": "warn", + "@typescript-eslint/no-invalid-void-type": "warn", + "@typescript-eslint/dot-notation": "warn", + "@typescript-eslint/prefer-optional-chain": "warn", + "@typescript-eslint/no-meaningless-void-operator": "warn", + "@typescript-eslint/no-unnecessary-type-arguments": "warn", + "@typescript-eslint/consistent-indexed-object-style": "warn", + "unused-imports/no-unused-imports": "error" + } +} diff --git a/.prettierrc b/.prettierrc index 0d73619..c0bff4a 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,5 +3,8 @@ "trailingComma": "all", "tabWidth": 2, "semi": true, - "printWidth": 100 + "printWidth": 100, + "importOrderSeparation": true, + "importOrderSortSpecifiers": true, + "plugins": ["@trivago/prettier-plugin-sort-imports"] } \ No newline at end of file diff --git a/package.json b/package.json index a6955ee..5866103 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "handlebars": "^4.7.8", "hbs": "^4.2.0", "ioredis": "^5.4.1", + "joi": "^17.13.3", "keygrip": "^1.1.0", "mysql2": "^3.10.2", "nanoid": "^5.0.7", @@ -63,10 +64,13 @@ "wildcard": "^2.0.1" }, "devDependencies": { + "@eslint/js": "^9.8.0", "@nestjs/cli": "^10.4.2", "@nestjs/schematics": "^10.1.2", "@nestjs/testing": "^10.3.10", + "@trivago/prettier-plugin-sort-imports": "^4.3.0", "@types/cookie-parser": "^1.4.7", + "@types/eslint__js": "^8.42.3", "@types/express": "^4.17.21", "@types/jest": "^29.5.12", "@types/keygrip": "^1.0.6", @@ -89,7 +93,8 @@ "ts-node": "^10.9.2", "ts-patch": "^3.2.1", "tsconfig-paths": "^4.2.0", - "typescript": "^5.5.2" + "typescript": "^5.5.2", + "typescript-eslint": "^7.17.0" }, "jest": { "moduleFileExtensions": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index abaf822..153a28f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,6 +68,9 @@ importers: ioredis: specifier: ^5.4.1 version: 5.4.1 + joi: + specifier: ^17.13.3 + version: 17.13.3 keygrip: specifier: ^1.1.0 version: 1.1.0 @@ -114,6 +117,9 @@ importers: specifier: ^2.0.1 version: 2.0.1 devDependencies: + '@eslint/js': + specifier: ^9.8.0 + version: 9.8.0 '@nestjs/cli': specifier: ^10.4.2 version: 10.4.2 @@ -123,9 +129,15 @@ importers: '@nestjs/testing': specifier: ^10.3.10 version: 10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.3.10(@nestjs/common@10.3.10(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.3.10)) + '@trivago/prettier-plugin-sort-imports': + specifier: ^4.3.0 + version: 4.3.0(prettier@3.3.3) '@types/cookie-parser': specifier: ^1.4.7 version: 1.4.7 + '@types/eslint__js': + specifier: ^8.42.3 + version: 8.42.3 '@types/express': specifier: ^4.17.21 version: 4.17.21 @@ -195,6 +207,9 @@ importers: typescript: specifier: ^5.5.2 version: 5.5.2 + typescript-eslint: + specifier: ^7.17.0 + version: 7.17.0(eslint@8.57.0)(typescript@5.5.2) packages: @@ -239,6 +254,10 @@ packages: resolution: {integrity: sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg==} engines: {node: '>=6.9.0'} + '@babel/generator@7.17.7': + resolution: {integrity: sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.24.9': resolution: {integrity: sha512-G8v3jRg+z8IwY1jHFxvCNhOPYPterE4XljNgdGTYfSTtzzwjIswIzIaSPSLs3R7yFuqnqNeay5rjICfqVr+/6A==} engines: {node: '>=6.9.0'} @@ -383,10 +402,18 @@ packages: resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.23.2': + resolution: {integrity: sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.24.8': resolution: {integrity: sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ==} engines: {node: '>=6.9.0'} + '@babel/types@7.17.0': + resolution: {integrity: sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==} + engines: {node: '>=6.9.0'} + '@babel/types@7.24.9': resolution: {integrity: sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==} engines: {node: '>=6.9.0'} @@ -420,6 +447,10 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + '@eslint/js@9.8.0': + resolution: {integrity: sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/ajv-compiler@3.6.0': resolution: {integrity: sha512-LwdXQJjmMD+GwLOkP7TVC68qa+pSSogeWWmznRJ/coyTcfe9qA05AHFSe1eZFwK6q+xVRpChnvFUkf1iYaSZsQ==} @@ -432,6 +463,12 @@ packages: '@fastify/merge-json-schemas@0.1.1': resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==} + '@hapi/hoek@9.3.0': + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + '@hapi/topo@5.1.0': + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} @@ -863,6 +900,15 @@ packages: '@samchon/openapi@0.4.2': resolution: {integrity: sha512-lpL+HExpCH65EA8M4L0+bcC0T+0MQCaXjaHdmQ6oHQeSjTgS7ZyoA3l2B6FrkF6XA+mWosQhCmXH5t+VqmoGUQ==} + '@sideway/address@4.1.5': + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + + '@sideway/formula@3.0.1': + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + + '@sideway/pinpoint@2.0.0': + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -883,6 +929,15 @@ packages: resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} + '@trivago/prettier-plugin-sort-imports@4.3.0': + resolution: {integrity: sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ==} + peerDependencies: + '@vue/compiler-sfc': 3.x + prettier: 2.x - 3.x + peerDependenciesMeta: + '@vue/compiler-sfc': + optional: true + '@tsconfig/node10@1.0.11': resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -934,6 +989,9 @@ packages: '@types/eslint@8.56.10': resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} + '@types/eslint__js@8.42.3': + resolution: {integrity: sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==} + '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -1032,6 +1090,17 @@ packages: typescript: optional: true + '@typescript-eslint/eslint-plugin@7.17.0': + resolution: {integrity: sha512-pyiDhEuLM3PuANxH7uNYan1AaFs5XE0zw1hq69JBvGvE7gSuEoQl1ydtEe/XQeoC3GQxLXyOVa5kNOATgM638A==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/parser@7.16.1': resolution: {integrity: sha512-u+1Qx86jfGQ5i4JjK33/FnawZRpsLxRnKzGE6EABZ40KxVT/vWsiZFEBBHjFOljmmV3MBYOHEKi0Jm9hbAOClA==} engines: {node: ^18.18.0 || >=20.0.0} @@ -1042,10 +1111,24 @@ packages: typescript: optional: true + '@typescript-eslint/parser@7.17.0': + resolution: {integrity: sha512-puiYfGeg5Ydop8eusb/Hy1k7QmOU6X3nvsqCgzrB2K4qMavK//21+PzNE8qeECgNOIoertJPUC1SpegHDI515A==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/scope-manager@7.16.1': resolution: {integrity: sha512-nYpyv6ALte18gbMz323RM+vpFpTjfNdyakbf3nsLvF43uF9KeNC289SUEW3QLZ1xPtyINJ1dIsZOuWuSRIWygw==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@7.17.0': + resolution: {integrity: sha512-0P2jTTqyxWp9HiKLu/Vemr2Rg1Xb5B7uHItdVZ6iAenXmPo4SZ86yOPCJwMqpCyaMiEHTNqizHfsbmCFT1x9SA==} + engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/type-utils@7.16.1': resolution: {integrity: sha512-rbu/H2MWXN4SkjIIyWcmYBjlp55VT+1G3duFOIukTNFxr9PI35pLc2ydwAfejCEitCv4uztA07q0QWanOHC7dA==} engines: {node: ^18.18.0 || >=20.0.0} @@ -1056,10 +1139,24 @@ packages: typescript: optional: true + '@typescript-eslint/type-utils@7.17.0': + resolution: {integrity: sha512-XD3aaBt+orgkM/7Cei0XNEm1vwUxQ958AOLALzPlbPqb8C1G8PZK85tND7Jpe69Wualri81PLU+Zc48GVKIMMA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/types@7.16.1': resolution: {integrity: sha512-AQn9XqCzUXd4bAVEsAXM/Izk11Wx2u4H3BAfQVhSfzfDOm/wAON9nP7J5rpkCxts7E5TELmN845xTUCQrD1xIQ==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@7.17.0': + resolution: {integrity: sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A==} + engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/typescript-estree@7.16.1': resolution: {integrity: sha512-0vFPk8tMjj6apaAZ1HlwM8w7jbghC8jc1aRNJG5vN8Ym5miyhTQGMqU++kuBFDNKe9NcPeZ6x0zfSzV8xC1UlQ==} engines: {node: ^18.18.0 || >=20.0.0} @@ -1069,16 +1166,35 @@ packages: typescript: optional: true + '@typescript-eslint/typescript-estree@7.17.0': + resolution: {integrity: sha512-72I3TGq93t2GoSBWI093wmKo0n6/b7O4j9o8U+f65TVD0FS6bI2180X5eGEr8MA8PhKMvYe9myZJquUT2JkCZw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + '@typescript-eslint/utils@7.16.1': resolution: {integrity: sha512-WrFM8nzCowV0he0RlkotGDujx78xudsxnGMBHI88l5J8wEhED6yBwaSLP99ygfrzAjsQvcYQ94quDwI0d7E1fA==} engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: eslint: ^8.56.0 + '@typescript-eslint/utils@7.17.0': + resolution: {integrity: sha512-r+JFlm5NdB+JXc7aWWZ3fKSm1gn0pkswEwIYsrGPdsT2GjsRATAKXiNtp3vgAAO1xZhX8alIOEQnNMl3kbTgJw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + '@typescript-eslint/visitor-keys@7.16.1': resolution: {integrity: sha512-Qlzzx4sE4u3FsHTPQAAQFJFNOuqtuY0LFrZHwQ8IHK705XxBiWOFkfKRWu6niB7hwfgnwIpO4jTC75ozW1PHWg==} engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/visitor-keys@7.17.0': + resolution: {integrity: sha512-RVGC9UhPOCsfCdI9pU++K4nD7to+jTcMIbXTSOcrLqUEW6gF2pU1UUbYJKc9cvcRSK1UDeMJ7pdMxf4bhMpV/A==} + engines: {node: ^18.18.0 || >=20.0.0} + '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} @@ -2462,6 +2578,9 @@ packages: engines: {node: '>=10'} hasBin: true + javascript-natural-sort@0.7.1: + resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==} + jest-changed-files@29.7.0: resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2599,6 +2718,9 @@ packages: resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} hasBin: true + joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + jose@5.6.3: resolution: {integrity: sha512-1Jh//hEEwMhNYPDDLwXHa2ePWgWiFNNUadVmguAAw2IJ6sj9mNxV5tGXJNqlMkJAybF6Lgw1mISDxTePP/187g==} @@ -3603,6 +3725,10 @@ packages: source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -3976,6 +4102,16 @@ packages: typeorm-aurora-data-api-driver: optional: true + typescript-eslint@7.17.0: + resolution: {integrity: sha512-spQxsQvPguduCUfyUvLItvKqK3l8KJ/kqs5Pb/URtzQ5AC53Z6us32St37rpmlt2uESG23lOFpV4UErrmy4dZQ==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + typescript@5.3.3: resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} engines: {node: '>=14.17'} @@ -4259,6 +4395,12 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/generator@7.17.7': + dependencies: + '@babel/types': 7.24.9 + jsesc: 2.5.2 + source-map: 0.5.7 + '@babel/generator@7.24.9': dependencies: '@babel/types': 7.24.9 @@ -4416,6 +4558,21 @@ snapshots: '@babel/parser': 7.24.8 '@babel/types': 7.24.9 + '@babel/traverse@7.23.2': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.9 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-hoist-variables': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/parser': 7.24.8 + '@babel/types': 7.24.9 + debug: 4.3.5 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + '@babel/traverse@7.24.8': dependencies: '@babel/code-frame': 7.24.7 @@ -4431,6 +4588,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/types@7.17.0': + dependencies: + '@babel/helper-validator-identifier': 7.24.7 + to-fast-properties: 2.0.0 + '@babel/types@7.24.9': dependencies: '@babel/helper-string-parser': 7.24.8 @@ -4469,6 +4631,8 @@ snapshots: '@eslint/js@8.57.0': {} + '@eslint/js@9.8.0': {} + '@fastify/ajv-compiler@3.6.0': dependencies: ajv: 8.17.1 @@ -4485,6 +4649,12 @@ snapshots: dependencies: fast-deep-equal: 3.1.3 + '@hapi/hoek@9.3.0': {} + + '@hapi/topo@5.1.0': + dependencies: + '@hapi/hoek': 9.3.0 + '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 @@ -4983,6 +5153,14 @@ snapshots: '@samchon/openapi@0.4.2': {} + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + + '@sideway/formula@3.0.1': {} + + '@sideway/pinpoint@2.0.0': {} + '@sinclair/typebox@0.27.8': {} '@sindresorhus/is@5.6.0': {} @@ -5001,6 +5179,18 @@ snapshots: dependencies: defer-to-connect: 2.0.1 + '@trivago/prettier-plugin-sort-imports@4.3.0(prettier@3.3.3)': + dependencies: + '@babel/generator': 7.17.7 + '@babel/parser': 7.24.8 + '@babel/traverse': 7.23.2 + '@babel/types': 7.17.0 + javascript-natural-sort: 0.7.1 + lodash: 4.17.21 + prettier: 3.3.3 + transitivePeerDependencies: + - supports-color + '@tsconfig/node10@1.0.11': {} '@tsconfig/node12@1.0.11': {} @@ -5068,6 +5258,10 @@ snapshots: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 + '@types/eslint__js@8.42.3': + dependencies: + '@types/eslint': 8.56.10 + '@types/estree@1.0.5': {} '@types/express-serve-static-core@4.19.5': @@ -5195,6 +5389,24 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2)': + dependencies: + '@eslint-community/regexpp': 4.11.0 + '@typescript-eslint/parser': 7.17.0(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/scope-manager': 7.17.0 + '@typescript-eslint/type-utils': 7.17.0(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/utils': 7.17.0(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/visitor-keys': 7.17.0 + eslint: 8.57.0 + graphemer: 1.4.0 + ignore: 5.3.1 + natural-compare: 1.4.0 + ts-api-utils: 1.3.0(typescript@5.5.2) + optionalDependencies: + typescript: 5.5.2 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@7.16.1(eslint@8.57.0)(typescript@5.5.2)': dependencies: '@typescript-eslint/scope-manager': 7.16.1 @@ -5208,11 +5420,29 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.2)': + dependencies: + '@typescript-eslint/scope-manager': 7.17.0 + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.2) + '@typescript-eslint/visitor-keys': 7.17.0 + debug: 4.3.5 + eslint: 8.57.0 + optionalDependencies: + typescript: 5.5.2 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/scope-manager@7.16.1': dependencies: '@typescript-eslint/types': 7.16.1 '@typescript-eslint/visitor-keys': 7.16.1 + '@typescript-eslint/scope-manager@7.17.0': + dependencies: + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/visitor-keys': 7.17.0 + '@typescript-eslint/type-utils@7.16.1(eslint@8.57.0)(typescript@5.5.2)': dependencies: '@typescript-eslint/typescript-estree': 7.16.1(typescript@5.5.2) @@ -5225,8 +5455,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@7.17.0(eslint@8.57.0)(typescript@5.5.2)': + dependencies: + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.2) + '@typescript-eslint/utils': 7.17.0(eslint@8.57.0)(typescript@5.5.2) + debug: 4.3.5 + eslint: 8.57.0 + ts-api-utils: 1.3.0(typescript@5.5.2) + optionalDependencies: + typescript: 5.5.2 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@7.16.1': {} + '@typescript-eslint/types@7.17.0': {} + '@typescript-eslint/typescript-estree@7.16.1(typescript@5.5.2)': dependencies: '@typescript-eslint/types': 7.16.1 @@ -5242,6 +5486,21 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@7.17.0(typescript@5.5.2)': + dependencies: + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/visitor-keys': 7.17.0 + debug: 4.3.5 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.6.2 + ts-api-utils: 1.3.0(typescript@5.5.2) + optionalDependencies: + typescript: 5.5.2 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@7.16.1(eslint@8.57.0)(typescript@5.5.2)': dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) @@ -5253,11 +5512,27 @@ snapshots: - supports-color - typescript + '@typescript-eslint/utils@7.17.0(eslint@8.57.0)(typescript@5.5.2)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0) + '@typescript-eslint/scope-manager': 7.17.0 + '@typescript-eslint/types': 7.17.0 + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.2) + eslint: 8.57.0 + transitivePeerDependencies: + - supports-color + - typescript + '@typescript-eslint/visitor-keys@7.16.1': dependencies: '@typescript-eslint/types': 7.16.1 eslint-visitor-keys: 3.4.3 + '@typescript-eslint/visitor-keys@7.17.0': + dependencies: + '@typescript-eslint/types': 7.17.0 + eslint-visitor-keys: 3.4.3 + '@ungap/structured-clone@1.2.0': {} '@webassemblyjs/ast@1.12.1': @@ -6826,6 +7101,8 @@ snapshots: filelist: 1.0.4 minimatch: 3.1.2 + javascript-natural-sort@0.7.1: {} + jest-changed-files@29.7.0: dependencies: execa: 5.1.1 @@ -7143,6 +7420,14 @@ snapshots: jiti@1.21.6: {} + joi@17.13.3: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + jose@5.6.3: {} js-beautify@1.15.1: @@ -8129,6 +8414,8 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 + source-map@0.5.7: {} + source-map@0.6.1: {} source-map@0.7.4: {} @@ -8465,6 +8752,17 @@ snapshots: transitivePeerDependencies: - supports-color + typescript-eslint@7.17.0(eslint@8.57.0)(typescript@5.5.2): + dependencies: + '@typescript-eslint/eslint-plugin': 7.17.0(@typescript-eslint/parser@7.17.0(eslint@8.57.0)(typescript@5.5.2))(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/parser': 7.17.0(eslint@8.57.0)(typescript@5.5.2) + '@typescript-eslint/utils': 7.17.0(eslint@8.57.0)(typescript@5.5.2) + eslint: 8.57.0 + optionalDependencies: + typescript: 5.5.2 + transitivePeerDependencies: + - supports-color + typescript@5.3.3: {} typescript@5.5.2: {} diff --git a/src/app.module.ts b/src/app.module.ts index 4a3ae3e..9aab226 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -15,6 +15,7 @@ import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path'; import { AuthMiddleware } from './auth/middleware/auth.middleware'; import { ClsModule } from 'nestjs-cls'; +import { validate } from './config/env.validation'; @Module({ imports: [ @@ -37,6 +38,7 @@ import { ClsModule } from 'nestjs-cls'; }, }), ConfigModule.forRoot({ + validate, cache: true, isGlobal: true, load: [config, databaseConfig], diff --git a/src/auth/oidc/core.service.ts b/src/auth/oidc/core.service.ts index 3402a98..eccd22f 100644 --- a/src/auth/oidc/core.service.ts +++ b/src/auth/oidc/core.service.ts @@ -27,7 +27,7 @@ import { import { ConfigService } from '@nestjs/config'; import { DataSource } from 'typeorm'; import { RedisService } from '../../redis/redis.service'; -import { UserService } from 'src/user/user.service'; +import { UserService } from '../../user/service/user.service'; import { Span } from 'nestjs-otel'; import generateId from './helper/nanoid.helper'; import { context, trace } from '@opentelemetry/api'; diff --git a/src/auth/services/auth.service.ts b/src/auth/services/auth.service.ts index f95c6f4..cecfb8c 100644 --- a/src/auth/services/auth.service.ts +++ b/src/auth/services/auth.service.ts @@ -1,6 +1,6 @@ import { BadRequestException, Injectable } from '@nestjs/common'; -import { UserService } from '../../user/user.service'; +import { UserService } from '../../user/service/user.service'; import { RedisService } from '../../redis/redis.service'; import { MailService } from '../../mail/mail.service'; import { getResetKey, PASSWORD_RESET_EXPIRATION } from '../auth.const'; diff --git a/src/config/database.config.ts b/src/config/database.config.ts index 9395c16..ea61688 100644 --- a/src/config/database.config.ts +++ b/src/config/database.config.ts @@ -6,7 +6,7 @@ export default registerAs('database', () => { type: 'mysql', autoLoadEntities: true, host: process.env['DATABASE_HOST'], - port: parseInt(process.env['DATABASE_PORT'] || '0', 10), + port: parseInt(process.env['DATABASE_PORT'] || '3306', 10), database: process.env['DATABASE_NAME'], username: process.env['DATABASE_USERNAME'], password: process.env['DATABASE_PASSWORD'], diff --git a/src/config/env.validation.ts b/src/config/env.validation.ts new file mode 100644 index 0000000..a41f861 --- /dev/null +++ b/src/config/env.validation.ts @@ -0,0 +1,100 @@ +import { plainToInstance } from 'class-transformer'; +import { + IsBoolean, + IsEnum, + IsNumber, + IsOptional, + IsString, + Max, + Min, + validateSync, +} from 'class-validator'; + +enum Environment { + Development = 'development', + Production = 'production', + Test = 'test', +} + +class EnvironmentVariables { + @IsEnum(Environment) + @IsOptional() + NODE_ENV: Environment; + + @IsString() + BASE_URL: string; + + @IsString() + @IsOptional() + APP_NAME: string; + + @IsString() + DATABASE_HOST: string; + + @IsNumber() + @IsOptional() + @Min(0) + @Max(65535) + DATABASE_PORT: number; + + @IsString() + DATABASE_USERNAME: string; + + @IsString() + DATABASE_PASSWORD: string; + + @IsString() + DATABASE_NAME: string; + + @IsBoolean() + @IsOptional() + DATABASE_SYNCHRONIZE: boolean; + + @IsBoolean() + @IsOptional() + DATABASE_DISABLE_MIGRATIONS: boolean; + + @IsBoolean() + @IsOptional() + DATABASE_DEBUG_LOGGING: boolean; + + @IsString() + REDIS_HOST: string; + + @IsNumber() + @IsOptional() + @Min(0) + @Max(65535) + REDIS_PORT: number; + + @IsString() + @IsOptional() + REDIS_PASSWORD: string; + + @IsNumber() + @IsOptional() + @Min(0) + @Max(10) + REDIS_DB: number; + + @IsString() + POSTAL_BASE_URL: string; + + @IsString() + POSTAL_API_KEY: string; + + @IsString() + FROM_EMAIL: string; +} + +export function validate(config: Record) { + const validatedConfig = plainToInstance(EnvironmentVariables, config, { + enableImplicitConversion: true, + }); + const errors = validateSync(validatedConfig, { skipMissingProperties: false }); + + if (errors.length > 0) { + throw new Error(errors.toString()); + } + return validatedConfig; +} diff --git a/src/user/user.service.ts b/src/user/service/user.service.ts similarity index 97% rename from src/user/user.service.ts rename to src/user/service/user.service.ts index a5731a3..2b79e31 100644 --- a/src/user/user.service.ts +++ b/src/user/service/user.service.ts @@ -3,15 +3,15 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { hash, verify } from 'argon2'; -import { User } from '../database/models/user.model'; -import { RedisService } from '../redis/redis.service'; +import { User } from '../../database/models/user.model'; +import { RedisService } from '../../redis/redis.service'; import { Span } from 'nestjs-otel'; import { DISABLED_USER_ERROR, INVALID_CREDENTIALS_ERROR, USER_NOT_FOUND_ERROR, userCacheKey, -} from './user.constant'; +} from '../user.constant'; import { ClsService } from 'nestjs-cls'; @Injectable() diff --git a/src/user/user.module.ts b/src/user/user.module.ts index 6a61618..22215aa 100644 --- a/src/user/user.module.ts +++ b/src/user/user.module.ts @@ -2,7 +2,7 @@ import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { RedisModule } from '../redis/redis.module'; -import { UserService } from './user.service'; +import { UserService } from './service/user.service'; import { DATABASE_ENTITIES } from 'src/database/database.entities'; @Module({