From 7f83a491092f7900288cf2b4d5b2f96d6bfb837a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EA=B7=BC=ED=98=95?= Date: Thu, 11 Apr 2024 13:47:10 +0900 Subject: [PATCH] refactor: Test, Repository, HTTP Exception, Class Validator (#7) (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: 미작성 테스트 코드 삭제 * refactor: Group, Member repository 생성 * refactor: DB 이름 넣어주면서 살짝 변경 * refactor: Group * refactor: Member * refactor: HTTP Exception + Class validator * fix: member.controller.spec.ts * fix: Database query built from user-controlled sources --- package-lock.json | 411 +++++++++++++++++- package.json | 3 + src/auth/auth.controller.spec.ts | 20 - src/auth/auth.service.spec.ts | 18 - src/database/database.providers.ts | 8 +- src/event/event.controller.spec.ts | 20 - src/event/event.service.spec.ts | 18 - src/group/dto/create-group.dto.ts | 9 + src/group/group.controller.spec.ts | 61 ++- src/group/group.controller.ts | 11 +- src/group/group.module.ts | 3 +- src/group/group.repository.ts | 39 ++ src/group/group.service.spec.ts | 24 - src/group/group.service.ts | 43 +- src/group/schemas/group.schema.ts | 21 +- src/http-exception.filter.ts | 24 + src/main.ts | 3 +- src/member/dto/create-member.dto.ts | 4 + src/member/member.controller.spec.ts | 72 ++- src/member/member.controller.ts | 11 +- src/member/member.module.ts | 3 +- src/member/member.repository.ts | 39 ++ src/member/member.service.spec.ts | 24 - src/member/member.service.ts | 43 +- src/member/schemas/member.schema.ts | 4 +- .../transaction.controller.spec.ts | 20 - src/transaction/transaction.service.spec.ts | 18 - src/user/user.controller.spec.ts | 20 - src/user/user.service.spec.ts | 18 - 29 files changed, 704 insertions(+), 308 deletions(-) delete mode 100644 src/auth/auth.controller.spec.ts delete mode 100644 src/auth/auth.service.spec.ts delete mode 100644 src/event/event.controller.spec.ts delete mode 100644 src/event/event.service.spec.ts create mode 100644 src/group/group.repository.ts delete mode 100644 src/group/group.service.spec.ts create mode 100644 src/http-exception.filter.ts create mode 100644 src/member/member.repository.ts delete mode 100644 src/member/member.service.spec.ts delete mode 100644 src/transaction/transaction.controller.spec.ts delete mode 100644 src/transaction/transaction.service.spec.ts delete mode 100644 src/user/user.controller.spec.ts delete mode 100644 src/user/user.service.spec.ts diff --git a/package-lock.json b/package-lock.json index f5f11c0..14c1ddb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,9 @@ "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.3.1", "@typescript-eslint/typescript-estree": "^7.5.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "mongodb-memory-server": "^9.1.8", "mongoose": "^8.3.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" @@ -2373,7 +2376,6 @@ "version": "20.12.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.4.tgz", "integrity": "sha512-E+Fa9z3wSQpzgYQdYmme5X3OTuejnnTx88A6p6vkkJosR3KBz+HpE3kqNm98VE6cfLFcISx7zW7MsJkH6KwbTw==", - "dev": true, "dependencies": { "undici-types": "~5.26.4" } @@ -2443,6 +2445,11 @@ "@types/superagent": "*" } }, + "node_modules/@types/validator": { + "version": "13.11.9", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.11.9.tgz", + "integrity": "sha512-FCTsikRozryfayPuiI46QzH3fnrOoctTjvOYZkho9BTFLCOZ2rgZJHMOVgCOfttjPJcgOx52EpkY0CMfy87MIw==" + }, "node_modules/@types/webidl-conversions": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", @@ -2987,6 +2994,17 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", @@ -3145,12 +3163,25 @@ "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true }, + "node_modules/async-mutex": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.1.tgz", + "integrity": "sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==" + }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -3272,6 +3303,12 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/bare-events": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz", + "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==", + "optional": true + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -3469,6 +3506,14 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -3633,6 +3678,21 @@ "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", "dev": true }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==" + }, + "node_modules/class-validator": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", + "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", + "dependencies": { + "@types/validator": "^13.11.8", + "libphonenumber-js": "^1.10.53", + "validator": "^13.9.0" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -3790,6 +3850,11 @@ "node": ">= 6" } }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" + }, "node_modules/component-emitter": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", @@ -4654,6 +4719,11 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, "node_modules/fast-glob": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", @@ -4703,6 +4773,14 @@ "bser": "2.1.1" } }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", @@ -4780,6 +4858,44 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-cache-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -4873,6 +4989,25 @@ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/foreground-child": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", @@ -5276,6 +5411,18 @@ "node": ">= 0.8" } }, + "node_modules/https-proxy-agent": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", + "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", @@ -5418,6 +5565,23 @@ "node": ">= 0.10" } }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ip-address/node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -6348,6 +6512,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -6462,6 +6631,11 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.10.60", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.10.60.tgz", + "integrity": "sha512-Ctgq2lXUpEJo5j1762NOzl2xo7z7pqmVWYai0p07LvAkQ32tbPv3wb+tcUeHEiXhKU5buM4H9MXsXo6OlM6C2g==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -6817,6 +6991,149 @@ "node": ">=16" } }, + "node_modules/mongodb-memory-server": { + "version": "9.1.8", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-9.1.8.tgz", + "integrity": "sha512-QumPTOlWWWzgW6dtV4bf+ZFdTxenHSSGS4ZT85Vbb+FzPVoOtfcFwYlSfC9IM4e2nw7xvnQZvWtILYseO6HE9Q==", + "hasInstallScript": true, + "dependencies": { + "mongodb-memory-server-core": "9.1.8", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongodb-memory-server-core": { + "version": "9.1.8", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-9.1.8.tgz", + "integrity": "sha512-iCWwaP7De4lm1lRCUKB2ffUYr6GB0I/cj6fK0NV9dgwc9fA3xapHTTT/cPYRNx29M5gmAOSaOpUgjP7i2GZ/LQ==", + "dependencies": { + "async-mutex": "^0.4.0", + "camelcase": "^6.3.0", + "debug": "^4.3.4", + "find-cache-dir": "^3.3.2", + "follow-redirects": "^1.15.6", + "https-proxy-agent": "^7.0.2", + "mongodb": "^5.9.1", + "new-find-package-json": "^2.0.0", + "semver": "^7.5.4", + "tar-stream": "^3.0.0", + "tslib": "^2.6.2", + "yauzl": "^2.10.0" + }, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/@types/whatwg-url": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-8.2.2.tgz", + "integrity": "sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA==", + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/bson": { + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/bson/-/bson-5.5.1.tgz", + "integrity": "sha512-ix0EwukN2EpC0SRWIj/7B5+A6uQMQy6KMREI9qQqvgpkV2frH63T0UDVd1SYedL6dNCmDBYB3QtXi4ISk9YT+g==", + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/mongodb": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.9.2.tgz", + "integrity": "sha512-H60HecKO4Bc+7dhOv4sJlgvenK4fQNqqUIlXxZYQNbfEWSALGAwGoyJd/0Qwk4TttFXUOHJ2ZJQe/52ScaUwtQ==", + "dependencies": { + "bson": "^5.5.0", + "mongodb-connection-string-url": "^2.6.0", + "socks": "^2.7.1" + }, + "engines": { + "node": ">=14.20.1" + }, + "optionalDependencies": { + "@mongodb-js/saslprep": "^1.1.0" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.0.0", + "kerberos": "^1.0.0 || ^2.0.0", + "mongodb-client-encryption": ">=2.3.0 <3", + "snappy": "^7.2.2" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "node_modules/mongodb-memory-server-core/node_modules/mongodb-connection-string-url": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-2.6.0.tgz", + "integrity": "sha512-WvTZlI9ab0QYtTYnuMLgobULWhokRjtC7db9LtcVfJ+Hsnyr5eo6ZtNAt3Ly24XZScGMelOcGtm7lSn0332tPQ==", + "dependencies": { + "@types/whatwg-url": "^8.2.1", + "whatwg-url": "^11.0.0" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/mongoose": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.3.0.tgz", @@ -6910,6 +7227,17 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true }, + "node_modules/new-find-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/new-find-package-json/-/new-find-package-json-2.0.0.tgz", + "integrity": "sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12.22.0" + } + }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", @@ -7111,7 +7439,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, "engines": { "node": ">=6" } @@ -7158,7 +7485,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -7225,6 +7551,11 @@ "node": ">=8" } }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -7256,7 +7587,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, "dependencies": { "find-up": "^4.0.0" }, @@ -7268,7 +7598,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -7281,7 +7610,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -7293,7 +7621,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, "dependencies": { "p-try": "^2.0.0" }, @@ -7308,7 +7635,6 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -7474,6 +7800,11 @@ } ] }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -8097,6 +8428,28 @@ "node": ">=8" } }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -8176,6 +8529,18 @@ "node": ">=10.0.0" } }, + "node_modules/streamx": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", + "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -8394,6 +8759,16 @@ "node": ">=6" } }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/terser": { "version": "5.30.3", "resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz", @@ -8844,8 +9219,7 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/universalify": { "version": "2.0.1", @@ -8948,6 +9322,14 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.11.0.tgz", + "integrity": "sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -9213,6 +9595,15 @@ "node": ">=12" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index 0d60ba4..8ea8e8a 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,9 @@ "@nestjs/platform-express": "^10.0.0", "@nestjs/swagger": "^7.3.1", "@typescript-eslint/typescript-estree": "^7.5.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.1", + "mongodb-memory-server": "^9.1.8", "mongoose": "^8.3.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1" diff --git a/src/auth/auth.controller.spec.ts b/src/auth/auth.controller.spec.ts deleted file mode 100644 index 58dee31..0000000 --- a/src/auth/auth.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AuthController } from './auth.controller'; -import { AuthService } from './auth.service'; - -describe('AuthController', () => { - let controller: AuthController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [AuthController], - providers: [AuthService], - }).compile(); - - controller = module.get(AuthController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/auth/auth.service.spec.ts b/src/auth/auth.service.spec.ts deleted file mode 100644 index 800ab66..0000000 --- a/src/auth/auth.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AuthService } from './auth.service'; - -describe('AuthService', () => { - let service: AuthService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [AuthService], - }).compile(); - - service = module.get(AuthService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/database/database.providers.ts b/src/database/database.providers.ts index 1c78f84..2111b88 100644 --- a/src/database/database.providers.ts +++ b/src/database/database.providers.ts @@ -6,8 +6,12 @@ export const databaseProviders = [ provide: 'MONGODB_CONNECTION', useFactory: (): Promise => { const configService = new ConfigService(); - const uri = configService.get('MONGODB_URI'); - return mongoose.connect(uri); + const uri = configService.get('MONGODB_URI'); + const dbName = configService.get('MONGODB_DB_NAME'); + + return mongoose.connect(uri, { + dbName, + }); }, }, ]; diff --git a/src/event/event.controller.spec.ts b/src/event/event.controller.spec.ts deleted file mode 100644 index 66996db..0000000 --- a/src/event/event.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { EventController } from './event.controller'; -import { EventService } from './event.service'; - -describe('EventController', () => { - let controller: EventController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [EventController], - providers: [EventService], - }).compile(); - - controller = module.get(EventController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/event/event.service.spec.ts b/src/event/event.service.spec.ts deleted file mode 100644 index 78cb816..0000000 --- a/src/event/event.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { EventService } from './event.service'; - -describe('EventService', () => { - let service: EventService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [EventService], - }).compile(); - - service = module.get(EventService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/group/dto/create-group.dto.ts b/src/group/dto/create-group.dto.ts index 0b11ec2..cb764e5 100644 --- a/src/group/dto/create-group.dto.ts +++ b/src/group/dto/create-group.dto.ts @@ -1,25 +1,33 @@ import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty } from 'class-validator'; export class CreateGroupDto { + @IsNotEmpty() @ApiProperty({ + required: true, description: '모임 이름', example: 'SSU', }) readonly name: string; + @IsNotEmpty() @ApiProperty({ + required: true, description: '모임 설명', example: '숭실대학교 학생들의 모임', }) readonly description: string; + @IsNotEmpty() @ApiProperty({ + required: true, description: '모임장 사용자의 ObjectId', example: '60f4b3b3b3b3b3b3b3b3b3b3', }) readonly manager: string; @ApiProperty({ + required: false, description: '부모임장 사용자의 ObjectId와 권한', example: [ { @@ -36,6 +44,7 @@ export class CreateGroupDto { ]; @ApiProperty({ + required: false, description: '모임원 사용자의 ObjectId', example: ['60f4b3b3b3b3b3b3b3b3b3'], }) diff --git a/src/group/group.controller.spec.ts b/src/group/group.controller.spec.ts index cbae502..4c6c9c8 100644 --- a/src/group/group.controller.spec.ts +++ b/src/group/group.controller.spec.ts @@ -1,27 +1,60 @@ import { GroupController } from './group.controller'; import { GroupService } from './group.service'; -import { HydratedDocument, Model } from 'mongoose'; -import { Group } from './interfaces/group.interface'; +import { Test, TestingModule } from '@nestjs/testing'; +import { DatabaseModule } from '../database/database.module'; +import { groupProvider } from './group.provider'; +import { MongoMemoryServer } from 'mongodb-memory-server'; +import * as process from 'process'; +import { Group } from './entities/group.entity'; +import mongoose from 'mongoose'; +import { CreateGroupDto } from './dto/create-group.dto'; +import { GroupRepository } from './group.repository'; describe('GroupController', () => { let groupController: GroupController; let groupService: GroupService; - let groupModel: Model; + let mongoServer: MongoMemoryServer; - beforeEach(async () => { - groupService = new GroupService(groupModel); - groupController = new GroupController(groupService); + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + process.env.MONGODB_URI = mongoServer.getUri(); + + const module: TestingModule = await Test.createTestingModule({ + imports: [DatabaseModule], + controllers: [GroupController], + providers: [GroupService, ...groupProvider, GroupRepository], + }).compile(); + + groupService = module.get(GroupService); + groupController = module.get(GroupController); }); - describe('findAll', () => { - it('should return an array of groups', async () => { - const result: HydratedDocument = [ - { _id: 1, name: 'Group 1' }, - { _id: 2, name: 'Group 2' }, - ]; - jest.spyOn(groupService, 'findAll').mockImplementation(() => result); + describe('create', () => { + it('should return a group', async () => { + const group: CreateGroupDto = { + name: 'Group 1', + description: 'Group 1 description', + manager: 'user1', + subManagers: null, + members: ['user1', 'user2'], + }; + + const result: Group = await groupService.create(group); - expect(await groupController.findAll()).toBe(result); + jest.spyOn(groupService, 'create').mockImplementation(async () => result); + + expect(await groupController.create(group)).toBe(result); + expect(result).toHaveProperty('name', group.name); + expect(result).toHaveProperty('description', group.description); + expect(result).toHaveProperty('manager', group.manager); + expect(result).toHaveProperty('subManagers', group.subManagers); + expect(result).toHaveProperty('members', group.members); }); }); + + afterAll(async () => { + await mongoose.connection.dropDatabase(); + await mongoose.connection.close(); + await mongoServer.stop(); + }); }); diff --git a/src/group/group.controller.ts b/src/group/group.controller.ts index a206545..d8f7bc3 100644 --- a/src/group/group.controller.ts +++ b/src/group/group.controller.ts @@ -6,14 +6,17 @@ import { Param, Patch, Post, + UseFilters, } from '@nestjs/common'; import { GroupService } from './group.service'; import { CreateGroupDto } from './dto/create-group.dto'; import { UpdateGroupDto } from './dto/update-group.dto'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { HttpExceptionFilter } from '../http-exception.filter'; @ApiTags('Group') @Controller('group') +@UseFilters(HttpExceptionFilter) export class GroupController { constructor(private readonly groupService: GroupService) {} @@ -58,8 +61,8 @@ export class GroupController { summary: '모든 모임 삭제', description: '모든 모임을 삭제합니다.', }) - removeAll() { - return this.groupService.removeAll(); + deleteAll() { + return this.groupService.deleteAll(); } @Delete(':id') @@ -67,7 +70,7 @@ export class GroupController { summary: '모임 삭제', description: '특정 모임을 삭제합니다.', }) - remove(@Param('id') id: string) { - return this.groupService.remove(id); + delete(@Param('id') id: string) { + return this.groupService.delete(id); } } diff --git a/src/group/group.module.ts b/src/group/group.module.ts index dbdfe88..71ffec6 100644 --- a/src/group/group.module.ts +++ b/src/group/group.module.ts @@ -3,10 +3,11 @@ import { GroupService } from './group.service'; import { GroupController } from './group.controller'; import { groupProvider } from './group.provider'; import { DatabaseModule } from '../database/database.module'; +import { GroupRepository } from './group.repository'; @Module({ imports: [DatabaseModule], controllers: [GroupController], - providers: [GroupService, ...groupProvider], + providers: [GroupService, ...groupProvider, GroupRepository], }) export class GroupModule {} diff --git a/src/group/group.repository.ts b/src/group/group.repository.ts new file mode 100644 index 0000000..4a4bf9d --- /dev/null +++ b/src/group/group.repository.ts @@ -0,0 +1,39 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Model } from 'mongoose'; +import { Group } from './interfaces/group.interface'; +import { CreateGroupDto } from './dto/create-group.dto'; +import { UpdateGroupDto } from './dto/update-group.dto'; + +@Injectable() +export class GroupRepository { + constructor( + @Inject('GROUP_MODEL') + private readonly groupModel: Model, + ) {} + + create(createGroupDto: CreateGroupDto): Promise { + return this.groupModel.create(createGroupDto); + } + + findAll(): Promise { + return this.groupModel.find().exec(); + } + + findOne(id: string): Promise { + return this.groupModel.findById(id).exec(); + } + + update(id: string, updateGroupDto: UpdateGroupDto): Promise { + return this.groupModel + .findByIdAndUpdate(id, updateGroupDto, { new: true }) + .exec(); + } + + delete(id: string) { + this.groupModel.findByIdAndDelete(id); + } + + deleteAll() { + this.groupModel.deleteMany({}).exec(); + } +} diff --git a/src/group/group.service.spec.ts b/src/group/group.service.spec.ts deleted file mode 100644 index 145d726..0000000 --- a/src/group/group.service.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { GroupService } from './group.service'; -import { HydratedDocument, Model } from 'mongoose'; -import { Group } from './interfaces/group.interface'; - -describe('GroupService', () => { - let groupService: GroupService; - let groupModel: Model; - - beforeEach(async () => { - groupService = new GroupService(groupModel); - }); - - describe('findAll', () => { - it('should return an array of groups', async () => { - const result: HydratedDocument = [ - { _id: 1, name: 'Group 1' }, - { _id: 2, name: 'Group 2' }, - ]; - jest.spyOn(groupService, 'findAll').mockImplementation(() => result); - - expect(await groupService.findAll()).toBe(result); - }); - }); -}); diff --git a/src/group/group.service.ts b/src/group/group.service.ts index a61093f..a05c2f3 100644 --- a/src/group/group.service.ts +++ b/src/group/group.service.ts @@ -1,44 +1,37 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; +import { GroupRepository } from './group.repository'; import { CreateGroupDto } from './dto/create-group.dto'; +import { Group } from './entities/group.entity'; import { UpdateGroupDto } from './dto/update-group.dto'; -import { Model } from 'mongoose'; -import { Group } from './interfaces/group.interface'; @Injectable() export class GroupService { - constructor( - @Inject('GROUP_MODEL') - private readonly groupModel: Model, - ) {} + constructor(private readonly groupRepository: GroupRepository) {} - create(createGroupDto: CreateGroupDto) { - return new this.groupModel(createGroupDto).save(); + async create(createGroupDto: CreateGroupDto): Promise { + return (await this.groupRepository.create(createGroupDto)) as Group; } async findAll(): Promise { - return this.groupModel.find().exec(); - } - - removeAll() { - return this.groupModel.deleteMany({}); + return (await this.groupRepository.findAll()) as Group[]; } async findOne(id: string): Promise { - return this.groupModel.findById(id).exec(); + return (await this.groupRepository.findOne(id)) as Group; } async update(id: string, updateGroupDto: UpdateGroupDto): Promise { - try { - const group = await this.groupModel.findById(id).exec(); - group.name = updateGroupDto.name; - group.description = updateGroupDto.description; - return group.save(); - } catch (e) { - return null; - } + return (await this.groupRepository.update(id, { + name: updateGroupDto.name, + description: updateGroupDto.description, + } as UpdateGroupDto)) as Group; + } + + async delete(id: string): Promise { + return this.groupRepository.delete(id); } - async remove(id: string): Promise { - return this.groupModel.findByIdAndDelete(id); + async deleteAll(): Promise { + return this.groupRepository.deleteAll(); } } diff --git a/src/group/schemas/group.schema.ts b/src/group/schemas/group.schema.ts index f35f831..12de0ba 100644 --- a/src/group/schemas/group.schema.ts +++ b/src/group/schemas/group.schema.ts @@ -1,14 +1,17 @@ -import * as mongoose from 'mongoose'; +import { Schema } from 'mongoose'; -export const GroupSchema = new mongoose.Schema({ +export const GroupSchema = new Schema({ name: String, description: String, manager: String, - subManagers: [ - { - user: String, - authorities: [String], - }, - ], - members: [String], + subManagers: { + type: [ + { + user: String, + authorities: [String], + }, + ], + default: [], + }, + members: { type: [String], default: [] }, }); diff --git a/src/http-exception.filter.ts b/src/http-exception.filter.ts new file mode 100644 index 0000000..61c4a8b --- /dev/null +++ b/src/http-exception.filter.ts @@ -0,0 +1,24 @@ +import { + ArgumentsHost, + Catch, + ExceptionFilter, + HttpException, +} from '@nestjs/common'; +import { Request, Response } from 'express'; + +@Catch(HttpException) +export class HttpExceptionFilter implements ExceptionFilter { + catch(exception: HttpException, host: ArgumentsHost) { + const ctx = host.switchToHttp(); + const response = ctx.getResponse(); + const request = ctx.getRequest(); + const status = exception.getStatus(); + const message = exception.getResponse() as object; + + response.status(status).json({ + ...message, + timestamp: new Date().toISOString(), + path: request.url, + }); + } +} diff --git a/src/main.ts b/src/main.ts index 156e262..2a46ca4 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,11 +2,12 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { configSwagger } from './config.swagger'; import * as process from 'process'; +import { ValidationPipe } from '@nestjs/common'; async function bootstrap() { const app = await NestFactory.create(AppModule); configSwagger(app); - + app.useGlobalPipes(new ValidationPipe()); app.enableCors(); await app.listen(process.env.SERVER_PORT); } diff --git a/src/member/dto/create-member.dto.ts b/src/member/dto/create-member.dto.ts index d69811d..ad76161 100644 --- a/src/member/dto/create-member.dto.ts +++ b/src/member/dto/create-member.dto.ts @@ -1,12 +1,16 @@ import { ApiProperty } from '@nestjs/swagger'; +import { IsNotEmpty, IsPhoneNumber, IsString } from 'class-validator'; export class CreateMemberDto { + @IsNotEmpty() + @IsString() @ApiProperty({ description: '회원 이름', example: '박하은', }) readonly name: string; + @IsPhoneNumber('KR') @ApiProperty({ description: '회원 연락처', example: '010-1234-5678', diff --git a/src/member/member.controller.spec.ts b/src/member/member.controller.spec.ts index cd49dd0..9a3ab1b 100644 --- a/src/member/member.controller.spec.ts +++ b/src/member/member.controller.spec.ts @@ -1,27 +1,79 @@ import { MemberController } from './member.controller'; import { MemberService } from './member.service'; -import { Member } from './interfaces/member.interface'; -import { HydratedDocument, Model } from 'mongoose'; +import mongoose from 'mongoose'; +import { Test, TestingModule } from '@nestjs/testing'; +import { DatabaseModule } from '../database/database.module'; +import { MongoMemoryServer } from 'mongodb-memory-server'; +import * as process from 'process'; +import { memberProvider } from './member.provider'; +import { Member } from './entities/member.entity'; +import { MemberRepository } from './member.repository'; describe('MemberController', () => { let memberController: MemberController; let memberService: MemberService; - let memberModel: Model; + let mongoServer: MongoMemoryServer; - beforeEach(async () => { - memberService = new MemberService(memberModel); - memberController = new MemberController(memberService); + beforeAll(async () => { + mongoServer = await MongoMemoryServer.create(); + process.env.MONGODB_URI = mongoServer.getUri(); + + const module: TestingModule = await Test.createTestingModule({ + imports: [DatabaseModule], + controllers: [MemberController], + providers: [MemberService, ...memberProvider, MemberRepository], + }).compile(); + + memberService = module.get(MemberService); + memberController = module.get(MemberController); + }); + + describe('create', () => { + it('should return a member', async () => { + const member = { + name: 'Member 1', + phoneNumber: '010-1234-5678', + }; + + const result: Member = await memberService.create(member); + + jest + .spyOn(memberService, 'create') + .mockImplementation(async () => result); + + expect(await memberController.create(member)).toBe(result); + }); }); describe('findAll', () => { it('should return an array of members', async () => { - const result: HydratedDocument = [ - { _id: 1, name: '테스트1' }, - { _id: 2, name: '테스트2' }, + const testMembers = [ + { + name: 'Member 1', + phoneNumber: '010-1234-5678', + }, + { + name: 'Member 2', + phoneNumber: '010-2345-6789', + }, ]; - jest.spyOn(memberService, 'findAll').mockImplementation(() => result); + + const result: Member[] = []; + for (const member of testMembers) { + result.push(await memberService.create(member)); + } + + jest + .spyOn(memberService, 'findAll') + .mockImplementation(async () => result); expect(await memberController.findAll()).toBe(result); }); }); + + afterAll(async () => { + await mongoose.connection.dropDatabase(); + await mongoose.connection.close(); + await mongoServer.stop(); + }); }); diff --git a/src/member/member.controller.ts b/src/member/member.controller.ts index 0f752c8..dc8795c 100644 --- a/src/member/member.controller.ts +++ b/src/member/member.controller.ts @@ -6,14 +6,17 @@ import { Param, Patch, Post, + UseFilters, } from '@nestjs/common'; import { MemberService } from './member.service'; import { CreateMemberDto } from './dto/create-member.dto'; import { UpdateMemberDto } from './dto/update-member.dto'; import { ApiOperation, ApiTags } from '@nestjs/swagger'; +import { HttpExceptionFilter } from '../http-exception.filter'; @ApiTags('Member') @Controller('member') +@UseFilters(HttpExceptionFilter) export class MemberController { constructor(private readonly memberService: MemberService) {} @@ -58,8 +61,8 @@ export class MemberController { summary: '모든 회원 삭제', description: '모든 회원을 삭제합니다.', }) - removeAll() { - return this.memberService.removeAll(); + deleteAll() { + return this.memberService.deleteAll(); } @Delete(':id') @@ -67,7 +70,7 @@ export class MemberController { summary: '회원 삭제', description: '특정 회원을 삭제합니다.', }) - remove(@Param('id') id: string) { - return this.memberService.remove(id); + delete(@Param('id') id: string) { + return this.memberService.delete(id); } } diff --git a/src/member/member.module.ts b/src/member/member.module.ts index 812f9a1..0d3774f 100644 --- a/src/member/member.module.ts +++ b/src/member/member.module.ts @@ -3,10 +3,11 @@ import { MemberService } from './member.service'; import { MemberController } from './member.controller'; import { DatabaseModule } from '../database/database.module'; import { memberProvider } from './member.provider'; +import { MemberRepository } from './member.repository'; @Module({ imports: [DatabaseModule], controllers: [MemberController], - providers: [MemberService, ...memberProvider], + providers: [MemberService, ...memberProvider, MemberRepository], }) export class MemberModule {} diff --git a/src/member/member.repository.ts b/src/member/member.repository.ts new file mode 100644 index 0000000..ecda8d5 --- /dev/null +++ b/src/member/member.repository.ts @@ -0,0 +1,39 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { Member } from './interfaces/member.interface'; +import { Model } from 'mongoose'; +import { CreateMemberDto } from './dto/create-member.dto'; +import { UpdateMemberDto } from './dto/update-member.dto'; + +@Injectable() +export class MemberRepository { + constructor( + @Inject('MEMBER_MODEL') + private readonly memberModel: Model, + ) {} + + create(createMemberDto: CreateMemberDto): Promise { + return this.memberModel.create(createMemberDto); + } + + findAll(): Promise { + return this.memberModel.find().exec(); + } + + findOne(id: string): Promise { + return this.memberModel.findById(id).exec(); + } + + update(id: string, updateMemberDto: UpdateMemberDto): Promise { + return this.memberModel + .findByIdAndUpdate(id, updateMemberDto, { new: true }) + .exec(); + } + + delete(id: string) { + this.memberModel.findByIdAndDelete(id); + } + + deleteAll() { + this.memberModel.deleteMany({}).exec(); + } +} diff --git a/src/member/member.service.spec.ts b/src/member/member.service.spec.ts deleted file mode 100644 index 629a31f..0000000 --- a/src/member/member.service.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { MemberService } from './member.service'; -import { Member } from './interfaces/member.interface'; -import { HydratedDocument, Model } from 'mongoose'; - -describe('MemberService', () => { - let memberService: MemberService; - let memberModel: Model; - - beforeEach(async () => { - memberService = new MemberService(memberModel); - }); - - describe('findAll', () => { - it('should return an array of members', async () => { - const result: HydratedDocument = [ - { _id: 1, name: '테스트1' }, - { _id: 2, name: '테스트2' }, - ]; - jest.spyOn(memberService, 'findAll').mockImplementation(() => result); - - expect(await memberService.findAll()).toBe(result); - }); - }); -}); diff --git a/src/member/member.service.ts b/src/member/member.service.ts index 1fd0c87..7d08965 100644 --- a/src/member/member.service.ts +++ b/src/member/member.service.ts @@ -1,44 +1,37 @@ -import { Inject, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; +import { MemberRepository } from './member.repository'; +import { Member } from './entities/member.entity'; import { CreateMemberDto } from './dto/create-member.dto'; import { UpdateMemberDto } from './dto/update-member.dto'; -import { Model } from 'mongoose'; -import { Member } from './interfaces/member.interface'; @Injectable() export class MemberService { - constructor( - @Inject('MEMBER_MODEL') - private readonly memberModel: Model, - ) {} + constructor(private readonly memberRepository: MemberRepository) {} - create(createMemberDto: CreateMemberDto) { - return new this.memberModel(createMemberDto).save(); + async create(createMemberDto: CreateMemberDto): Promise { + return (await this.memberRepository.create(createMemberDto)) as Member; } async findAll(): Promise { - return this.memberModel.find().exec(); - } - - removeAll() { - return this.memberModel.deleteMany({}); + return (await this.memberRepository.findAll()) as Member[]; } async findOne(id: string): Promise { - return this.memberModel.findById(id).exec(); + return (await this.memberRepository.findOne(id)) as Member; } async update(id: string, updateMemberDto: UpdateMemberDto): Promise { - try { - const member = await this.memberModel.findById(id).exec(); - member.name = updateMemberDto.name; - member.phoneNumber = updateMemberDto.phoneNumber; - return member.save(); - } catch (e) { - return null; - } + return (await this.memberRepository.update(id, { + name: updateMemberDto.name, + phoneNumber: updateMemberDto.phoneNumber, + } as UpdateMemberDto)) as Member; + } + + async delete(id: string): Promise { + return this.memberRepository.delete(id); } - async remove(id: string) { - return this.memberModel.findByIdAndDelete(id); + async deleteAll(): Promise { + return this.memberRepository.deleteAll(); } } diff --git a/src/member/schemas/member.schema.ts b/src/member/schemas/member.schema.ts index 9cdfafa..aa378a0 100644 --- a/src/member/schemas/member.schema.ts +++ b/src/member/schemas/member.schema.ts @@ -1,6 +1,6 @@ -import * as mongoose from 'mongoose'; +import { Schema } from 'mongoose'; -export const MemberSchema = new mongoose.Schema({ +export const MemberSchema = new Schema({ name: String, phoneNumber: String, }); diff --git a/src/transaction/transaction.controller.spec.ts b/src/transaction/transaction.controller.spec.ts deleted file mode 100644 index 666b002..0000000 --- a/src/transaction/transaction.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { TransactionController } from './transaction.controller'; -import { TransactionService } from './transaction.service'; - -describe('TransactionController', () => { - let controller: TransactionController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [TransactionController], - providers: [TransactionService], - }).compile(); - - controller = module.get(TransactionController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/transaction/transaction.service.spec.ts b/src/transaction/transaction.service.spec.ts deleted file mode 100644 index 8ac5dfc..0000000 --- a/src/transaction/transaction.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { TransactionService } from './transaction.service'; - -describe('TransactionService', () => { - let service: TransactionService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [TransactionService], - }).compile(); - - service = module.get(TransactionService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/user/user.controller.spec.ts b/src/user/user.controller.spec.ts deleted file mode 100644 index 1f38440..0000000 --- a/src/user/user.controller.spec.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UserController } from './user.controller'; -import { UserService } from './user.service'; - -describe('UserController', () => { - let controller: UserController; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - controllers: [UserController], - providers: [UserService], - }).compile(); - - controller = module.get(UserController); - }); - - it('should be defined', () => { - expect(controller).toBeDefined(); - }); -}); diff --git a/src/user/user.service.spec.ts b/src/user/user.service.spec.ts deleted file mode 100644 index 873de8a..0000000 --- a/src/user/user.service.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { UserService } from './user.service'; - -describe('UserService', () => { - let service: UserService; - - beforeEach(async () => { - const module: TestingModule = await Test.createTestingModule({ - providers: [UserService], - }).compile(); - - service = module.get(UserService); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -});