diff --git a/package-lock.json b/package-lock.json index 6da1fd9..994e303 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "eslint-plugin-n": "^16.1.0", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", + "mongodb-memory-server": "^9.1.6", "nodemon": "^3.0.1", "prettier": "^3.0.3", "ts-node": "^10.9.1", @@ -1881,6 +1882,15 @@ "node": "*" } }, + "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==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/async-retry": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", @@ -1902,12 +1912,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "dev": true + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "devOptional": true }, + "node_modules/bare-events": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.0.tgz", + "integrity": "sha512-Yyyqff4PIFfSuthCZqLlPISTWHmnQxoPuAvkmgzsJEmG3CesdIv6Xweayl0JkCZJSB2yYIdJyEz97tpxNhgjbg==", + "dev": true, + "optional": true + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -2055,6 +2078,15 @@ "node": ">=16.20.1" } }, + "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==", + "dev": true, + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -2122,6 +2154,18 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/catharsis": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", @@ -2251,6 +2295,12 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "devOptional": true }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -3434,6 +3484,12 @@ "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==", + "dev": true + }, "node_modules/fast-glob": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", @@ -3522,6 +3578,15 @@ "node": ">=0.8.0" } }, + "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==", + "dev": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -3576,6 +3641,23 @@ "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==", + "dev": true, + "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-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3633,6 +3715,26 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", + "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -4265,6 +4367,19 @@ "node": ">= 0.4" } }, + "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==", + "devOptional": true, + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -4645,6 +4760,12 @@ "xmlcreate": "^2.0.4" } }, + "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==", + "devOptional": true + }, "node_modules/jsdoc": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", @@ -4976,6 +5097,30 @@ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", "dev": true }, + "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==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-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==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -5249,6 +5394,172 @@ "node": ">=16" } }, + "node_modules/mongodb-memory-server": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/mongodb-memory-server/-/mongodb-memory-server-9.1.6.tgz", + "integrity": "sha512-gzcpgGYlPhuKmria37W+bvYy6W+OkX2UVG7MoP41OWFvQv2Hn7A+fLXkV+lsMmhog1lMQprdV6AR+gixgheLaw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "mongodb-memory-server-core": "9.1.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongodb-memory-server-core": { + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/mongodb-memory-server-core/-/mongodb-memory-server-core-9.1.6.tgz", + "integrity": "sha512-3H/dq5II+XcSbK80hicMw4zFlDxcpjt4oWJq76RlOVuLoaf3AFqVheR6Vqx9ymlIqER4Jni58FMCIIRbesia1A==", + "dev": true, + "dependencies": { + "async-mutex": "^0.4.0", + "camelcase": "^6.3.0", + "debug": "^4.3.4", + "find-cache-dir": "^3.3.2", + "follow-redirects": "^1.15.3", + "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==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/webidl-conversions": "*" + } + }, + "node_modules/mongodb-memory-server-core/node_modules/agent-base": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", + "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=14.20.1" + } + }, + "node_modules/mongodb-memory-server-core/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==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/mongoose": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.1.1.tgz", @@ -5370,6 +5681,18 @@ "node": ">= 0.6" } }, + "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==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">=12.22.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -5698,6 +6021,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "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" + } + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5780,6 +6112,12 @@ "node": "*" } }, + "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==", + "dev": true + }, "node_modules/picocolors": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", @@ -5798,6 +6136,70 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-dir": { + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "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" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "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" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "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" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pkg-types": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", @@ -6068,6 +6470,12 @@ } ] }, + "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==", + "dev": true + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -6541,6 +6949,30 @@ "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==", + "devOptional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", + "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==", + "devOptional": true, + "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.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6567,6 +6999,12 @@ "memory-pager": "^1.0.2" } }, + "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==", + "devOptional": true + }, "node_modules/stackback": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", @@ -6602,6 +7040,19 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "optional": true }, + "node_modules/streamx": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", + "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", + "dev": true, + "dependencies": { + "fast-fifo": "^1.1.0", + "queue-tick": "^1.0.1" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -6767,6 +7218,17 @@ "url": "https://opencollective.com/unts" } }, + "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==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, "node_modules/teeny-request": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.3.tgz", @@ -7667,6 +8129,16 @@ "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==", + "dev": true, + "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 27a23b8..f62bbe6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "nodemon src/index.ts", "build": "tsc", - "test": "vitest", + "test": "vitest run", "lint": "eslint --ext .ts . --fix", "typecheck": "tsc --noEmit" }, @@ -35,6 +35,7 @@ "eslint-plugin-n": "^16.1.0", "eslint-plugin-prettier": "^5.0.0", "eslint-plugin-promise": "^6.1.1", + "mongodb-memory-server": "^9.1.6", "nodemon": "^3.0.1", "prettier": "^3.0.3", "ts-node": "^10.9.1", diff --git a/src/controllers/.gitkeep b/src/controllers/.gitkeep deleted file mode 100644 index 380ec71..0000000 --- a/src/controllers/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -This is just here so the folder is tracked by git \ No newline at end of file diff --git a/src/controllers/activities.controller.ts b/src/controllers/activities.controller.ts new file mode 100644 index 0000000..8423bbd --- /dev/null +++ b/src/controllers/activities.controller.ts @@ -0,0 +1,148 @@ +import { + ActivitiesModel, + type ActivitiesType, +} from "../models/activities.model"; +import { ResumeModel } from "../models/resume.model"; +import mongoose from "mongoose"; +import { HttpError, HttpStatus, checkMongooseErrors } from "../utils/errors"; +import { checkDuplicateItemName } from "../utils/checkDuplicates"; + +export const createActivity = async (activitiesFields: ActivitiesType) => { + try { + if (await checkDuplicateItemName(activitiesFields.itemName)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const newActivity = new ActivitiesModel(activitiesFields); + await newActivity.save(); + return newActivity; + } catch (err: unknown) { + if (err instanceof HttpError) { + throw err; + } + + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Activity creation failed", + { cause: err }, + ); + } +}; + +export const getAllActivities = async (user: string) => { + try { + const activities = await ActivitiesModel.find({ user: user }); + return activities; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Activities retrieval failed", + { cause: err }, + ); + } +}; + +export const getActivityById = async (user: string, activityId: string) => { + try { + const activity = await ActivitiesModel.findOne({ + user: user, + _id: activityId, + }); + return activity; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Activity retrieval failed", + { cause: err }, + ); + } +}; + +export const updateActivity = async ( + user: string, + activityId: string, + activitiesFields: ActivitiesType, +) => { + try { + if (!activityId) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Missing activity ID for update", + ); + } + + if (await checkDuplicateItemName(activitiesFields.itemName, activityId)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const updatedActivity = await ActivitiesModel.findOneAndUpdate( + { _id: activityId, user: user }, // Query to match the document by _id and user + { $set: activitiesFields }, // Update operation + { new: true, runValidators: true }, // Options: return the updated document and run schema validators + ); + return updatedActivity; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Activity update failed", + { cause: err }, + ); + } +}; + +export const deleteActivity = async (user: string, activityId: string) => { + try { + await ResumeModel.updateMany( + { itemIds: new mongoose.Types.ObjectId(activityId) }, + { $pull: { itemIds: new mongoose.Types.ObjectId(activityId) } } + ); + + const deletedActivity = await ActivitiesModel.findOneAndDelete({ + _id: activityId, + user: user, + }); + if (!deletedActivity) { + throw new HttpError( + HttpStatus.NOT_FOUND, + "Activity not found or already deleted", + ); + } + return { message: "Activity deleted successfully" }; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Activity deletion failed", + { cause: err }, + ); + } +}; diff --git a/src/controllers/education.controller.ts b/src/controllers/education.controller.ts new file mode 100644 index 0000000..93653f5 --- /dev/null +++ b/src/controllers/education.controller.ts @@ -0,0 +1,145 @@ +import { EducationModel, type EducationType } from "../models/education.model"; +import { ResumeModel } from "../models/resume.model"; +import mongoose from "mongoose"; +import { HttpError, HttpStatus, checkMongooseErrors } from "../utils/errors"; +import { checkDuplicateItemName } from "../utils/checkDuplicates"; + +export const createEducation = async (educationFields: EducationType) => { + try { + if (await checkDuplicateItemName(educationFields.itemName)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const newEducation = new EducationModel(educationFields); + await newEducation.save(); + return newEducation; + } catch (err: unknown) { + if (err instanceof HttpError) { + throw err; + } + + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Education creation failed", + { cause: err }, + ); + } +}; + +export const getAllEducation = async (user: string) => { + try { + const education = await EducationModel.find({ user: user }); + return education; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Education retrieval failed", + { cause: err }, + ); + } +}; + +export const getEducationById = async (user: string, educationId: string) => { + try { + const education = await EducationModel.findOne({ + user: user, + _id: educationId, + }); + return education; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Education retrieval failed", + { cause: err }, + ); + } +}; + +export const updateEducation = async ( + user: string, + educationId: string, + educationFields: EducationType, +) => { + try { + if (!educationId) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Missing education ID for update", + ); + } + + if (await checkDuplicateItemName(educationFields.itemName, educationId)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const updatedEducation = await EducationModel.findOneAndUpdate( + { _id: educationId, user: user }, // Query to match the document by _id and user + { $set: educationFields }, // Update operation + { new: true, runValidators: true }, // Options: return the updated document and run schema validators + ); + return updatedEducation; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Education update failed", + { cause: err }, + ); + } +}; + +export const deleteEducation = async (user: string, educationId: string) => { + try { + await ResumeModel.updateMany( + { itemIds: new mongoose.Types.ObjectId(educationId) }, + { $pull: { itemIds: new mongoose.Types.ObjectId(educationId) } } + ); + + const deletedEducation = await EducationModel.findOneAndDelete({ + _id: educationId, + user: user, + }); + if (!deletedEducation) { + throw new HttpError( + HttpStatus.NOT_FOUND, + "Education not found or already deleted", + ); + } + return { message: "Education deleted successfully" }; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Education deletion failed", + { cause: err }, + ); + } +}; diff --git a/src/controllers/experience.controller.ts b/src/controllers/experience.controller.ts new file mode 100644 index 0000000..dd9c8bb --- /dev/null +++ b/src/controllers/experience.controller.ts @@ -0,0 +1,149 @@ +import { + ExperienceModel, + type ExperienceType, + } from "../models/experience.model"; + import { ResumeModel } from "../models/resume.model"; +import mongoose from "mongoose"; + import { HttpError, HttpStatus, checkMongooseErrors } from "../utils/errors"; + import { checkDuplicateItemName } from "../utils/checkDuplicates"; + + export const createExperience = async (experienceFields: ExperienceType) => { + try { + if (await checkDuplicateItemName(experienceFields.itemName)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const newExperience = new ExperienceModel(experienceFields); + await newExperience.save(); + return newExperience; + } catch (err: unknown) { + if (err instanceof HttpError) { + throw err; + } + + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Experience creation failed", + { cause: err }, + ); + } + }; + + export const getAllExperiences = async (user: string) => { + try { + const experience = await ExperienceModel.find({ user: user }); + return experience; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Experience retrieval failed", + { cause: err }, + ); + } + }; + + export const getExperienceById = async (user: string, experienceId: string) => { + try { + const experience = await ExperienceModel.findOne({ + user: user, + _id: experienceId, + }); + return experience; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Experience retrieval failed", + { cause: err }, + ); + } + }; + + export const updateExperience = async ( + user: string, + experienceId: string, + experienceFields: ExperienceType, + ) => { + try { + if (!experienceId) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Missing experience ID for update", + ); + } + + if (await checkDuplicateItemName(experienceFields.itemName, experienceId)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const updatedExperience = await ExperienceModel.findOneAndUpdate( + { _id: experienceId, user: user }, // Query to match the document by _id and user + { $set: experienceFields }, // Update operation + { new: true, runValidators: true }, // Options: return the updated document and run schema validators + ); + return updatedExperience; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Experience update failed", + { cause: err }, + ); + } + }; + + export const deleteExperience = async (user: string, experienceId: string) => { + try { + await ResumeModel.updateMany( + { itemIds: new mongoose.Types.ObjectId(experienceId) }, + { $pull: { itemIds: new mongoose.Types.ObjectId(experienceId) } } + ); + + const deletedExperience = await ExperienceModel.findOneAndDelete({ + _id: experienceId, + user: user, + }); + if (!deletedExperience) { + throw new HttpError( + HttpStatus.NOT_FOUND, + "Experience not found or already deleted", + ); + } + return { message: "Experience deleted successfully" }; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Experience deletion failed", + { cause: err }, + ); + } + }; + \ No newline at end of file diff --git a/src/controllers/folder.controller.ts b/src/controllers/folder.controller.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/controllers/heading.controller.ts b/src/controllers/heading.controller.ts new file mode 100644 index 0000000..da5d4ce --- /dev/null +++ b/src/controllers/heading.controller.ts @@ -0,0 +1,149 @@ +import { + HeadingModel, + type HeadingType, + } from "../models/heading.model"; + import { ResumeModel } from "../models/resume.model"; +import mongoose from "mongoose"; + import { HttpError, HttpStatus, checkMongooseErrors } from "../utils/errors"; + import { checkDuplicateItemName } from "../utils/checkDuplicates"; + + export const createHeading = async (headingFields: HeadingType) => { + try { + if (await checkDuplicateItemName(headingFields.itemName)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const newHeading = new HeadingModel(headingFields); + await newHeading.save(); + return newHeading; + } catch (err: unknown) { + if (err instanceof HttpError) { + throw err; + } + + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Heading creation failed", + { cause: err }, + ); + } + }; + + export const getAllHeadings = async (user: string) => { + try { + const heading = await HeadingModel.find({ user: user }); + return heading; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Heading retrieval failed", + { cause: err }, + ); + } + }; + + export const getHeadingById = async (user: string, headingId: string) => { + try { + const heading = await HeadingModel.findOne({ + user: user, + _id: headingId, + }); + return heading; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Heading retrieval failed", + { cause: err }, + ); + } + }; + + export const updateHeading = async ( + user: string, + headingId: string, + headingFields: HeadingType, + ) => { + try { + if (!headingId) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Missing heading ID for update", + ); + } + + if (await checkDuplicateItemName(headingFields.itemName, headingId)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const updatedHeading = await HeadingModel.findOneAndUpdate( + { _id: headingId, user: user }, // Query to match the document by _id and user + { $set: headingFields }, // Update operation + { new: true, runValidators: true }, // Options: return the updated document and run schema validators + ); + return updatedHeading; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Heading update failed", + { cause: err }, + ); + } + }; + + export const deleteHeading = async (user: string, headingId: string) => { + try { + await ResumeModel.updateMany( + { itemIds: new mongoose.Types.ObjectId(headingId) }, + { $pull: { itemIds: new mongoose.Types.ObjectId(headingId) } } + ); + + const deletedHeading = await HeadingModel.findOneAndDelete({ + _id: headingId, + user: user, + }); + if (!deletedHeading) { + throw new HttpError( + HttpStatus.NOT_FOUND, + "Heading not found or already deleted", + ); + } + return { message: "Heading deleted successfully" }; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Heading deletion failed", + { cause: err }, + ); + } + }; + \ No newline at end of file diff --git a/src/controllers/project.controller.ts b/src/controllers/project.controller.ts new file mode 100644 index 0000000..de67f97 --- /dev/null +++ b/src/controllers/project.controller.ts @@ -0,0 +1,149 @@ +import { + ProjectModel, + type ProjectType, + } from "../models/project.model"; + import { ResumeModel } from "../models/resume.model"; +import mongoose from "mongoose"; + import { HttpError, HttpStatus, checkMongooseErrors } from "../utils/errors"; + import { checkDuplicateItemName } from "../utils/checkDuplicates"; + + export const createProject = async (projectFields: ProjectType) => { + try { + if (await checkDuplicateItemName(projectFields.itemName)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const newProject = new ProjectModel(projectFields); + await newProject.save(); + return newProject; + } catch (err: unknown) { + if (err instanceof HttpError) { + throw err; + } + + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Project creation failed", + { cause: err }, + ); + } + }; + + export const getAllProjects = async (user: string) => { + try { + const project = await ProjectModel.find({ user: user }); + return project; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Project retrieval failed", + { cause: err }, + ); + } + }; + + export const getProjectById = async (user: string, projectId: string) => { + try { + const project = await ProjectModel.findOne({ + user: user, + _id: projectId, + }); + return project; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Project retrieval failed", + { cause: err }, + ); + } + }; + + export const updateProject = async ( + user: string, + projectId: string, + projectFields: ProjectType, + ) => { + try { + if (!projectId) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Missing project ID for update", + ); + } + + if (await checkDuplicateItemName(projectFields.itemName, projectId)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const updatedProject = await ProjectModel.findOneAndUpdate( + { _id: projectId, user: user }, // Query to match the document by _id and user + { $set: projectFields }, // Update operation + { new: true, runValidators: true }, // Options: return the updated document and run schema validators + ); + return updatedProject; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Project update failed", + { cause: err }, + ); + } + }; + + export const deleteProject = async (user: string, projectId: string) => { + try { + await ResumeModel.updateMany( + { itemIds: new mongoose.Types.ObjectId(projectId) }, + { $pull: { itemIds: new mongoose.Types.ObjectId(projectId) } } + ); + + const deletedProject = await ProjectModel.findOneAndDelete({ + _id: projectId, + user: user, + }); + if (!deletedProject) { + throw new HttpError( + HttpStatus.NOT_FOUND, + "Project not found or already deleted", + ); + } + return { message: "Project deleted successfully" }; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Project deletion failed", + { cause: err }, + ); + } + }; + \ No newline at end of file diff --git a/src/controllers/resume.controller.ts b/src/controllers/resume.controller.ts new file mode 100644 index 0000000..6ee7f04 --- /dev/null +++ b/src/controllers/resume.controller.ts @@ -0,0 +1,141 @@ +import { ResumeModel, type resumeType } from "../models/resume.model"; +import { HttpError, HttpStatus, checkMongooseErrors } from "../utils/errors"; +import { checkDuplicateItemName } from "../utils/checkDuplicates"; + +export const createResume = async (resumesFields: resumeType) => { + try { + if(await checkDuplicateItemName(resumesFields.itemName)){ + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Duplicate item name", + ) + } + + const newResumes = new ResumeModel(resumesFields); + await newResumes.save(); + return newResumes; + } catch (err: unknown) { + if (err instanceof HttpError) { + throw err; + } + + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Resume creation failed", + { cause: err }, + ); + } +}; + +export const getAllResumes = async (user: string) => { + try { + const resumes = await ResumeModel.find({ user: user }); + return resumes; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Resumes retrieval failed", + { cause: err }, + ); + } +} + +export const getResumeById = async (user: string, resumeId: string) => { + try { + const resume = await ResumeModel.findOne({ + user: user, + _id: resumeId, + }); + return resume; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Resume retrieval failed", + { cause: err }, + ); + } +}; + +export const updateResume = async ( + user: string, + resumeId: string, + resumesFields: resumeType, +) => { + try { + if (!resumeId) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Missing resume ID for update", + ); + } + + if (await checkDuplicateItemName(resumesFields.itemName, resumeId)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const updatedResume = await ResumeModel.findOneAndUpdate( + { _id: resumeId, user: user }, // Query to match the document by _id and user + { $set: resumesFields }, // Update operation + { new: true, runValidators: true }, // Options: return the updated document and run schema validators + ); + return updatedResume; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Resume update failed", + { cause: err }, + ); + } +}; + +export const deleteResume = async (user: string, resumeId: string) => { + try { + const deletedResume = await ResumeModel.findOneAndDelete({ + _id: resumeId, + user: user, + }); + if (!deletedResume) { + throw new HttpError( + HttpStatus.NOT_FOUND, + "Resume not found or already deleted", + ); + } + return { message: "Resume deleted successfully" }; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Resume deletion failed", + { cause: err }, + ); + } +}; diff --git a/src/controllers/sectionHeading.controller.ts b/src/controllers/sectionHeading.controller.ts new file mode 100644 index 0000000..e2dc68e --- /dev/null +++ b/src/controllers/sectionHeading.controller.ts @@ -0,0 +1,148 @@ +import { SectionHeadingModel, type SectionHeadingType } from "../models/sectionHeading.model"; +import { ResumeModel } from "../models/resume.model"; +import mongoose from "mongoose"; +import { HttpError, HttpStatus, checkMongooseErrors } from "../utils/errors"; +import { checkDuplicateItemName } from "../utils/checkDuplicates"; + +export const createSectionHeading = async (sectionHeadingsFields: SectionHeadingType) => { + try { + if(await checkDuplicateItemName(sectionHeadingsFields.itemName)){ + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Duplicate item name", + ) + } + + const newSectionHeadings = new SectionHeadingModel(sectionHeadingsFields); + await newSectionHeadings.save(); + return newSectionHeadings; + } catch (err: unknown) { + if (err instanceof HttpError) { + throw err; + } + + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "SectionHeading creation failed", + { cause: err }, + ); + } +}; + +export const getAllSectionHeadings = async (user: string) => { + try { + const sectionHeadings = await SectionHeadingModel.find({ user: user }); + return sectionHeadings; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "SectionHeadings retrieval failed", + { cause: err }, + ); + } +} + +export const getSectionHeadingById = async (user: string, sectionHeadingId: string) => { + try { + const sectionHeading = await SectionHeadingModel.findOne({ + user: user, + _id: sectionHeadingId, + }); + return sectionHeading; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "SectionHeading retrieval failed", + { cause: err }, + ); + } +}; + +export const updateSectionHeading = async ( + user: string, + sectionHeadingId: string, + sectionHeadingsFields: SectionHeadingType, +) => { + try { + if (!sectionHeadingId) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Missing sectionHeading ID for update", + ); + } + + if (await checkDuplicateItemName(sectionHeadingsFields.itemName, sectionHeadingId)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const updatedSectionHeading = await SectionHeadingModel.findOneAndUpdate( + { _id: sectionHeadingId, user: user }, // Query to match the document by _id and user + { $set: sectionHeadingsFields }, // Update operation + { new: true, runValidators: true }, // Options: return the updated document and run schema validators + ); + return updatedSectionHeading; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "SectionHeading update failed", + { cause: err }, + ); + } +}; + +export const deleteSectionHeading = async (user: string, sectionHeadingId: string) => { + try { + await ResumeModel.updateMany( + { itemIds: new mongoose.Types.ObjectId(sectionHeadingId) }, + { $pull: { itemIds: new mongoose.Types.ObjectId(sectionHeadingId) } } + ); + + const deletedSectionHeading = await SectionHeadingModel.findOneAndDelete({ + _id: sectionHeadingId, + user: user, + }); + if (!deletedSectionHeading) { + throw new HttpError( + HttpStatus.NOT_FOUND, + "SectionHeading not found or already deleted", + ); + } + return { message: "SectionHeading deleted successfully" }; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "SectionHeading deletion failed", + { cause: err }, + ); + } +}; diff --git a/src/controllers/skills.controller.ts b/src/controllers/skills.controller.ts new file mode 100644 index 0000000..9a80cd5 --- /dev/null +++ b/src/controllers/skills.controller.ts @@ -0,0 +1,148 @@ +import { SkillsModel, type SkillsType } from "../models/skills.model"; +import { ResumeModel } from "../models/resume.model"; +import mongoose from "mongoose"; +import { HttpError, HttpStatus, checkMongooseErrors } from "../utils/errors"; +import { checkDuplicateItemName } from "../utils/checkDuplicates"; + +export const createSkill = async (skillsFields: SkillsType) => { + try { + if(await checkDuplicateItemName(skillsFields.itemName)){ + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Duplicate item name", + ) + } + + const newSkills = new SkillsModel(skillsFields); + await newSkills.save(); + return newSkills; + } catch (err: unknown) { + if (err instanceof HttpError) { + throw err; + } + + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Skill creation failed", + { cause: err }, + ); + } +}; + +export const getAllSkills = async (user: string) => { + try { + const skills = await SkillsModel.find({ user: user }); + return skills; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Skills retrieval failed", + { cause: err }, + ); + } +} + +export const getSkillById = async (user: string, skillId: string) => { + try { + const skill = await SkillsModel.findOne({ + user: user, + _id: skillId, + }); + return skill; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Skill retrieval failed", + { cause: err }, + ); + } +}; + +export const updateSkill = async ( + user: string, + skillId: string, + skillsFields: SkillsType, +) => { + try { + if (!skillId) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Missing skill ID for update", + ); + } + + if (await checkDuplicateItemName(skillsFields.itemName, skillId)) { + throw new HttpError(HttpStatus.BAD_REQUEST, "Duplicate item name"); + } + + const updatedSkill = await SkillsModel.findOneAndUpdate( + { _id: skillId, user: user }, // Query to match the document by _id and user + { $set: skillsFields }, // Update operation + { new: true, runValidators: true }, // Options: return the updated document and run schema validators + ); + return updatedSkill; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Skill update failed", + { cause: err }, + ); + } +}; + +export const deleteSkill = async (user: string, skillId: string) => { + try { + await ResumeModel.updateMany( + { itemIds: new mongoose.Types.ObjectId(skillId) }, + { $pull: { itemIds: new mongoose.Types.ObjectId(skillId) } } + ); + + const deletedSkill = await SkillsModel.findOneAndDelete({ + _id: skillId, + user: user, + }); + if (!deletedSkill) { + throw new HttpError( + HttpStatus.NOT_FOUND, + "Skill not found or already deleted", + ); + } + return { message: "Skill deleted successfully" }; + } catch (err: unknown) { + //rethrow any errors as HttpErrors + if (err instanceof HttpError) { + throw err; + } + //checks if mongoose threw and will rethrow with appropriate status code and message + checkMongooseErrors(err); + + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Skill deletion failed", + { cause: err }, + ); + } +}; diff --git a/src/index.ts b/src/index.ts index 65cb442..0a5b9d3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ import cors from "cors"; import morgan from "morgan"; import helmet from "helmet"; import { exampleRoute } from "./routers/exampleRoute"; +import { router } from "./routers/root.router"; import { verifyToken } from "./middlewares/verifyToken"; import { notFound, errorHandler } from "./middlewares/errors"; import { connectDb } from "./utils/mongodb"; @@ -24,6 +25,7 @@ app.use(helmet()); * Uses the verifyToken middleware to protect the "/data" route * Use the verifyToken to protect all the routes that require authentication */ +app.use("/api", verifyToken, router); app.use("/example", verifyToken, exampleRoute); // Default route: Unprotected diff --git a/src/models/activities.model.ts b/src/models/activities.model.ts new file mode 100644 index 0000000..36f8144 --- /dev/null +++ b/src/models/activities.model.ts @@ -0,0 +1,27 @@ +import mongoose from "mongoose"; + +const Schema = mongoose.Schema; + +// Interface for Activities document +export interface ActivitiesType extends mongoose.Document { + user: string; + itemName: string; + bullets: string[]; + title: string; + subtitle: string; + year: string; + location: string; +} + +// Activities Schema +const Activities = new Schema({ + user: { type: String, required: true }, + itemName: { type: String, required: true }, + bullets: { type: [String], required: true }, + title: { type: String, required: true }, + subtitle: { type: String, required: true }, + year: { type: String, required: true }, + location: { type: String, required: true }, +}); + +export const ActivitiesModel = mongoose.model("Activities", Activities); \ No newline at end of file diff --git a/src/models/education.model.ts b/src/models/education.model.ts new file mode 100644 index 0000000..1020532 --- /dev/null +++ b/src/models/education.model.ts @@ -0,0 +1,27 @@ +import mongoose from "mongoose"; + +const Schema = mongoose.Schema; + +// Interface for Education document +export interface EducationType extends mongoose.Document { + user: string; + itemName: string; + bullets: string[]; + title: string; + subtitle: string; + location: string; + year: string; +} + +// Education Schema +const Education = new Schema({ + user: { type: String, required: true }, + itemName: { type: String, required: true }, + bullets: { type: [String], required: true }, + title: { type: String, required: true }, + subtitle: { type: String, required: true }, + location: { type: String, required: true }, + year: { type: String, required: true }, +}); + +export const EducationModel = mongoose.model("Education", Education, "education"); diff --git a/src/models/example_event_model.ts b/src/models/example_event_model.ts deleted file mode 100644 index ee38a40..0000000 --- a/src/models/example_event_model.ts +++ /dev/null @@ -1,36 +0,0 @@ -//this is a model from another project with comments added - -//get default import from mongoose library (which is used for interacting w/ mongodb) -import mongoose from "mongoose"; - -//mongoose object for defining structure of all the documents in a mongodb collection -const Schema = mongoose.Schema; - -//typescript type corresponding with the mongoose schema structure -export interface eventType { - name: string; - description: string; - code: string; - date: Date; - programs: string[]; - staff: string[]; - attended_youth?: string[]; - attached_forms?: string[]; -} - -//mongoose schema for an event document -const Event = new Schema({ - name: { type: String, required: true }, - description: { type: String, required: true }, - code: { type: String, required: true }, - date: { type: Date, required: true }, - programs: { type: [String], required: true }, - staff: { type: [String], required: true }, // by Staff.fireID - attended_youth: { type: [String], default: [] }, // by Youth.fireID - attached_forms: { type: [String], default: [] }, // by Note._id -}); - -//this takes the schema and uses it to form a model object. Model objects allow us -//to interact with mongodb, such as creating new documents or querying documents. The event -//model object is exported so that it can be used in other files -export const EventModel = mongoose.model("Event", Event); \ No newline at end of file diff --git a/src/models/experience.model.ts b/src/models/experience.model.ts new file mode 100644 index 0000000..42184c2 --- /dev/null +++ b/src/models/experience.model.ts @@ -0,0 +1,29 @@ +//get default import from mongoose library (which is used for interacting w/ mongodb) +import mongoose from "mongoose"; + +//mongoose object for defining structure of all the documents in a mongodb collection +const Schema = mongoose.Schema; + +//typescript type corresponding with the mongoose schema structure +export interface ExperienceType extends mongoose.Document { + user: string; + itemName: string; + bullets: string[]; + title: string; + subtitle: string; + year: string; + location: string; +} + +//mongoose schema for an event document +const Experience = new Schema({ + user: { type: String, required: true }, + itemName: { type: String, required: true }, + bullets: { type: [String], required: true }, + title: { type: String, required: true }, + subtitle: { type: String, required: true }, + year: { type: String, required: true }, + location: { type: String, required: true }, +}); + +export const ExperienceModel = mongoose.model("Experience", Experience); diff --git a/src/models/folder.model.ts b/src/models/folder.model.ts new file mode 100644 index 0000000..e9414ce --- /dev/null +++ b/src/models/folder.model.ts @@ -0,0 +1,21 @@ +//mongoose object for defining structure of all the documents in a mongodb collection +import mongoose from "mongoose"; + +const Schema = mongoose.Schema; + +//typescript type corresponding with the mongoose schema structure +export interface folderType extends mongoose.Document { + user: string; + name: string; + resumeIds: mongoose.Schema.Types.ObjectId[]; + folderIds: mongoose.Schema.Types.ObjectId[]; +} + +const Folder = new Schema({ + user: { type: String, required: true }, + name: { type: String, required: true }, + resumeIds: { type: [Schema.Types.ObjectId], required: true, ref: 'ResumeModel' }, + folderIds: { type: [Schema.Types.ObjectId], required: true, ref: 'FolderModel' }, +}); + +export const FolderModel = mongoose.model("Folder", Folder); \ No newline at end of file diff --git a/src/models/heading.model.ts b/src/models/heading.model.ts new file mode 100644 index 0000000..56e5f03 --- /dev/null +++ b/src/models/heading.model.ts @@ -0,0 +1,44 @@ +import mongoose from "mongoose"; + +const Schema = mongoose.Schema; + +interface HeadingItem { + item: string; + href: string | null; +} + +// Interface for Contact details document +export interface HeadingType extends mongoose.Document { + user: string; + itemName: string; + name: string; + items: HeadingItem[]; +} + +// Schema for HeadingItem +const headingItemSchema = new Schema( + { + item: { type: String, required: true }, + href: { type: String, default: null }, // Assuming href can be null or a string + }, + { _id: false }, +); // _id is not needed for subdocuments by default unless you want them + +// Schema for HeadingType which includes HeadingItem +const Heading = new Schema({ + user: { type: String, required: true }, + itemName: { type: String, required: true }, + name: { type: String, required: true }, + items: [headingItemSchema], +}); + +// Contact Schema +// const Heading = new Schema({ +// name: { type: String, required: true }, +// phoneNumber: { type: String, required: true }, +// email: { type: String, required: true }, +// linkedIn: { type: String, required: true }, +// websitesGitHub: { type: String, required: true }, +// }); + +export const HeadingModel = mongoose.model("Heading", Heading); diff --git a/src/models/project.model.ts b/src/models/project.model.ts new file mode 100644 index 0000000..1e52305 --- /dev/null +++ b/src/models/project.model.ts @@ -0,0 +1,25 @@ +import mongoose from "mongoose"; + +const Schema = mongoose.Schema; + +// Interface for Project document +export interface ProjectType extends mongoose.Document { + user: string; + itemName: string; + bullets: string[]; + title: string; + technologies?: string; + year: string; +} + +// Project Schema +const Project = new Schema({ + user: { type: String, required: true }, + itemName: { type: String, required: true }, + bullets: { type: [String], required: true }, + title: { type: String, required: true }, + technologies: {type: String, required: false}, + year: { type: String, required: true }, +}); + +export const ProjectModel = mongoose.model("Project", Project); diff --git a/src/models/resume.model.ts b/src/models/resume.model.ts new file mode 100644 index 0000000..6e9e1df --- /dev/null +++ b/src/models/resume.model.ts @@ -0,0 +1,21 @@ +//mongoose object for defining structure of all the documents in a mongodb collection +import mongoose from "mongoose"; + +const Schema = mongoose.Schema; + +//typescript type corresponding with the mongoose schema structure +export interface resumeType extends mongoose.Document { + user: string; + itemName: string; + itemIds: mongoose.Types.ObjectId[]; + templateId: mongoose.Types.ObjectId; +} + +const Resume = new Schema({ + user: { type: String, required: true }, + itemName: { type: String, required: true, unique: true }, + itemIds: { type: [Schema.Types.ObjectId], required: true }, + templateId: { type: Schema.Types.ObjectId, required: true }, +}); + +export const ResumeModel = mongoose.model("Resume", Resume); \ No newline at end of file diff --git a/src/models/sectionHeading.model.ts b/src/models/sectionHeading.model.ts new file mode 100644 index 0000000..c6d8391 --- /dev/null +++ b/src/models/sectionHeading.model.ts @@ -0,0 +1,19 @@ +import mongoose from "mongoose"; + +const Schema = mongoose.Schema; + +// Interface for Section Heading document +export interface SectionHeadingType extends mongoose.Document { + user: string; + itemName: string; + title: string; +} + +// Section Heading Schema +const SectionHeading = new Schema({ + user: { type: String, required: true }, + itemName: { type: String, required: true }, + title: { type: String, required: true }, +}); + +export const SectionHeadingModel = mongoose.model("SectionHeading", SectionHeading); diff --git a/src/models/skills.model.ts b/src/models/skills.model.ts new file mode 100644 index 0000000..32be2a8 --- /dev/null +++ b/src/models/skills.model.ts @@ -0,0 +1,21 @@ +import mongoose from "mongoose"; + +const Schema = mongoose.Schema; + +// Interface for Skills document +export interface SkillsType extends mongoose.Document { + user: string; + itemName: string; + title: string; + description: string; +} + +// Skills Schema +const Skills = new Schema({ + user: { type: String, required: true }, + itemName: { type: String, required: true }, + title: { type: String, required: true }, + description: { type: String, required: true }, +}); + +export const SkillsModel = mongoose.model("Skills", Skills); diff --git a/src/routers/activities.router.ts b/src/routers/activities.router.ts new file mode 100644 index 0000000..74853c5 --- /dev/null +++ b/src/routers/activities.router.ts @@ -0,0 +1,108 @@ +import { Router, type Request, type Response } from "express"; +import { + createActivity, + getAllActivities, + getActivityById, + updateActivity, + deleteActivity, +} from "../controllers/activities.controller"; +import { HttpError, HttpStatus } from "../utils/errors"; +import { type ActivitiesType } from "../models/activities.model"; + +export const activitiesRouter = Router(); + +//Add an activity +//Note that the user field (which is part of activitiesType) in body is automatically populated by verifyToken middleware +activitiesRouter.post( + "/", + async (req: Request, res: Response) => { + try { + const activity = await createActivity(req.body); + res.status(HttpStatus.OK).json(activity); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get all activities +activitiesRouter.get( + "/", + async (req: Request, res: Response) => { + try { + const activities = await getAllActivities(req.body.user); + res.status(HttpStatus.OK).json(activities); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get a single activity by id +activitiesRouter.get( + "/:activityId", + async (req: Request, res: Response) => { + try { + const activity = await getActivityById(req.body.user, req.params.activityId); + res.status(HttpStatus.OK).json(activity); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Update an activity +activitiesRouter.put( + "/:activityId", + async (req: Request, res: Response) => { + try { + const activity = await updateActivity(req.body.user, req.params.activityId, req.body); + res.status(HttpStatus.OK).json(activity); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Delete an activity +activitiesRouter.delete( + "/:activityId", + async (req: Request, res: Response) => { + try { + const activity = await deleteActivity(req.body.user, req.params.activityId); + res.status(HttpStatus.OK).json(activity); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); diff --git a/src/routers/education.router.ts b/src/routers/education.router.ts new file mode 100644 index 0000000..a48aee3 --- /dev/null +++ b/src/routers/education.router.ts @@ -0,0 +1,118 @@ +import { Router, type Request, type Response } from "express"; +import { + createEducation, + getAllEducation, + getEducationById, + updateEducation, + deleteEducation, +} from "../controllers/education.controller"; +import { HttpError, HttpStatus } from "../utils/errors"; +import { type EducationType } from "../models/education.model"; + +export const educationRouter = Router(); + +//Add an education +//Note that the user field (which is part of EducationType) in body is automatically populated by verifyToken middleware +educationRouter.post( + "/", + async (req: Request, res: Response) => { + try { + const education = await createEducation(req.body); + res.status(HttpStatus.OK).json(education); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get all education +educationRouter.get( + "/", + async (req: Request, res: Response) => { + try { + const education = await getAllEducation(req.body.user); + res.status(HttpStatus.OK).json(education); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get a single education by id +educationRouter.get( + "/:educationId", + async (req: Request, res: Response) => { + try { + const education = await getEducationById( + req.body.user, + req.params.educationId, + ); + res.status(HttpStatus.OK).json(education); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Update an education +educationRouter.put( + "/:educationId", + async (req: Request, res: Response) => { + try { + const education = await updateEducation( + req.body.user, + req.params.educationId, + req.body, + ); + res.status(HttpStatus.OK).json(education); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Delete an education +educationRouter.delete( + "/:educationId", + async (req: Request, res: Response) => { + try { + const education = await deleteEducation( + req.body.user, + req.params.educationId, + ); + res.status(HttpStatus.OK).json(education); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); diff --git a/src/routers/experience.router.ts b/src/routers/experience.router.ts new file mode 100644 index 0000000..c72d450 --- /dev/null +++ b/src/routers/experience.router.ts @@ -0,0 +1,118 @@ +import { Router, type Request, type Response } from "express"; +import { + createExperience, + getAllExperiences, + getExperienceById, + updateExperience, + deleteExperience, +} from "../controllers/experience.controller"; +import { HttpError, HttpStatus } from "../utils/errors"; +import { type ExperienceType } from "../models/experience.model"; + +export const experienceRouter = Router(); + +//Add an experience +//Note that the user field (which is part of ExperienceType) in body is automatically populated by verifyToken middleware +experienceRouter.post( + "/", + async (req: Request, res: Response) => { + try { + const experience = await createExperience(req.body); + res.status(HttpStatus.OK).json(experience); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get all experience +experienceRouter.get( + "/", + async (req: Request, res: Response) => { + try { + const experience = await getAllExperiences(req.body.user); + res.status(HttpStatus.OK).json(experience); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get a single experience by id +experienceRouter.get( + "/:experienceId", + async (req: Request, res: Response) => { + try { + const experience = await getExperienceById( + req.body.user, + req.params.experienceId, + ); + res.status(HttpStatus.OK).json(experience); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Update an experience +experienceRouter.put( + "/:experienceId", + async (req: Request, res: Response) => { + try { + const experience = await updateExperience( + req.body.user, + req.params.experienceId, + req.body, + ); + res.status(HttpStatus.OK).json(experience); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Delete an experience +experienceRouter.delete( + "/:experienceId", + async (req: Request, res: Response) => { + try { + const experience = await deleteExperience( + req.body.user, + req.params.experienceId, + ); + res.status(HttpStatus.OK).json(experience); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); diff --git a/src/routers/folder.router.ts b/src/routers/folder.router.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/routers/heading.router.ts b/src/routers/heading.router.ts new file mode 100644 index 0000000..e10fc26 --- /dev/null +++ b/src/routers/heading.router.ts @@ -0,0 +1,118 @@ +import { Router, type Request, type Response } from "express"; +import { + createHeading, + getAllHeadings, + getHeadingById, + updateHeading, + deleteHeading, +} from "../controllers/heading.controller"; +import { HttpError, HttpStatus } from "../utils/errors"; +import { type HeadingType } from "../models/heading.model"; + +export const headingRouter = Router(); + +//Add an heading +//Note that the user field (which is part of HeadingType) in body is automatically populated by verifyToken middleware +headingRouter.post( + "/", + async (req: Request, res: Response) => { + try { + const heading = await createHeading(req.body); + res.status(HttpStatus.OK).json(heading); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get all heading +headingRouter.get( + "/", + async (req: Request, res: Response) => { + try { + const heading = await getAllHeadings(req.body.user); + res.status(HttpStatus.OK).json(heading); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get a single heading by id +headingRouter.get( + "/:headingId", + async (req: Request, res: Response) => { + try { + const heading = await getHeadingById( + req.body.user, + req.params.headingId, + ); + res.status(HttpStatus.OK).json(heading); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Update an heading +headingRouter.put( + "/:headingId", + async (req: Request, res: Response) => { + try { + const heading = await updateHeading( + req.body.user, + req.params.headingId, + req.body, + ); + res.status(HttpStatus.OK).json(heading); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Delete an heading +headingRouter.delete( + "/:headingId", + async (req: Request, res: Response) => { + try { + const heading = await deleteHeading( + req.body.user, + req.params.headingId, + ); + res.status(HttpStatus.OK).json(heading); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); diff --git a/src/routers/project.router.ts b/src/routers/project.router.ts new file mode 100644 index 0000000..b4c1fc4 --- /dev/null +++ b/src/routers/project.router.ts @@ -0,0 +1,118 @@ +import { Router, type Request, type Response } from "express"; +import { + createProject, + getAllProjects, + getProjectById, + updateProject, + deleteProject, +} from "../controllers/project.controller"; +import { HttpError, HttpStatus } from "../utils/errors"; +import { type ProjectType } from "../models/project.model"; + +export const projectRouter = Router(); + +//Add an project +//Note that the user field (which is part of ProjectType) in body is automatically populated by verifyToken middleware +projectRouter.post( + "/", + async (req: Request, res: Response) => { + try { + const project = await createProject(req.body); + res.status(HttpStatus.OK).json(project); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get all project +projectRouter.get( + "/", + async (req: Request, res: Response) => { + try { + const project = await getAllProjects(req.body.user); + res.status(HttpStatus.OK).json(project); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get a single project by id +projectRouter.get( + "/:projectId", + async (req: Request, res: Response) => { + try { + const project = await getProjectById( + req.body.user, + req.params.projectId, + ); + res.status(HttpStatus.OK).json(project); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Update an project +projectRouter.put( + "/:projectId", + async (req: Request, res: Response) => { + try { + const project = await updateProject( + req.body.user, + req.params.projectId, + req.body, + ); + res.status(HttpStatus.OK).json(project); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Delete an project +projectRouter.delete( + "/:projectId", + async (req: Request, res: Response) => { + try { + const project = await deleteProject( + req.body.user, + req.params.projectId, + ); + res.status(HttpStatus.OK).json(project); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); diff --git a/src/routers/resume.router.ts b/src/routers/resume.router.ts new file mode 100644 index 0000000..23eabef --- /dev/null +++ b/src/routers/resume.router.ts @@ -0,0 +1,119 @@ +import { Router, type Request, type Response } from "express"; +import { + createResume, + getAllResumes, + getResumeById, + updateResume, + deleteResume, +} from "../controllers/resume.controller"; +import { HttpError, HttpStatus } from "../utils/errors"; +import { type resumeType } from "../models/resume.model"; + +export const resumeRouter = Router(); + +//Add an resume +//Note that the user field (which is part of resumesType) in body is automatically populated by verifyToken middleware +resumeRouter.post( + "/", + async (req: Request, res: Response) => { + try { + const resume = await createResume(req.body); + res.status(HttpStatus.OK).json(resume); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get all resumes +resumeRouter.get( + "/", + async (req: Request, res: Response) => { + try { + const resume = await getAllResumes(req.body.user); + res.status(HttpStatus.OK).json(resume); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get a single resume by id +resumeRouter.get( + "/:resumeId", + async (req: Request, res: Response) => { + try { + const resume = await getResumeById( + req.body.user, + req.params.resumeId, + ); + res.status(HttpStatus.OK).json(resume); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Update an resume +resumeRouter.put( + "/:resumeId", + async (req: Request, res: Response) => { + try { + const resume = await updateResume( + req.body.user, + req.params.resumeId, + req.body, + ); + res.status(HttpStatus.OK).json(resume); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Delete an resume +resumeRouter.delete( + "/:resumeId", + async (req: Request, res: Response) => { + try { + const resume = await deleteResume( + req.body.user, + req.params.resumeId, + ); + res.status(HttpStatus.OK).json(resume); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + diff --git a/src/routers/root.router.ts b/src/routers/root.router.ts new file mode 100644 index 0000000..e3b4f99 --- /dev/null +++ b/src/routers/root.router.ts @@ -0,0 +1,20 @@ +import { Router } from "express"; +import { skillRouter } from "./skills.router"; +import { activitiesRouter } from "./activities.router"; +import { educationRouter } from "./education.router"; +import { experienceRouter } from "./experience.router"; +import { headingRouter } from "./heading.router"; +import { projectRouter } from "./project.router"; +import { resumeRouter } from "./resume.router"; +import { sectionHeadingRouter } from "./sectionHeading.router"; + +export const router = Router(); + +router.use("/skills", skillRouter); +router.use("/activities", activitiesRouter); +router.use("/education", educationRouter); +router.use("/experience", experienceRouter); +router.use("/headings", headingRouter); +router.use("/projects", projectRouter); +router.use("/resumes", resumeRouter); +router.use("/sectionHeadings", sectionHeadingRouter); diff --git a/src/routers/sectionHeading.router.ts b/src/routers/sectionHeading.router.ts new file mode 100644 index 0000000..1f6d3b1 --- /dev/null +++ b/src/routers/sectionHeading.router.ts @@ -0,0 +1,119 @@ +import { Router, type Request, type Response } from "express"; +import { + createSectionHeading, + getAllSectionHeadings, + getSectionHeadingById, + updateSectionHeading, + deleteSectionHeading, +} from "../controllers/sectionHeading.controller"; +import { HttpError, HttpStatus } from "../utils/errors"; +import { type SectionHeadingType } from "../models/sectionHeading.model"; + +export const sectionHeadingRouter = Router(); + +//Add an sectionHeading +//Note that the user field (which is part of SectionHeadingType) in body is automatically populated by verifyToken middleware +sectionHeadingRouter.post( + "/", + async (req: Request, res: Response) => { + try { + const sectionHeading = await createSectionHeading(req.body); + res.status(HttpStatus.OK).json(sectionHeading); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get all sectionHeading +sectionHeadingRouter.get( + "/", + async (req: Request, res: Response) => { + try { + const sectionHeading = await getAllSectionHeadings(req.body.user); + res.status(HttpStatus.OK).json(sectionHeading); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get a single sectionHeading by id +sectionHeadingRouter.get( + "/:sectionHeadingId", + async (req: Request, res: Response) => { + try { + const sectionHeading = await getSectionHeadingById( + req.body.user, + req.params.sectionHeadingId, + ); + res.status(HttpStatus.OK).json(sectionHeading); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Update an sectionHeading +sectionHeadingRouter.put( + "/:sectionHeadingId", + async (req: Request, res: Response) => { + try { + const sectionHeading = await updateSectionHeading( + req.body.user, + req.params.sectionHeadingId, + req.body, + ); + res.status(HttpStatus.OK).json(sectionHeading); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Delete an sectionHeading +sectionHeadingRouter.delete( + "/:sectionHeadingId", + async (req: Request, res: Response) => { + try { + const sectionHeading = await deleteSectionHeading( + req.body.user, + req.params.sectionHeadingId, + ); + res.status(HttpStatus.OK).json(sectionHeading); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + diff --git a/src/routers/skills.router.ts b/src/routers/skills.router.ts new file mode 100644 index 0000000..b96df0e --- /dev/null +++ b/src/routers/skills.router.ts @@ -0,0 +1,119 @@ +import { Router, type Request, type Response } from "express"; +import { + createSkill, + getAllSkills, + getSkillById, + updateSkill, + deleteSkill, +} from "../controllers/skills.controller"; +import { HttpError, HttpStatus } from "../utils/errors"; +import { type SkillsType } from "../models/skills.model"; + +export const skillRouter = Router(); + +//Add an skill +//Note that the user field (which is part of SkillsType) in body is automatically populated by verifyToken middleware +skillRouter.post( + "/", + async (req: Request, res: Response) => { + try { + const skill = await createSkill(req.body); + res.status(HttpStatus.OK).json(skill); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get all skill +skillRouter.get( + "/", + async (req: Request, res: Response) => { + try { + const skill = await getAllSkills(req.body.user); + res.status(HttpStatus.OK).json(skill); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Get a single skill by id +skillRouter.get( + "/:skillId", + async (req: Request, res: Response) => { + try { + const skill = await getSkillById( + req.body.user, + req.params.skillId, + ); + res.status(HttpStatus.OK).json(skill); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Update an skill +skillRouter.put( + "/:skillId", + async (req: Request, res: Response) => { + try { + const skill = await updateSkill( + req.body.user, + req.params.skillId, + req.body, + ); + res.status(HttpStatus.OK).json(skill); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + +//Delete an skill +skillRouter.delete( + "/:skillId", + async (req: Request, res: Response) => { + try { + const skill = await deleteSkill( + req.body.user, + req.params.skillId, + ); + res.status(HttpStatus.OK).json(skill); + } catch (err: unknown) { + if (err instanceof HttpError) { + res.status(err.errorCode).json({ error: err.message }); + } else { + res + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .json({ error: "An unknown error occurred" }); + } + } + }, +); + diff --git a/src/tests/controllers.tests/activities.test.ts b/src/tests/controllers.tests/activities.test.ts new file mode 100644 index 0000000..9325f90 --- /dev/null +++ b/src/tests/controllers.tests/activities.test.ts @@ -0,0 +1,71 @@ +import { dbConnect, dbDisconnect } from "../dbHandler"; +import { type ActivitiesType } from "../../models/activities.model"; +import { activityDummyData1 } from "./dummyData"; +import { + createActivity, + getAllActivities, + getActivityById, + updateActivity, + deleteActivity, +} from "../../controllers/activities.controller"; +import { describe, test, expect, beforeEach, afterEach } from "vitest"; + +describe("Activities controller tests", () => { + beforeEach(async () => dbConnect()); + afterEach(async () => dbDisconnect()); + + test("Adds and retrieves an activity", async () => { + await createActivity(activityDummyData1 as ActivitiesType); + const returnedActivities = await getAllActivities(activityDummyData1.user); + + //get back the 1 activity that was added + expect(returnedActivities.length).to.equal(1); + expect(returnedActivities[0]).toMatchObject(activityDummyData1); + + //Can't add duplicate name + await expect( + createActivity(activityDummyData1 as ActivitiesType), + ).rejects.toThrowError(); + + const returnedActivities2 = await getAllActivities(activityDummyData1.user); + + //if duplicate, shouldn't add to db + expect(returnedActivities2.length).to.equal(1); + + const returnedActivities3 = await getAllActivities("fakeuserid"); + + //don't get records for a different user id + expect(returnedActivities3.length).to.equal(0); + }); + + test("Finds, updates, and deletes an activity", async () => { + await createActivity(activityDummyData1 as ActivitiesType); + const returnedActivities = await getAllActivities(activityDummyData1.user); + + const returnedActivity = await getActivityById( + activityDummyData1.user, + returnedActivities[0]._id, + ); + + expect(returnedActivity).toMatchObject(activityDummyData1); + + const newItem = "activitiesItem2"; + await updateActivity(activityDummyData1.user, returnedActivities[0]._id, { + ...activityDummyData1, + itemName: newItem, + } as ActivitiesType); + const returnedActivity2 = await getActivityById( + activityDummyData1.user, + returnedActivities[0]._id, + ); + expect(returnedActivity2?.itemName).to.equal(newItem); + + await deleteActivity(activityDummyData1.user, returnedActivities[0]._id); + const returnedActivities3 = await getAllActivities(activityDummyData1.user); + expect(returnedActivities3.length).to.equal(0); + + await expect( + updateActivity(activityDummyData1.user, "", {} as ActivitiesType), + ).rejects.toThrowError("Missing"); + }); +}); diff --git a/src/tests/controllers.tests/dummyData.ts b/src/tests/controllers.tests/dummyData.ts new file mode 100644 index 0000000..58d8272 --- /dev/null +++ b/src/tests/controllers.tests/dummyData.ts @@ -0,0 +1,71 @@ +import mongoose from "mongoose"; + +export const activityDummyData1 = { + user: "test", + itemName: "activitesItem1", + bullets: ["example bullet"], + title: "title", + subtitle: "subtitle", + year: "year", + location: "location", +}; + +export const educationDummyData1 = { + user: "test", + itemName: "activitiesItem1", + bullets: ["example bullet"], + title: "title", + subtitle: "subtitle", + location: "location", + year: "year", +} + +export const experienceDummyData1 = { + user: "test", + itemName: "activitiesItem1", + bullets: ["example bullet"], + title: "title", + subtitle: "subtitle", + location: "location", + year: "year", +} + +export const headingItemDummy = { + item: "item", + href: null, +} + +export const headingDummyData1 = { + user: "user", + itemName: "itemName", + name: "name", + items: [headingItemDummy], +} + +export const projectDummyData1 = { + user: "test", + itemName: "activitiesItem1", + bullets: ["example bullet"], + title: "title", + year: "year", +} + +export const skillsDummyData1 = { + user: "test", + itemName: "activitiesItem1", + title: "title", + description: "description" +} + +export const resumeDummyData1 = { + user: "test", + itemName: "resumeItem1", + itemIds: [new mongoose.Types.ObjectId("65e4f54db1e12e776e01cf31")], + templateId: new mongoose.Types.ObjectId("75e4f54db1e12e776e01cf31"), +} + +export const sectionHeadingDummyData1 = { + user: "test", + itemName: "sectionHeadingItem1", + title: "test section heading", +} diff --git a/src/tests/controllers.tests/education.test.ts b/src/tests/controllers.tests/education.test.ts new file mode 100644 index 0000000..0fc046d --- /dev/null +++ b/src/tests/controllers.tests/education.test.ts @@ -0,0 +1,71 @@ +import { dbConnect, dbDisconnect } from "../dbHandler"; +import { type EducationType } from "../../models/education.model"; +import { educationDummyData1 } from "./dummyData"; +import { + createEducation, + getAllEducation, + getEducationById, + updateEducation, + deleteEducation, +} from "../../controllers/education.controller"; +import { describe, test, expect, beforeEach, afterEach } from "vitest"; + +describe("Education controller tests", () => { + beforeEach(async () => dbConnect()); + afterEach(async () => dbDisconnect()); + + test("Adds and retrieves an education", async () => { + await createEducation(educationDummyData1 as EducationType); + const returnedEducation = await getAllEducation(educationDummyData1.user); + + //get back the 1 education that was added + expect(returnedEducation.length).to.equal(1); + expect(returnedEducation[0]).toMatchObject(educationDummyData1); + + //Can't add duplicate name + await expect( + createEducation(educationDummyData1 as EducationType), + ).rejects.toThrowError(); + + const returnedEducation2 = await getAllEducation(educationDummyData1.user); + + //if duplicate, shouldn't add to db + expect(returnedEducation2.length).to.equal(1); + + const returnedEducation3 = await getAllEducation("fakeuserid"); + + //don't get records for a different user id + expect(returnedEducation3.length).to.equal(0); + }); + + test("Finds, updates, and deletes an education", async () => { + await createEducation(educationDummyData1 as EducationType); + const returnedEd = await getAllEducation(educationDummyData1.user); + + const returnedEducation = await getEducationById( + educationDummyData1.user, + returnedEd[0]._id, + ); + + expect(returnedEducation).toMatchObject(educationDummyData1); + + const newItemName = "educationItem2"; + await updateEducation(educationDummyData1.user, returnedEd[0]._id, { + ...educationDummyData1, + itemName: newItemName, + } as EducationType); + const returnedEducation2 = await getEducationById( + educationDummyData1.user, + returnedEd[0]._id, + ); + expect(returnedEducation2?.itemName).to.equal(newItemName); + + await deleteEducation(educationDummyData1.user, returnedEd[0]._id); + const returnedEducation3 = await getAllEducation(educationDummyData1.user); + expect(returnedEducation3.length).to.equal(0); + + await expect( + updateEducation(educationDummyData1.user, "", {} as EducationType), + ).rejects.toThrowError("Missing"); + }); +}); diff --git a/src/tests/controllers.tests/experience.test.ts b/src/tests/controllers.tests/experience.test.ts new file mode 100644 index 0000000..435bcfb --- /dev/null +++ b/src/tests/controllers.tests/experience.test.ts @@ -0,0 +1,71 @@ +import { dbConnect, dbDisconnect } from "../dbHandler"; +import { type ExperienceType } from "../../models/experience.model"; +import { experienceDummyData1 } from "./dummyData"; +import { + createExperience, + getAllExperiences, + getExperienceById, + updateExperience, + deleteExperience, +} from "../../controllers/experience.controller"; +import { describe, test, expect, beforeEach, afterEach } from "vitest"; + +describe("Experience controller tests", () => { + beforeEach(async () => dbConnect()); + afterEach(async () => dbDisconnect()); + + test("Adds and retrieves an experience", async () => { + await createExperience(experienceDummyData1 as ExperienceType); + const returnedExperience = await getAllExperiences(experienceDummyData1.user); + + //get back the 1 experience that was added + expect(returnedExperience.length).to.equal(1); + expect(returnedExperience[0]).toMatchObject(experienceDummyData1); + + //Can't add duplicate name + await expect( + createExperience(experienceDummyData1 as ExperienceType), + ).rejects.toThrowError(); + + const returnedExperience2 = await getAllExperiences(experienceDummyData1.user); + + //if duplicate, shouldn't add to db + expect(returnedExperience2.length).to.equal(1); + + const returnedExperience3 = await getAllExperiences("fakeuserid"); + + //don't get records for a different user id + expect(returnedExperience3.length).to.equal(0); + }); + + test("Finds, updates, and deletes an experience", async () => { + await createExperience(experienceDummyData1 as ExperienceType); + const returnedEd = await getAllExperiences(experienceDummyData1.user); + + const returnedExperience = await getExperienceById( + experienceDummyData1.user, + returnedEd[0]._id, + ); + + expect(returnedExperience).toMatchObject(experienceDummyData1); + + const newItemName = "experienceItem2"; + await updateExperience(experienceDummyData1.user, returnedEd[0]._id, { + ...experienceDummyData1, + itemName: newItemName, + } as ExperienceType); + const returnedExperience2 = await getExperienceById( + experienceDummyData1.user, + returnedEd[0]._id, + ); + expect(returnedExperience2?.itemName).to.equal(newItemName); + + await deleteExperience(experienceDummyData1.user, returnedEd[0]._id); + const returnedExperience3 = await getAllExperiences(experienceDummyData1.user); + expect(returnedExperience3.length).to.equal(0); + + await expect( + updateExperience(experienceDummyData1.user, "", {} as ExperienceType), + ).rejects.toThrowError("Missing"); + }); +}); diff --git a/src/tests/controllers.tests/heading.test.ts b/src/tests/controllers.tests/heading.test.ts new file mode 100644 index 0000000..93f1253 --- /dev/null +++ b/src/tests/controllers.tests/heading.test.ts @@ -0,0 +1,71 @@ +import { dbConnect, dbDisconnect } from "../dbHandler"; +import { type HeadingType } from "../../models/heading.model"; +import { headingDummyData1 } from "./dummyData"; +import { + createHeading, + getAllHeadings, + getHeadingById, + updateHeading, + deleteHeading, +} from "../../controllers/heading.controller"; +import { describe, test, expect, beforeEach, afterEach } from "vitest"; + +describe("Heading controller tests", () => { + beforeEach(async () => dbConnect()); + afterEach(async () => dbDisconnect()); + + test("Adds and retrieves an heading", async () => { + await createHeading(headingDummyData1 as HeadingType); + const returnedHeading = await getAllHeadings(headingDummyData1.user); + + //get back the 1 heading that was added + expect(returnedHeading.length).to.equal(1); + expect(returnedHeading[0]).toMatchObject(headingDummyData1); + + //Can't add duplicate name + await expect( + createHeading(headingDummyData1 as HeadingType), + ).rejects.toThrowError(); + + const returnedHeading2 = await getAllHeadings(headingDummyData1.user); + + //if duplicate, shouldn't add to db + expect(returnedHeading2.length).to.equal(1); + + const returnedHeading3 = await getAllHeadings("fakeuserid"); + + //don't get records for a different user id + expect(returnedHeading3.length).to.equal(0); + }); + + test("Finds, updates, and deletes an heading", async () => { + await createHeading(headingDummyData1 as HeadingType); + const returnedEd = await getAllHeadings(headingDummyData1.user); + + const returnedHeading = await getHeadingById( + headingDummyData1.user, + returnedEd[0]._id, + ); + + expect(returnedHeading).toMatchObject(headingDummyData1); + + const newItemName = "headingItem2"; + await updateHeading(headingDummyData1.user, returnedEd[0]._id, { + ...headingDummyData1, + itemName: newItemName, + } as HeadingType); + const returnedHeading2 = await getHeadingById( + headingDummyData1.user, + returnedEd[0]._id, + ); + expect(returnedHeading2?.itemName).to.equal(newItemName); + + await deleteHeading(headingDummyData1.user, returnedEd[0]._id); + const returnedHeading3 = await getAllHeadings(headingDummyData1.user); + expect(returnedHeading3.length).to.equal(0); + + await expect( + updateHeading(headingDummyData1.user, "", {} as HeadingType), + ).rejects.toThrowError("Missing"); + }); +}); diff --git a/src/tests/controllers.tests/project.test.ts b/src/tests/controllers.tests/project.test.ts new file mode 100644 index 0000000..76aeb15 --- /dev/null +++ b/src/tests/controllers.tests/project.test.ts @@ -0,0 +1,71 @@ +import { dbConnect, dbDisconnect } from "../dbHandler"; +import { type ProjectType } from "../../models/project.model"; +import { projectDummyData1 } from "./dummyData"; +import { + createProject, + getAllProjects, + getProjectById, + updateProject, + deleteProject, +} from "../../controllers/project.controller"; +import { describe, test, expect, beforeEach, afterEach } from "vitest"; + +describe("Project controller tests", () => { + beforeEach(async () => dbConnect()); + afterEach(async () => dbDisconnect()); + + test("Adds and retrieves an project", async () => { + await createProject(projectDummyData1 as ProjectType); + const returnedProject = await getAllProjects(projectDummyData1.user); + + //get back the 1 project that was added + expect(returnedProject.length).to.equal(1); + expect(returnedProject[0]).toMatchObject(projectDummyData1); + + //Can't add duplicate name + await expect( + createProject(projectDummyData1 as ProjectType), + ).rejects.toThrowError(); + + const returnedProject2 = await getAllProjects(projectDummyData1.user); + + //if duplicate, shouldn't add to db + expect(returnedProject2.length).to.equal(1); + + const returnedProject3 = await getAllProjects("fakeuserid"); + + //don't get records for a different user id + expect(returnedProject3.length).to.equal(0); + }); + + test("Finds, updates, and deletes an project", async () => { + await createProject(projectDummyData1 as ProjectType); + const returnedEd = await getAllProjects(projectDummyData1.user); + + const returnedProject = await getProjectById( + projectDummyData1.user, + returnedEd[0]._id, + ); + + expect(returnedProject).toMatchObject(projectDummyData1); + + const newItemName = "projectItem2"; + await updateProject(projectDummyData1.user, returnedEd[0]._id, { + ...projectDummyData1, + itemName: newItemName, + } as ProjectType); + const returnedProject2 = await getProjectById( + projectDummyData1.user, + returnedEd[0]._id, + ); + expect(returnedProject2?.itemName).to.equal(newItemName); + + await deleteProject(projectDummyData1.user, returnedEd[0]._id); + const returnedProject3 = await getAllProjects(projectDummyData1.user); + expect(returnedProject3.length).to.equal(0); + + await expect( + updateProject(projectDummyData1.user, "", {} as ProjectType), + ).rejects.toThrowError("Missing"); + }); +}); diff --git a/src/tests/controllers.tests/resume.test.ts b/src/tests/controllers.tests/resume.test.ts new file mode 100644 index 0000000..a904ff9 --- /dev/null +++ b/src/tests/controllers.tests/resume.test.ts @@ -0,0 +1,88 @@ +import { dbConnect, dbDisconnect } from "../dbHandler"; +import { type resumeType } from "../../models/resume.model"; +import { type ActivitiesType } from "../../models/activities.model"; +import { resumeDummyData1, activityDummyData1 } from "./dummyData"; +import { + createResume, + getAllResumes, + getResumeById, + updateResume, + deleteResume, +} from "../../controllers/resume.controller"; +import { createActivity, deleteActivity } from "../../controllers/activities.controller"; +import { describe, test, expect, beforeEach, afterEach } from "vitest"; +import mongoose from "mongoose"; + +describe("Resume controller tests", () => { + beforeEach(async () => dbConnect()); + afterEach(async () => dbDisconnect()); + + test("Adds and retrieves a resumes", async () => { + await createResume(resumeDummyData1 as resumeType); + const returnedResumes = await getAllResumes(resumeDummyData1.user); + + //get back the 1 resumes that was added + expect(returnedResumes.length).to.equal(1); + expect(returnedResumes[0]).toMatchObject(resumeDummyData1); + + //Can't add duplicate name + await expect( + createResume(resumeDummyData1 as resumeType), + ).rejects.toThrowError(); + + const returnedResumes2 = await getAllResumes(resumeDummyData1.user); + + //if duplicate, shouldn't add to db + expect(returnedResumes2.length).to.equal(1); + + const returnedResumes3 = await getAllResumes("fakeuserid"); + + //don't get records for a different user id + expect(returnedResumes3.length).to.equal(0); + }); + + test("Finds, updates, and deletes a resume", async () => { + await createResume(resumeDummyData1 as resumeType); + const returnedRe = await getAllResumes(resumeDummyData1.user); + + const returnedResumes = await getResumeById( + resumeDummyData1.user, + returnedRe[0]._id, + ); + + expect(returnedResumes).toMatchObject(resumeDummyData1); + + const newItemName = "resumesItem2"; + await updateResume(resumeDummyData1.user, returnedRe[0]._id, { + ...resumeDummyData1, + itemName: newItemName, + } as resumeType); + const returnedResumes2 = await getResumeById( + resumeDummyData1.user, + returnedRe[0]._id, + ); + expect(returnedResumes2?.itemName).to.equal(newItemName); + + await deleteResume(resumeDummyData1.user, returnedRe[0]._id); + const returnedResumes3 = await getAllResumes(resumeDummyData1.user); + expect(returnedResumes3.length).to.equal(0); + + await expect( + updateResume(resumeDummyData1.user, "", {} as resumeType), + ).rejects.toThrowError("Missing"); + }); + + test("Correctly updates resume array upon item deletion", async () => { + const newActivity = await createActivity(activityDummyData1 as ActivitiesType); + let resumeDummyData2 = structuredClone(resumeDummyData1); + resumeDummyData2.itemIds = [newActivity._id]; + resumeDummyData2.templateId = new mongoose.Types.ObjectId("75e4f54db1e12e776e01cf31"); + + + const origResume = await createResume(resumeDummyData2 as resumeType); + await deleteActivity(activityDummyData1.user, newActivity._id); + const updatedResume = await getResumeById(origResume.user, origResume._id); + + expect(updatedResume?.itemIds).toHaveLength(0); + }) +}); diff --git a/src/tests/controllers.tests/sectionHeading.test.ts b/src/tests/controllers.tests/sectionHeading.test.ts new file mode 100644 index 0000000..5c5879e --- /dev/null +++ b/src/tests/controllers.tests/sectionHeading.test.ts @@ -0,0 +1,71 @@ +import { dbConnect, dbDisconnect } from "../dbHandler"; +import { type SectionHeadingType } from "../../models/sectionHeading.model"; +import { sectionHeadingDummyData1 } from "./dummyData"; +import { + createSectionHeading, + getAllSectionHeadings, + getSectionHeadingById, + updateSectionHeading, + deleteSectionHeading, +} from "../../controllers/sectionHeading.controller"; +import { describe, test, expect, beforeEach, afterEach } from "vitest"; + +describe("SectionHeadings controller tests", () => { + beforeEach(async () => dbConnect()); + afterEach(async () => dbDisconnect()); + + test("Adds and retrieves an sectionHeadings", async () => { + await createSectionHeading(sectionHeadingDummyData1 as SectionHeadingType); + const returnedSectionHeadings = await getAllSectionHeadings(sectionHeadingDummyData1.user); + + //get back the 1 sectionHeadings that was added + expect(returnedSectionHeadings.length).to.equal(1); + expect(returnedSectionHeadings[0]).toMatchObject(sectionHeadingDummyData1); + + //Can't add duplicate name + await expect( + createSectionHeading(sectionHeadingDummyData1 as SectionHeadingType), + ).rejects.toThrowError(); + + const returnedSectionHeadings2 = await getAllSectionHeadings(sectionHeadingDummyData1.user); + + //if duplicate, shouldn't add to db + expect(returnedSectionHeadings2.length).to.equal(1); + + const returnedSectionHeadings3 = await getAllSectionHeadings("fakeuserid"); + + //don't get records for a different user id + expect(returnedSectionHeadings3.length).to.equal(0); + }); + + test("Finds, updates, and deletes an sectionHeadings", async () => { + await createSectionHeading(sectionHeadingDummyData1 as SectionHeadingType); + const returnedEd = await getAllSectionHeadings(sectionHeadingDummyData1.user); + + const returnedSectionHeadings = await getSectionHeadingById( + sectionHeadingDummyData1.user, + returnedEd[0]._id, + ); + + expect(returnedSectionHeadings).toMatchObject(sectionHeadingDummyData1); + + const newItemName = "sectionHeadingsItem2"; + await updateSectionHeading(sectionHeadingDummyData1.user, returnedEd[0]._id, { + ...sectionHeadingDummyData1, + itemName: newItemName, + } as SectionHeadingType); + const returnedSectionHeadings2 = await getSectionHeadingById( + sectionHeadingDummyData1.user, + returnedEd[0]._id, + ); + expect(returnedSectionHeadings2?.itemName).to.equal(newItemName); + + await deleteSectionHeading(sectionHeadingDummyData1.user, returnedEd[0]._id); + const returnedSectionHeadings3 = await getAllSectionHeadings(sectionHeadingDummyData1.user); + expect(returnedSectionHeadings3.length).to.equal(0); + + await expect( + updateSectionHeading(sectionHeadingDummyData1.user, "", {} as SectionHeadingType), + ).rejects.toThrowError("Missing"); + }); +}); diff --git a/src/tests/controllers.tests/skills.test.ts b/src/tests/controllers.tests/skills.test.ts new file mode 100644 index 0000000..f550de9 --- /dev/null +++ b/src/tests/controllers.tests/skills.test.ts @@ -0,0 +1,71 @@ +import { dbConnect, dbDisconnect } from "../dbHandler"; +import { type SkillsType } from "../../models/skills.model"; +import { skillsDummyData1 } from "./dummyData"; +import { + createSkill, + getAllSkills, + getSkillById, + updateSkill, + deleteSkill, +} from "../../controllers/skills.controller"; +import { describe, test, expect, beforeEach, afterEach } from "vitest"; + +describe("Skills controller tests", () => { + beforeEach(async () => dbConnect()); + afterEach(async () => dbDisconnect()); + + test("Adds and retrieves an skills", async () => { + await createSkill(skillsDummyData1 as SkillsType); + const returnedSkills = await getAllSkills(skillsDummyData1.user); + + //get back the 1 skills that was added + expect(returnedSkills.length).to.equal(1); + expect(returnedSkills[0]).toMatchObject(skillsDummyData1); + + //Can't add duplicate name + await expect( + createSkill(skillsDummyData1 as SkillsType), + ).rejects.toThrowError(); + + const returnedSkills2 = await getAllSkills(skillsDummyData1.user); + + //if duplicate, shouldn't add to db + expect(returnedSkills2.length).to.equal(1); + + const returnedSkills3 = await getAllSkills("fakeuserid"); + + //don't get records for a different user id + expect(returnedSkills3.length).to.equal(0); + }); + + test("Finds, updates, and deletes an skills", async () => { + await createSkill(skillsDummyData1 as SkillsType); + const returnedEd = await getAllSkills(skillsDummyData1.user); + + const returnedSkills = await getSkillById( + skillsDummyData1.user, + returnedEd[0]._id, + ); + + expect(returnedSkills).toMatchObject(skillsDummyData1); + + const newItemName = "skillsItem2"; + await updateSkill(skillsDummyData1.user, returnedEd[0]._id, { + ...skillsDummyData1, + itemName: newItemName, + } as SkillsType); + const returnedSkills2 = await getSkillById( + skillsDummyData1.user, + returnedEd[0]._id, + ); + expect(returnedSkills2?.itemName).to.equal(newItemName); + + await deleteSkill(skillsDummyData1.user, returnedEd[0]._id); + const returnedSkills3 = await getAllSkills(skillsDummyData1.user); + expect(returnedSkills3.length).to.equal(0); + + await expect( + updateSkill(skillsDummyData1.user, "", {} as SkillsType), + ).rejects.toThrowError("Missing"); + }); +}); diff --git a/src/tests/dbHandler.ts b/src/tests/dbHandler.ts new file mode 100644 index 0000000..ff40f59 --- /dev/null +++ b/src/tests/dbHandler.ts @@ -0,0 +1,16 @@ +import mongoose from "mongoose"; +import { MongoMemoryServer } from 'mongodb-memory-server'; + +let mongoServer: MongoMemoryServer; + +export const dbConnect = async () => { + mongoServer = await MongoMemoryServer.create(); + const uri = mongoServer.getUri(); + await mongoose.connect(uri); +}; + +export const dbDisconnect = async () => { + await mongoose.connection.dropDatabase(); + await mongoose.connection.close(); + await mongoServer.stop(); +}; \ No newline at end of file diff --git a/src/utils/checkDuplicates.ts b/src/utils/checkDuplicates.ts new file mode 100644 index 0000000..57085b6 --- /dev/null +++ b/src/utils/checkDuplicates.ts @@ -0,0 +1,35 @@ +import mongoose from "mongoose"; +import { ActivitiesModel } from "../models/activities.model"; +import { EducationModel } from "../models/education.model"; +import { ExperienceModel } from "../models/experience.model"; +import { HeadingModel } from "../models/heading.model"; +import { ProjectModel } from "../models/project.model"; +import { SectionHeadingModel } from "../models/sectionHeading.model"; +import { SkillsModel } from "../models/skills.model"; + +export const checkDuplicateItemName = async (value: string, excludedId: string | null = null): Promise => { + const field = "itemName"; + const models = [ + ActivitiesModel, + EducationModel, + ExperienceModel, + HeadingModel, + ProjectModel, + SectionHeadingModel, + SkillsModel, + ]; + + // Check each model for the count of documents with the specified itemName value + const checks = models.map((model) => + model.countDocuments({ [field]: value, '_id': { $ne: excludedId } }).exec(), + ); + + // Await all checks to resolve + const results = await Promise.all(checks); + + // Sum the counts from all models + const totalDuplicates = results.reduce((acc, count) => acc + count, 0); + + // If totalDuplicates is greater than 0, a duplicate exists + return totalDuplicates > 0; +};