From f08698ffa1272eb9f9cea3ce9030625dcb09175c Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 1 Jul 2024 11:19:12 +0700 Subject: [PATCH 001/102] feat: bump deps version --- package.json | 24 ++-- pnpm-lock.yaml | 287 +++++++++++++++++++++--------------------- src/services/minio.ts | 16 +-- 3 files changed, 163 insertions(+), 164 deletions(-) diff --git a/package.json b/package.json index 2502949..48b8114 100644 --- a/package.json +++ b/package.json @@ -20,28 +20,28 @@ "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", - "@types/node": "^20.12.2", + "@types/node": "^20.14.9", "@types/swagger-ui-express": "^4.1.6", - "nodemon": "^3.1.3", - "prettier": "^3.2.5", - "prisma": "^5.16.0", + "nodemon": "^3.1.4", + "prettier": "^3.3.2", + "prisma": "^5.16.1", "prisma-kysely": "^1.8.0", "ts-node": "^10.9.2", - "typescript": "^5.4.3" + "typescript": "^5.5.2" }, "dependencies": { - "@elastic/elasticsearch": "^8.13.0", - "@prisma/client": "^5.16.0", - "@tsoa/runtime": "^6.2.0", + "@elastic/elasticsearch": "^8.14.0", + "@prisma/client": "^5.16.1", + "@tsoa/runtime": "^6.3.0", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", - "fast-jwt": "^4.0.0", + "fast-jwt": "^4.0.1", "kysely": "^0.27.3", - "minio": "^7.1.3", + "minio": "^8.0.1", "prisma-extension-kysely": "^2.1.0", "promise.any": "^2.0.6", - "swagger-ui-express": "^5.0.0", - "tsoa": "^6.2.0" + "swagger-ui-express": "^5.0.1", + "tsoa": "^6.3.1" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 08a3c9d..bb3008f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,14 +9,14 @@ importers: .: dependencies: '@elastic/elasticsearch': - specifier: ^8.13.0 - version: 8.13.0 + specifier: ^8.14.0 + version: 8.14.0 '@prisma/client': - specifier: ^5.16.0 - version: 5.16.0(prisma@5.16.0) + specifier: ^5.16.1 + version: 5.16.1(prisma@5.16.1) '@tsoa/runtime': - specifier: ^6.2.0 - version: 6.2.0 + specifier: ^6.3.0 + version: 6.3.0 cors: specifier: ^2.8.5 version: 2.8.5 @@ -27,26 +27,26 @@ importers: specifier: ^4.19.2 version: 4.19.2 fast-jwt: - specifier: ^4.0.0 - version: 4.0.0 + specifier: ^4.0.1 + version: 4.0.1 kysely: specifier: ^0.27.3 version: 0.27.3 minio: - specifier: ^7.1.3 - version: 7.1.3 + specifier: ^8.0.1 + version: 8.0.1 prisma-extension-kysely: specifier: ^2.1.0 - version: 2.1.0(@prisma/client@5.16.0(prisma@5.16.0)) + version: 2.1.0(@prisma/client@5.16.1(prisma@5.16.1)) promise.any: specifier: ^2.0.6 version: 2.0.6 swagger-ui-express: - specifier: ^5.0.0 - version: 5.0.0(express@4.19.2) + specifier: ^5.0.1 + version: 5.0.1(express@4.19.2) tsoa: - specifier: ^6.2.0 - version: 6.2.0 + specifier: ^6.3.1 + version: 6.3.1 devDependencies: '@types/cors': specifier: ^2.8.17 @@ -55,29 +55,29 @@ importers: specifier: ^4.17.21 version: 4.17.21 '@types/node': - specifier: ^20.12.2 - version: 20.12.2 + specifier: ^20.14.9 + version: 20.14.9 '@types/swagger-ui-express': specifier: ^4.1.6 version: 4.1.6 nodemon: - specifier: ^3.1.3 - version: 3.1.3 + specifier: ^3.1.4 + version: 3.1.4 prettier: - specifier: ^3.2.5 - version: 3.2.5 + specifier: ^3.3.2 + version: 3.3.2 prisma: - specifier: ^5.16.0 - version: 5.16.0 + specifier: ^5.16.1 + version: 5.16.1 prisma-kysely: specifier: ^1.8.0 version: 1.8.0 ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@20.12.2)(typescript@5.4.3) + version: 10.9.2(@types/node@20.14.9)(typescript@5.5.2) typescript: - specifier: ^5.4.3 - version: 5.4.3 + specifier: ^5.5.2 + version: 5.5.2 packages: @@ -113,12 +113,12 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@elastic/elasticsearch@8.13.0': - resolution: {integrity: sha512-OAYgzqArPqgDaIJ1yT0RX31YCgr1lleo53zL+36i23PFjHu08CA6Uq+BmBzEV05yEidl+ILPdeSfF3G8hPG/JQ==} + '@elastic/elasticsearch@8.14.0': + resolution: {integrity: sha512-MGrgCI4y+Ozssf5Q2IkVJlqt5bUMnKIICG2qxeOfrJNrVugMCBCAQypyesmSSocAtNm8IX3LxfJ3jQlFHmKe2w==} engines: {node: '>=18'} - '@elastic/transport@8.5.0': - resolution: {integrity: sha512-T+zSUHXBfrqlj/E9pJiaEgKoTdGykBCohzNBt6omDfI6EQtaNT240oMO03oXo35T8rwrCVonSMSoedbmToncVA==} + '@elastic/transport@8.6.1': + resolution: {integrity: sha512-3vGs4W3wP5oeIT/4j1vcvd+t7m6ndP0uyb5GDY23LQCmbtI5Oq0aQwD9gb09KJbLFLUbI7db9vMFPzKavSFA5g==} engines: {node: '>=18'} '@hapi/accept@6.0.3': @@ -254,8 +254,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@prisma/client@5.16.0': - resolution: {integrity: sha512-8NOQSzgrSgpU2YcNm4MsPR4/vNvZdUBPuyX89PwWlClSKZeDjPSZ8+eds/ffu1Ttrjnm8V8D0hjgqpyc6wyqDg==} + '@prisma/client@5.16.1': + resolution: {integrity: sha512-wM9SKQjF0qLxdnOZIVAIMKiz6Hu7vDt4FFAih85K1dk/Rr2mdahy6d3QP41K62N9O0DJJA//gUDA3Mp49xsKIg==} engines: {node: '>=16.13'} peerDependencies: prisma: '*' @@ -263,8 +263,8 @@ packages: prisma: optional: true - '@prisma/debug@5.16.0': - resolution: {integrity: sha512-pfdOGxMShqZKkNNskYB0yXICsqL6rOkQUKNktouUZ9Y9ASd5736+ae2fpzif7onwJiIyEpu/yvOO3rFUbliKTA==} + '@prisma/debug@5.16.1': + resolution: {integrity: sha512-JsNgZAg6BD9RInLSrg7ZYzo11N7cVvYArq3fHGSD89HSgtN0VDdjV6bib7YddbcO6snzjchTiLfjeTqBjtArVQ==} '@prisma/debug@5.3.1': resolution: {integrity: sha512-eYrxqslEKf+wpMFIIHgbcNYuZBXUdiJLA85Or3TwOhgPIN1ZoXT9CwJph3ynW8H1Xg0LkdYLwVmuULCwiMoU5A==} @@ -272,14 +272,14 @@ packages: '@prisma/engines-version@5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303': resolution: {integrity: sha512-HkT2WbfmFZ9WUPyuJHhkiADxazHg8Y4gByrTSVeb3OikP6tjQ7txtSUGu9OBOBH0C13dPKN2qqH12xKtHu/Hiw==} - '@prisma/engines@5.16.0': - resolution: {integrity: sha512-OGvi/GvLX3XwTWQ+k/57kLyHGidQ8rC8zB+Zq9nEE7gegjazyzgLYN9qzfdcCfyI8ilc6IMxOyX4sspwkv98hg==} + '@prisma/engines@5.16.1': + resolution: {integrity: sha512-KkyF3eIUtBIyp5A/rJHCtwQO18OjpGgx18PzjyGcJDY/+vNgaVyuVd+TgwBgeq6NLdd1XMwRCI+58vinHsAdfA==} '@prisma/engines@5.3.1': resolution: {integrity: sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA==} - '@prisma/fetch-engine@5.16.0': - resolution: {integrity: sha512-8C8y6J9eWRl+R/aO3vQ2HlmM9IbjAmrZaaEAdC0OJfG3CHvbTOcL7VRY6CEUKo8RwZ8bdATOePaSMS634fHWgw==} + '@prisma/fetch-engine@5.16.1': + resolution: {integrity: sha512-oOkjaPU1lhcA/Rvr4GVfd1NLJBwExgNBE36Ueq7dr71kTMwy++a3U3oLd2ZwrV9dj9xoP6LjCcky799D9nEt4w==} '@prisma/fetch-engine@5.3.1': resolution: {integrity: sha512-w1yk1YiK8N82Pobdq58b85l6e8akyrkxuzwV9DoiUTRf3gpsuhJJesHc4Yi0WzUC9/3znizl1UfCsI6dhkj3Vw==} @@ -287,8 +287,8 @@ packages: '@prisma/generator-helper@5.3.1': resolution: {integrity: sha512-zrYS0iHLgPlOJjYnd5KvVMMvSS+ktOL39EwooS5EnyvfzwfzxlKCeOUgxTfiKYs0WUWqzEvyNAYtramYgSknsQ==} - '@prisma/get-platform@5.16.0': - resolution: {integrity: sha512-ynp2jAYfYdd7OObX+uWaFRpvhPVmpF0nsRMhbrWdVVUj39q3Zr8dGz5WDj2g+BTUE++u1T1Am3RyM3PBQdDZXA==} + '@prisma/get-platform@5.16.1': + resolution: {integrity: sha512-R4IKnWnMkR2nUAbU5gjrPehdQYUUd7RENFD2/D+xXTNhcqczp0N+WEGQ3ViyI3+6mtVcjjNIMdnUTNyu3GxIgA==} '@prisma/get-platform@5.3.1': resolution: {integrity: sha512-3IiZY2BUjKnAuZ0569zppZE6/rZbVAM09//c2nvPbbkGG9MqrirA8fbhhF7tfVmhyVfdmVCHnf/ujWPHJ8B46Q==} @@ -311,13 +311,13 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@tsoa/cli@6.2.0': - resolution: {integrity: sha512-kzr10MsARpuivJl59XD33+sdMA18Rx9KBu7NCp18nV6kPQ1LDrVBwVgLL96w7lBgUGsCgVJ1/cUePW313uTZCw==} + '@tsoa/cli@6.3.1': + resolution: {integrity: sha512-KOlsShmAZ8Ju8KCkBFFzdURlGa9YNgf6gAvU6GwnOQl7mcvqvKMk8IDy2bcotpqNcabHo6fbkeExztF/n0Z0kg==} engines: {node: '>=18.0.0', yarn: '>=1.9.4'} hasBin: true - '@tsoa/runtime@6.2.0': - resolution: {integrity: sha512-iXSi5rDZWcPRcLKa9WaMBUdh4SdDN+9Cmy+R7i/DSCtFtbtUqE+ui+IMV9o6+759N6J3FqGBaPWa9kAtOrL4sA==} + '@tsoa/runtime@6.3.0': + resolution: {integrity: sha512-9z/0ePKpSoYpUq1IJvihk4FcgpAkyMTEEl9oFLiL9p5SE86ndsGJdH3kt/IyqtD3wajPSykMRHlAidrGUyLuNQ==} engines: {node: '>=18.0.0', yarn: '>=1.9.4'} '@types/accepts@1.3.7': @@ -378,8 +378,8 @@ packages: '@types/multer@1.4.11': resolution: {integrity: sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w==} - '@types/node@20.12.2': - resolution: {integrity: sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==} + '@types/node@20.14.9': + resolution: {integrity: sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} @@ -537,10 +537,6 @@ packages: brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - braces@3.0.2: - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} - engines: {node: '>=8'} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -551,6 +547,10 @@ packages: buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-crc32@1.0.0: + resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==} + engines: {node: '>=8.0.0'} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -808,6 +808,9 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -820,9 +823,9 @@ packages: resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} - fast-jwt@4.0.0: - resolution: {integrity: sha512-CnI93Tmk7eSzfw4e0Jlc5ZcWiN+/zK7xAKAhtXS8PAcKmYqfcXqxJl1hMyhk3iEt0M9JLo7DCYRDV2XcIacWAw==} - engines: {node: '>=16 <22'} + fast-jwt@4.0.1: + resolution: {integrity: sha512-+mdSoH0QdOdFSbbGBctJu7L1yfXRtbmjbVJ4W/PEjyvivobDena0RKwihtBkOML1P+kUJ1QuewnH8u+mROsR1w==} + engines: {node: '>=16'} fast-xml-parser@4.3.6: resolution: {integrity: sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==} @@ -831,10 +834,6 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - fill-range@7.0.1: - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} - engines: {node: '>=8'} - fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -1219,9 +1218,6 @@ packages: json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-stream@1.0.0: - resolution: {integrity: sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg==} - jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} @@ -1362,8 +1358,8 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minio@7.1.3: - resolution: {integrity: sha512-xPrLjWkTT5E7H7VnzOjF//xBp9I40jYB4aWhb2xTFopXXfw+Wo82DDWngdUju7Doy3Wk7R8C4LAgwhLHHnf0wA==} + minio@8.0.1: + resolution: {integrity: sha512-FzDO6yGnqLtm8sp3mXafWtiRUOslJSSg/aI0v9YbN5vjw5KLoODKAROCyi766NIvTSxcfHBrbhCSGk1A+MOzDg==} engines: {node: ^16 || ^18 || >=20} minipass@7.0.4: @@ -1411,8 +1407,8 @@ packages: encoding: optional: true - nodemon@3.1.3: - resolution: {integrity: sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==} + nodemon@3.1.4: + resolution: {integrity: sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==} engines: {node: '>=10'} hasBin: true @@ -1562,8 +1558,8 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} - prettier@3.2.5: - resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + prettier@3.3.2: + resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} engines: {node: '>=14'} hasBin: true @@ -1576,8 +1572,8 @@ packages: resolution: {integrity: sha512-VpNpolZ8RXRgfU+j4R+fPZmX8EE95w3vJ2tt7+FwuiQc0leNTfLK5QLf3KbbPDes2rfjh3g20AjDxefQIo5GIA==} hasBin: true - prisma@5.16.0: - resolution: {integrity: sha512-T1ZWJT/vgzp3rtRmd1iCSnPPsgOItXnnny+/cfpHraowiBEvUMD2pEI6yEOL6CP2EelTmq4wKDbXbYucy4Fd+A==} + prisma@5.16.1: + resolution: {integrity: sha512-Z1Uqodk44diztImxALgJJfNl2Uisl9xDRvqybMKEBYJLNKNhDfAHf+ZIJbZyYiBhLMbKU9cYGdDVG5IIXEnL2Q==} engines: {node: '>=16.13'} hasBin: true @@ -1799,6 +1795,12 @@ packages: resolution: {integrity: sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==} engines: {node: '>= 0.4'} + stream-chain@2.2.5: + resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} + + stream-json@1.8.0: + resolution: {integrity: sha512-HZfXngYHUAr1exT4fxlbc1IOce1RYxp2ldeaf97LYCOPSoOqY/1Psp7iGvpb+6JIOgkra9zDYnPX01hGAHzEPw==} + strict-uri-encode@2.0.0: resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} engines: {node: '>=4'} @@ -1866,8 +1868,8 @@ packages: swagger-ui-dist@5.13.0: resolution: {integrity: sha512-uaWhh6j18IIs5tOX0arvIBnVINAzpTXaQXkr7qAk8zoupegJVg0UU/5+S/FgsgVCnzVsJ9d7QLjIxkswEeTg0Q==} - swagger-ui-express@5.0.0: - resolution: {integrity: sha512-tsU9tODVvhyfkNSvf03E6FAk+z+5cU3lXAzMy6Pv4av2Gt2xA0++fogwC4qo19XuFf6hdxevPuVCSKFuMHJhFA==} + swagger-ui-express@5.0.1: + resolution: {integrity: sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==} engines: {node: '>= v0.10.32'} peerDependencies: express: '>=4.0.0 || >=5.0.0-beta' @@ -1934,8 +1936,8 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - tsoa@6.2.0: - resolution: {integrity: sha512-EX/RyoU+4hD1rLM5NjYG+I7lEhqx1yuLgcHs/gyWQpkX/RL9cVR9hFA9LKQrK6PE+WTg1SEahn1MK3l/+6pVKw==} + tsoa@6.3.1: + resolution: {integrity: sha512-w2INTVkjqQDlWo5aLv/ZIVAgWSfOOUstOW1WWVYOa+9mY2Er/W9E9BcBpIdaoJ5BtUZimhhhs5BQ2Nn76OfQpw==} engines: {node: '>=18.0.0', yarn: '>=1.9.4'} hasBin: true @@ -1975,8 +1977,8 @@ packages: resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} engines: {node: '>= 0.4'} - typescript@5.4.3: - resolution: {integrity: sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==} + typescript@5.5.2: + resolution: {integrity: sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==} engines: {node: '>=14.17'} hasBin: true @@ -1994,9 +1996,9 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici@6.11.1: - resolution: {integrity: sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw==} - engines: {node: '>=18.0'} + undici@6.19.2: + resolution: {integrity: sha512-JfjKqIauur3Q6biAtHJ564e3bWa8VvT+7cSiOJHFbX4Erv6CLGDpg8z+Fmg/1OI/47RA+GI2QZaF48SSaLvyBA==} + engines: {node: '>=18.17'} unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} @@ -2077,9 +2079,6 @@ packages: resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==} engines: {node: '>=4.0.0'} - xml@1.0.1: - resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==} - xmlbuilder@11.0.1: resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} engines: {node: '>=4.0'} @@ -2156,21 +2155,21 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@elastic/elasticsearch@8.13.0': + '@elastic/elasticsearch@8.14.0': dependencies: - '@elastic/transport': 8.5.0 + '@elastic/transport': 8.6.1 tslib: 2.6.2 transitivePeerDependencies: - supports-color - '@elastic/transport@8.5.0': + '@elastic/transport@8.6.1': dependencies: debug: 4.3.4(supports-color@5.5.0) hpagent: 1.2.0 ms: 2.1.3 secure-json-parse: 2.7.0 tslib: 2.6.2 - undici: 6.11.1 + undici: 6.19.2 transitivePeerDependencies: - supports-color @@ -2379,11 +2378,11 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@prisma/client@5.16.0(prisma@5.16.0)': + '@prisma/client@5.16.1(prisma@5.16.1)': optionalDependencies: - prisma: 5.16.0 + prisma: 5.16.1 - '@prisma/debug@5.16.0': {} + '@prisma/debug@5.16.1': {} '@prisma/debug@5.3.1': dependencies: @@ -2395,20 +2394,20 @@ snapshots: '@prisma/engines-version@5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303': {} - '@prisma/engines@5.16.0': + '@prisma/engines@5.16.1': dependencies: - '@prisma/debug': 5.16.0 + '@prisma/debug': 5.16.1 '@prisma/engines-version': 5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303 - '@prisma/fetch-engine': 5.16.0 - '@prisma/get-platform': 5.16.0 + '@prisma/fetch-engine': 5.16.1 + '@prisma/get-platform': 5.16.1 '@prisma/engines@5.3.1': {} - '@prisma/fetch-engine@5.16.0': + '@prisma/fetch-engine@5.16.1': dependencies: - '@prisma/debug': 5.16.0 + '@prisma/debug': 5.16.1 '@prisma/engines-version': 5.16.0-24.34ace0eb2704183d2c05b60b52fba5c43c13f303 - '@prisma/get-platform': 5.16.0 + '@prisma/get-platform': 5.16.1 '@prisma/fetch-engine@5.3.1': dependencies: @@ -2442,9 +2441,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@prisma/get-platform@5.16.0': + '@prisma/get-platform@5.16.1': dependencies: - '@prisma/debug': 5.16.0 + '@prisma/debug': 5.16.1 '@prisma/get-platform@5.3.1': dependencies: @@ -2519,9 +2518,9 @@ snapshots: '@tsconfig/node16@1.0.4': {} - '@tsoa/cli@6.2.0': + '@tsoa/cli@6.3.1': dependencies: - '@tsoa/runtime': 6.2.0 + '@tsoa/runtime': 6.3.0 '@types/multer': 1.4.11 fs-extra: 11.2.0 glob: 10.3.12 @@ -2529,14 +2528,14 @@ snapshots: merge-anything: 5.1.7 minimatch: 9.0.4 ts-deepmerge: 7.0.0 - typescript: 5.4.3 + typescript: 5.5.2 validator: 13.11.0 yaml: 2.4.1 yargs: 17.7.2 transitivePeerDependencies: - supports-color - '@tsoa/runtime@6.2.0': + '@tsoa/runtime@6.3.0': dependencies: '@hapi/boom': 10.0.1 '@hapi/hapi': 21.3.7 @@ -2550,16 +2549,16 @@ snapshots: '@types/accepts@1.3.7': dependencies: - '@types/node': 20.12.2 + '@types/node': 20.14.9 '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.12.2 + '@types/node': 20.14.9 '@types/connect@3.4.38': dependencies: - '@types/node': 20.12.2 + '@types/node': 20.14.9 '@types/content-disposition@0.5.8': {} @@ -2568,15 +2567,15 @@ snapshots: '@types/connect': 3.4.38 '@types/express': 4.17.21 '@types/keygrip': 1.0.6 - '@types/node': 20.12.2 + '@types/node': 20.14.9 '@types/cors@2.8.17': dependencies: - '@types/node': 20.12.2 + '@types/node': 20.14.9 '@types/cross-spawn@6.0.2': dependencies: - '@types/node': 20.12.2 + '@types/node': 20.14.9 '@types/debug@4.1.8': dependencies: @@ -2584,7 +2583,7 @@ snapshots: '@types/express-serve-static-core@4.17.43': dependencies: - '@types/node': 20.12.2 + '@types/node': 20.14.9 '@types/qs': 6.9.14 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -2615,7 +2614,7 @@ snapshots: '@types/http-errors': 2.0.4 '@types/keygrip': 1.0.6 '@types/koa-compose': 3.2.8 - '@types/node': 20.12.2 + '@types/node': 20.14.9 '@types/mime@1.3.5': {} @@ -2629,7 +2628,7 @@ snapshots: dependencies: '@types/express': 4.17.21 - '@types/node@20.12.2': + '@types/node@20.14.9': dependencies: undici-types: 5.26.5 @@ -2644,13 +2643,13 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.12.2 + '@types/node': 20.14.9 '@types/serve-static@1.15.5': dependencies: '@types/http-errors': 2.0.4 '@types/mime': 4.0.0 - '@types/node': 20.12.2 + '@types/node': 20.14.9 '@types/swagger-ui-express@4.1.6': dependencies: @@ -2833,10 +2832,6 @@ snapshots: dependencies: balanced-match: 1.0.2 - braces@3.0.2: - dependencies: - fill-range: 7.0.1 - braces@3.0.3: dependencies: fill-range: 7.1.1 @@ -2845,6 +2840,8 @@ snapshots: buffer-crc32@0.2.13: {} + buffer-crc32@1.0.0: {} + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -2889,7 +2886,7 @@ snapshots: chokidar@3.6.0: dependencies: anymatch: 3.1.3 - braces: 3.0.2 + braces: 3.0.3 glob-parent: 5.1.2 is-binary-path: 2.1.0 is-glob: 4.0.3 @@ -3167,6 +3164,8 @@ snapshots: etag@1.8.1: {} + eventemitter3@5.0.1: {} + execa@5.1.1: dependencies: cross-spawn: 7.0.3 @@ -3223,7 +3222,7 @@ snapshots: merge2: 1.4.1 micromatch: 4.0.7 - fast-jwt@4.0.0: + fast-jwt@4.0.1: dependencies: '@lukeed/ms': 2.0.2 asn1.js: 5.4.1 @@ -3238,10 +3237,6 @@ snapshots: dependencies: reusify: 1.0.4 - fill-range@7.0.1: - dependencies: - to-regex-range: 5.0.1 - fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -3621,8 +3616,6 @@ snapshots: json-parse-even-better-errors@2.3.1: {} - json-stream@1.0.0: {} - jsonfile@6.1.0: dependencies: universalify: 2.0.1 @@ -3728,21 +3721,21 @@ snapshots: minimist@1.2.8: {} - minio@7.1.3: + minio@8.0.1: dependencies: async: 3.2.5 block-stream2: 2.1.0 browser-or-node: 2.1.1 - buffer-crc32: 0.2.13 + buffer-crc32: 1.0.0 + eventemitter3: 5.0.1 fast-xml-parser: 4.3.6 ipaddr.js: 2.1.0 - json-stream: 1.0.0 lodash: 4.17.21 mime-types: 2.1.35 query-string: 7.1.3 + stream-json: 1.8.0 through2: 4.0.2 web-encoding: 1.1.5 - xml: 1.0.1 xml2js: 0.5.0 minipass@7.0.4: {} @@ -3771,7 +3764,7 @@ snapshots: dependencies: whatwg-url: 5.0.0 - nodemon@3.1.3: + nodemon@3.1.4: dependencies: chokidar: 3.6.0 debug: 4.3.4(supports-color@5.5.0) @@ -3915,26 +3908,26 @@ snapshots: possible-typed-array-names@1.0.0: {} - prettier@3.2.5: {} + prettier@3.3.2: {} - prisma-extension-kysely@2.1.0(@prisma/client@5.16.0(prisma@5.16.0)): + prisma-extension-kysely@2.1.0(@prisma/client@5.16.1(prisma@5.16.1)): dependencies: - '@prisma/client': 5.16.0(prisma@5.16.0) + '@prisma/client': 5.16.1(prisma@5.16.1) prisma-kysely@1.8.0: dependencies: '@mrleebo/prisma-ast': 0.7.0 '@prisma/generator-helper': 5.3.1 '@prisma/internals': 5.3.1 - typescript: 5.4.3 + typescript: 5.5.2 zod: 3.23.8 transitivePeerDependencies: - encoding - supports-color - prisma@5.16.0: + prisma@5.16.1: dependencies: - '@prisma/engines': 5.16.0 + '@prisma/engines': 5.16.1 process-nextick-args@2.0.1: {} @@ -4185,6 +4178,12 @@ snapshots: dependencies: internal-slot: 1.0.7 + stream-chain@2.2.5: {} + + stream-json@1.8.0: + dependencies: + stream-chain: 2.2.5 + strict-uri-encode@2.0.0: {} string-width@4.2.3: @@ -4259,7 +4258,7 @@ snapshots: swagger-ui-dist@5.13.0: {} - swagger-ui-express@5.0.0(express@4.19.2): + swagger-ui-express@5.0.1(express@4.19.2): dependencies: express: 4.19.2 swagger-ui-dist: 5.13.0 @@ -4309,21 +4308,21 @@ snapshots: ts-deepmerge@7.0.0: {} - ts-node@10.9.2(@types/node@20.12.2)(typescript@5.4.3): + ts-node@10.9.2(@types/node@20.14.9)(typescript@5.5.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.12.2 + '@types/node': 20.14.9 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.4.3 + typescript: 5.5.2 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 @@ -4331,10 +4330,10 @@ snapshots: tslib@2.6.2: {} - tsoa@6.2.0: + tsoa@6.3.1: dependencies: - '@tsoa/cli': 6.2.0 - '@tsoa/runtime': 6.2.0 + '@tsoa/cli': 6.3.1 + '@tsoa/runtime': 6.3.0 transitivePeerDependencies: - supports-color @@ -4383,7 +4382,7 @@ snapshots: is-typed-array: 1.1.13 possible-typed-array-names: 1.0.0 - typescript@5.4.3: {} + typescript@5.5.2: {} uglify-js@3.17.4: optional: true @@ -4399,7 +4398,7 @@ snapshots: undici-types@5.26.5: {} - undici@6.11.1: {} + undici@6.19.2: {} unique-string@2.0.0: dependencies: @@ -4488,8 +4487,6 @@ snapshots: sax: 1.3.0 xmlbuilder: 11.0.1 - xml@1.0.1: {} - xmlbuilder@11.0.1: {} y18n@5.0.8: {} diff --git a/src/services/minio.ts b/src/services/minio.ts index 1204c72..1a78e8d 100644 --- a/src/services/minio.ts +++ b/src/services/minio.ts @@ -37,13 +37,15 @@ export async function listObjectVersion(bucket: string, obj: string) { export async function deleteObjectAllVersion(bucket: string, obj: string) { const item = await listObjectVersion(bucket, obj); - return await new Promise((resolve, reject) => { - minio.removeObjects( - bucket, - // @ts-ignore - item.map(({ name, versionId }) => ({ name, versionId })), // type error (ts not support) - expected "string[]" - (e) => (e && reject(e)) || resolve(true), - ); + return await new Promise(async (resolve, reject) => { + await minio + .removeObjects( + bucket, + item.map(({ name, versionId }) => ({ name, versionId })), + ) + .catch((e) => reject(e)); + + resolve(true); }); } From 2bd30b735df2e00a39288737adf41e1d2acd8735 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:24:02 +0700 Subject: [PATCH 002/102] refactor: user relation --- .../migration.sql | 238 ++++++++++++++++++ prisma/schema.prisma | 222 ++++++++++------ src/app.ts | 49 ++++ src/config.json | 8 + src/controllers/branch-contact-controller.ts | 4 +- src/controllers/branch-controller.ts | 6 +- src/controllers/branch-user-controller.ts | 16 +- src/controllers/customer-branch-controller.ts | 7 +- src/controllers/customer-controller.ts | 16 +- .../employee-checkup-controller.ts | 7 +- src/controllers/employee-controller.ts | 20 +- .../employee-other-info-controller.ts | 12 +- src/controllers/employee-work-controller.ts | 30 ++- src/controllers/keycloak-controller.ts | 8 +- src/controllers/product/group-controller.ts | 6 +- src/controllers/product/product-controller.ts | 6 +- src/controllers/product/type-controller.ts | 6 +- src/controllers/service/service-controller.ts | 6 +- src/controllers/user-controller.ts | 12 +- src/controllers/work/work-controller.ts | 14 +- src/services/keycloak.ts | 99 ++++++-- 21 files changed, 607 insertions(+), 185 deletions(-) create mode 100644 prisma/migrations/20240701062318_user_relation/migration.sql create mode 100644 src/config.json diff --git a/prisma/migrations/20240701062318_user_relation/migration.sql b/prisma/migrations/20240701062318_user_relation/migration.sql new file mode 100644 index 0000000..2525562 --- /dev/null +++ b/prisma/migrations/20240701062318_user_relation/migration.sql @@ -0,0 +1,238 @@ +/* + Warnings: + + - You are about to drop the column `createdBy` on the `Branch` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `Branch` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `BranchContact` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `BranchContact` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `BranchUser` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `BranchUser` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `Customer` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `Customer` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `CustomerBranch` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `CustomerBranch` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `Employee` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `EmployeeCheckup` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `EmployeeCheckup` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `EmployeeHistory` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `EmployeeOtherInfo` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `EmployeeOtherInfo` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `EmployeeWork` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `EmployeeWork` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `Product` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `Product` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `ProductGroup` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `ProductGroup` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `ProductType` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `ProductType` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `Service` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `Service` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `User` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `User` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `Work` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `Work` table. All the data in the column will be lost. + - You are about to drop the column `createdBy` on the `WorkProduct` table. All the data in the column will be lost. + - You are about to drop the column `updatedBy` on the `WorkProduct` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "Branch" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "BranchContact" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "BranchUser" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "Customer" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "CustomerBranch" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "Employee" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "EmployeeCheckup" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "EmployeeHistory" DROP COLUMN "updatedBy", +ADD COLUMN "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, +ADD COLUMN "createdByUserId" TEXT, +ALTER COLUMN "updatedAt" DROP DEFAULT; + +-- AlterTable +ALTER TABLE "EmployeeOtherInfo" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "EmployeeWork" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "Product" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "ProductGroup" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "ProductType" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "Service" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "User" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "Work" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AlterTable +ALTER TABLE "WorkProduct" DROP COLUMN "createdBy", +DROP COLUMN "updatedBy", +ADD COLUMN "createdByUserId" TEXT, +ADD COLUMN "updatedByUserId" TEXT; + +-- AddForeignKey +ALTER TABLE "Branch" ADD CONSTRAINT "Branch_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Branch" ADD CONSTRAINT "Branch_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BranchContact" ADD CONSTRAINT "BranchContact_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BranchContact" ADD CONSTRAINT "BranchContact_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BranchUser" ADD CONSTRAINT "BranchUser_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "BranchUser" ADD CONSTRAINT "BranchUser_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "User" ADD CONSTRAINT "User_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "User" ADD CONSTRAINT "User_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Customer" ADD CONSTRAINT "Customer_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Customer" ADD CONSTRAINT "Customer_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CustomerBranch" ADD CONSTRAINT "CustomerBranch_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CustomerBranch" ADD CONSTRAINT "CustomerBranch_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Employee" ADD CONSTRAINT "Employee_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Employee" ADD CONSTRAINT "Employee_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EmployeeHistory" ADD CONSTRAINT "EmployeeHistory_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EmployeeCheckup" ADD CONSTRAINT "EmployeeCheckup_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EmployeeCheckup" ADD CONSTRAINT "EmployeeCheckup_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EmployeeWork" ADD CONSTRAINT "EmployeeWork_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EmployeeWork" ADD CONSTRAINT "EmployeeWork_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EmployeeOtherInfo" ADD CONSTRAINT "EmployeeOtherInfo_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "EmployeeOtherInfo" ADD CONSTRAINT "EmployeeOtherInfo_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Service" ADD CONSTRAINT "Service_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Service" ADD CONSTRAINT "Service_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Work" ADD CONSTRAINT "Work_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Work" ADD CONSTRAINT "Work_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WorkProduct" ADD CONSTRAINT "WorkProduct_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "WorkProduct" ADD CONSTRAINT "WorkProduct_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProductGroup" ADD CONSTRAINT "ProductGroup_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProductGroup" ADD CONSTRAINT "ProductGroup_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProductType" ADD CONSTRAINT "ProductType_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ProductType" ADD CONSTRAINT "ProductType_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Product" ADD CONSTRAINT "Product_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Product" ADD CONSTRAINT "Product_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 3912749..f60e04e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -22,7 +22,7 @@ model Menu { createdBy String? createdAt DateTime @default(now()) - updatedBy String? + updatedBy String? updatedAt DateTime @updatedAt parent Menu? @relation(name: "MenuRelation", fields: [parentId], references: [id]) @@ -45,7 +45,7 @@ model RoleMenuPermission { createdBy String? createdAt DateTime @default(now()) - updatedBy String? + updatedBy String? updatedAt DateTime @updatedAt } @@ -62,7 +62,7 @@ model UserMenuPermission { createdBy String? createdAt DateTime @default(now()) - updatedBy String? + updatedBy String? updatedAt DateTime @updatedAt } @@ -77,7 +77,7 @@ model MenuComponent { createdBy String? createdAt DateTime @default(now()) - updatedBy String? + updatedBy String? updatedAt DateTime @updatedAt roleMenuComponentPermission RoleMenuComponentPermission[] userMennuComponentPermission UserMenuComponentPermission[] @@ -94,7 +94,7 @@ model RoleMenuComponentPermission { createdBy String? createdAt DateTime @default(now()) - updatedBy String? + updatedBy String? updatedAt DateTime @updatedAt } @@ -116,7 +116,7 @@ model UserMenuComponentPermission { createdBy String? createdAt DateTime @default(now()) - updatedBy String? + updatedBy String? updatedAt DateTime @updatedAt } @@ -127,7 +127,7 @@ model Province { createdBy String? createdAt DateTime @default(now()) - updatedBy String? + updatedBy String? updatedAt DateTime @updatedAt district District[] @@ -148,7 +148,7 @@ model District { createdBy String? createdAt DateTime @default(now()) - updatedBy String? + updatedBy String? updatedAt DateTime @updatedAt subDistrict SubDistrict[] @@ -169,7 +169,7 @@ model SubDistrict { createdBy String? createdAt DateTime @default(now()) - updatedBy String? + updatedBy String? updatedAt DateTime @updatedAt branch Branch[] @@ -220,10 +220,12 @@ model Branch { status Status @default(CREATED) statusOrder Int @default(0) - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "BranchCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "BranchUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? branch Branch[] @relation(name: "HeadOfficeRelation") contact BranchContact[] @@ -237,10 +239,12 @@ model BranchContact { branch Branch @relation(fields: [branchId], references: [id], onDelete: Cascade) branchId String - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "BranchContactCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "BranchContactUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? } model BranchUser { @@ -252,10 +256,12 @@ model BranchUser { user User @relation(fields: [userId], references: [id], onDelete: Cascade) userId String - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "BranchUserCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "BranchUserUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? } enum UserType { @@ -321,15 +327,51 @@ model User { status Status @default(CREATED) statusOrder Int @default(0) - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "UserCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "UserUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? branch BranchUser[] userMenuPermission UserMenuPermission[] userMenuComponentPermission UserMenuComponentPermission[] - employeeHistory EmployeeHistory[] + + userCreated User[] @relation("UserCreatedByUser") + userUpdated User[] @relation("UserUpdatedByUser") + branchCreated Branch[] @relation("BranchCreatedByUser") + branchUpdated Branch[] @relation("BranchUpdatedByUser") + branchContactCreated BranchContact[] @relation("BranchContactCreatedByUser") + branchContactUpdated BranchContact[] @relation("BranchContactUpdatedByUser") + branchUserCreated BranchUser[] @relation("BranchUserCreatedByUser") + branchUserUpdated BranchUser[] @relation("BranchUserUpdatedByUser") + customerCreated Customer[] @relation("CustomerCreatedByUser") + customerUpdated Customer[] @relation("CustomerUpdatedByUser") + customerBranchCreated CustomerBranch[] @relation("CustomerBranchCreatedByUser") + customerBranchUpdated CustomerBranch[] @relation("CustomerBranchUpdatedByUser") + emplyeeCreated Employee[] @relation("EmployeeCreatedByUser") + employeUpdated Employee[] @relation("EmployeeUpdatedByUser") + employeeHistoryCreated EmployeeHistory[] @relation("EmployeeHistoryCreatedByUser") + employeeHistoryUpdated EmployeeHistory[] @relation("EmployeeHistoryUpdatedByUser") + employeeCheckupCreated EmployeeCheckup[] @relation("EmployeeCheckupCreatedByUser") + employeeCheckupUpdated EmployeeCheckup[] @relation("EmployeeCheckupUpdatedByUser") + employeeWorkCreated EmployeeWork[] @relation("EmployeeWorkCreatedByUser") + employeeWorkUpdated EmployeeWork[] @relation("EmployeeWorkUpdatedByUser") + employeeOtherInfoCreated EmployeeOtherInfo[] @relation("EmployeeOtherInfoCreatedByUser") + employeeOtherInfoUpdated EmployeeOtherInfo[] @relation("EmployeeOtherInfoUpdatedByUser") + serviceCreated Service[] @relation("ServiceCreatedByUser") + serviceUpdated Service[] @relation("ServiceUpdatedByUser") + workCreated Work[] @relation("WorkCreatedByUser") + workUpdated Work[] @relation("WorkUpdatedByUser") + workProductCreated WorkProduct[] @relation("WorkProductCreatedByUser") + workProductUpdated WorkProduct[] @relation("WorkProductUpdatedByUser") + productGroupCreated ProductGroup[] @relation("ProductGroupCreatedByUser") + productGroupUpdated ProductGroup[] @relation("ProductGroupUpdatedByUser") + productTypeCreated ProductType[] @relation("ProductTypeCreatedByUser") + productTypeUpdated ProductType[] @relation("ProductTypeUpdatedByUser") + productCreated Product[] @relation("ProductCreatedByUser") + productUpdated Product[] @relation("ProductUpdatedByUser") } enum CustomerType { @@ -350,10 +392,12 @@ model Customer { status Status @default(CREATED) statusOrder Int @default(0) - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "CustomerCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "CustomerUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? branch CustomerBranch[] } @@ -405,10 +449,12 @@ model CustomerBranch { status Status @default(CREATED) statusOrder Int @default(0) - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "CustomerBranchCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "CustomerBranchUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? employee Employee[] } @@ -465,10 +511,12 @@ model Employee { status Status @default(CREATED) statusOrder Int @default(0) - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "EmployeeCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "EmployeeUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? employeeCheckup EmployeeCheckup[] employeeWork EmployeeWork[] @@ -485,10 +533,12 @@ model EmployeeHistory { timestamp DateTime @default(now()) + createdAt DateTime @default(now()) + createdBy User? @relation(name: "EmployeeHistoryCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "EmployeeHistoryUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) updatedByUserId String? - updatedByUser User? @relation(fields: [updatedByUserId], references: [id]) - updatedBy String? - updatedAt DateTime @default(now()) masterId String master Employee @relation(fields: [masterId], references: [id], onDelete: Cascade) @@ -513,10 +563,12 @@ model EmployeeCheckup { coverageStartDate DateTime? coverageExpireDate DateTime? - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "EmployeeCheckupCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "EmployeeCheckupUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? } model EmployeeWork { @@ -535,10 +587,12 @@ model EmployeeWork { workEndDate DateTime? remark String? - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "EmployeeWorkCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "EmployeeWorkUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? } model EmployeeOtherInfo { @@ -560,10 +614,12 @@ model EmployeeOtherInfo { motherFirstNameEN String? motherLastNameEN String? - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "EmployeeOtherInfoCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "EmployeeOtherInfoUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? } model Service { @@ -579,10 +635,12 @@ model Service { work Work[] - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "ServiceCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "ServiceUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? } model Work { @@ -598,10 +656,12 @@ model Work { service Service? @relation(fields: [serviceId], references: [id], onDelete: Cascade) serviceId String? - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "WorkCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "WorkUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? productOnWork WorkProduct[] } @@ -613,10 +673,12 @@ model WorkProduct { product Product @relation(fields: [productId], references: [id], onDelete: Cascade) productId String - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "WorkProductCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "WorkProductUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? @@id([workId, productId]) } @@ -632,10 +694,12 @@ model ProductGroup { status Status @default(CREATED) statusOrder Int @default(0) - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "ProductGroupCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "ProductGroupUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? type ProductType[] } @@ -651,10 +715,12 @@ model ProductType { status Status @default(CREATED) statusOrder Int @default(0) - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "ProductTypeCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "ProductTypeUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? productGroup ProductGroup @relation(fields: [productGroupId], references: [id], onDelete: Cascade) productGroupId String @@ -683,8 +749,10 @@ model Product { workProduct WorkProduct[] - createdBy String? - createdAt DateTime @default(now()) - updatedBy String? - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + createdBy User? @relation(name: "ProductCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "ProductUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? } diff --git a/src/app.ts b/src/app.ts index d9c0a8f..b933540 100644 --- a/src/app.ts +++ b/src/app.ts @@ -6,6 +6,8 @@ import swaggerDocument from "./swagger.json"; import error from "./middlewares/error"; import { RegisterRoutes } from "./routes"; import logMiddleware from "./middlewares/log"; +import { addUserRoles, createUser, getRoleByName, listUser } from "./services/keycloak"; +import prisma from "./db"; const APP_HOST = process.env.APP_HOST || "0.0.0.0"; const APP_PORT = +(process.env.APP_PORT || 3000); @@ -13,6 +15,53 @@ const APP_PORT = +(process.env.APP_PORT || 3000); (async () => { const app = express(); + let users = await (async () => { + let list = await listUser(); + while (!list) { + list = await listUser(); + await new Promise((resolve) => setTimeout(resolve, 1000)); + } + return list; + })(); + + if (users.length === 0) { + const role = await getRoleByName("system"); + const userId = await createUser("admin", "1234", { + firstName: "Admin", + lastName: "System", + email: "admin@jws.local", + requiredActions: ["UPDATE_PASSWORD"], + enabled: true, + }); + + if (!userId || typeof userId !== "string") { + throw new Error("Error create user with keycloak service."); + } + + if (role) await addUserRoles(userId, [role]); + + await prisma.user.create({ + include: { province: true, district: true, subDistrict: true }, + data: { + id: userId, + email: "admin@jws.local", + gender: "", + address: "", + addressEN: "", + zipCode: "", + userType: "USER", + userRole: "system", + telephoneNo: "", + firstName: "Admin", + firstNameEN: "Admin", + lastName: "System", + lastNameEN: "System", + statusOrder: 0, + username: "admin", + }, + }); + } + app.use(cors()); app.use(json()); app.use(urlencoded({ extended: true })); diff --git a/src/config.json b/src/config.json new file mode 100644 index 0000000..88ff9cb --- /dev/null +++ b/src/config.json @@ -0,0 +1,8 @@ +{ + "branch": { + "maxHeadOfficeBranch": 10 + }, + "personnel": { + "type": ["USER", "MESSENGER", "DELEGATE", "AGENCY"] + } +} diff --git a/src/controllers/branch-contact-controller.ts b/src/controllers/branch-contact-controller.ts index d98c966..b46e8cb 100644 --- a/src/controllers/branch-contact-controller.ts +++ b/src/controllers/branch-contact-controller.ts @@ -79,7 +79,7 @@ export class BranchContactController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "Branch cannot be found.", "branchBadReq"); } const record = await prisma.branchContact.create({ - data: { ...body, branchId, createdBy: req.user.name, updatedBy: req.user.name }, + data: { ...body, branchId, createdByUserId: req.user.sub, updatedByUserId: req.user.sub }, }); this.setStatus(HttpStatus.CREATED); @@ -107,7 +107,7 @@ export class BranchContactController extends Controller { } const record = await prisma.branchContact.update({ - data: { ...body, updatedBy: req.user.name }, + data: { ...body, updatedByUserId: req.user.sub }, where: { id: contactId, branchId }, }); diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index 50bce01..e5516a4 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -286,8 +286,8 @@ export class BranchController extends Controller { district: { connect: districtId ? { id: districtId } : undefined }, subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined }, headOffice: { connect: headOfficeId ? { id: headOfficeId } : undefined }, - createdBy: req.user.name, - updatedBy: req.user.name, + createdBy: { connect: { id: req.user.sub } }, + updatedBy: { connect: { id: req.user.sub } }, }, }); }, @@ -403,7 +403,7 @@ export class BranchController extends Controller { connect: headOfficeId ? { id: headOfficeId } : undefined, disconnect: headOfficeId === null || undefined, }, - updatedBy: req.user.name, + updatedBy: { connect: { id: req.user.sub } }, }, where: { id: branchId }, }); diff --git a/src/controllers/branch-user-controller.ts b/src/controllers/branch-user-controller.ts index 7dfed95..2d8100c 100644 --- a/src/controllers/branch-user-controller.ts +++ b/src/controllers/branch-user-controller.ts @@ -1,4 +1,4 @@ -import { Branch, Prisma, Status, User, UserType } from "@prisma/client"; +import { Branch, Prisma, Status, User } from "@prisma/client"; import { Body, Controller, @@ -113,11 +113,7 @@ export class BranchUserController extends Controller { ]); if (!branch) { - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Branch cannot be found.", - "branchBadReq", - ); + throw new HttpError(HttpStatus.BAD_REQUEST, "Branch cannot be found.", "branchBadReq"); } if (user.length !== body.user.length) { @@ -139,8 +135,8 @@ export class BranchUserController extends Controller { .map((v) => ({ branchId, userId: v.id, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, })), }), ]); @@ -249,8 +245,8 @@ export class UserBranchController extends Controller { .map((v) => ({ branchId: v.id, userId, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, })), }); diff --git a/src/controllers/customer-branch-controller.ts b/src/controllers/customer-branch-controller.ts index d07d7b4..6692cb8 100644 --- a/src/controllers/customer-branch-controller.ts +++ b/src/controllers/customer-branch-controller.ts @@ -294,8 +294,8 @@ export class CustomerBranchController extends Controller { province: { connect: provinceId ? { id: provinceId } : undefined }, district: { connect: districtId ? { id: districtId } : undefined }, subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined }, - createdBy: req.user.name, - updatedBy: req.user.name, + createdBy: { connect: { id: req.user.sub } }, + updatedBy: { connect: { id: req.user.sub } }, }, }); }, @@ -380,8 +380,7 @@ export class CustomerBranchController extends Controller { connect: subDistrictId ? { id: subDistrictId } : undefined, disconnect: subDistrictId === null || undefined, }, - createdBy: req.user.name, - updatedBy: req.user.name, + updatedBy: { connect: { id: req.user.sub } }, }, }); diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index 23b1e8d..ad4b756 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -305,13 +305,13 @@ export class CustomerController extends Controller { ...v, branchNo: i + 1, code: `${last.key.slice(9)}${last.value.toString().padStart(6, "0")}-${(i + 1).toString().padStart(2, "0")}`, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, })) || [], }, }, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, }, }); }, @@ -436,20 +436,20 @@ export class CustomerController extends Controller { ...v, branchNo: i + 1, code: `${customer.code}-${(i + 1).toString().padStart(2, "0")}`, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, id: undefined, }, update: { ...v, branchNo: i + 1, code: `${customer.code}-${(i + 1).toString().padStart(2, "0")}`, - updatedBy: req.user.name, + updatedByUserId: req.user.sub, }, })), }) || undefined, - updatedBy: req.user.name, + updatedByUserId: req.user.sub, }, }) .then((v) => { diff --git a/src/controllers/employee-checkup-controller.ts b/src/controllers/employee-checkup-controller.ts index 21fb896..e3ee273 100644 --- a/src/controllers/employee-checkup-controller.ts +++ b/src/controllers/employee-checkup-controller.ts @@ -90,8 +90,8 @@ export class EmployeeCheckupController extends Controller { ...rest, province: { connect: provinceId ? { id: provinceId } : undefined }, employee: { connect: { id: employeeId } }, - createdBy: req.user.name, - updatedBy: req.user.name, + createdBy: { connect: { id: req.user.sub } }, + updatedBy: { connect: { id: req.user.sub } }, }, }); @@ -142,8 +142,7 @@ export class EmployeeCheckupController extends Controller { data: { ...rest, province: { connect: provinceId ? { id: provinceId } : undefined }, - createdBy: req.user.name, - updatedBy: req.user.name, + updatedBy: { connect: { id: req.user.sub } }, }, }); diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts index c366aa2..0739099 100644 --- a/src/controllers/employee-controller.ts +++ b/src/controllers/employee-controller.ts @@ -418,8 +418,8 @@ export class EmployeeController extends Controller { district: { connect: districtId ? { id: districtId } : undefined }, subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined }, customerBranch: { connect: { id: customerBranchId } }, - createdBy: req.user.name, - updatedBy: req.user.name, + createdBy: { connect: { id: req.user.sub } }, + updatedBy: { connect: { id: req.user.sub } }, }, }); }, @@ -578,13 +578,13 @@ export class EmployeeController extends Controller { where: { id: v.id || "" }, create: { ...v, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, id: undefined, }, update: { ...v, - updatedBy: req.user.name, + updatedByUserId: req.user.sub, }, })), } @@ -602,13 +602,13 @@ export class EmployeeController extends Controller { create: { ...v, provinceId: !v.provinceId ? undefined : v.provinceId, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, id: undefined, }, update: { ...v, - updatedBy: req.user.name, + updatedByUserId: req.user.sub, }, })), } @@ -631,8 +631,8 @@ export class EmployeeController extends Controller { connect: subDistrictId ? { id: subDistrictId } : undefined, disconnect: subDistrictId === null || undefined, }, - createdBy: req.user.name, - updatedBy: req.user.name, + createdBy: { connect: { id: req.user.sub } }, + updatedBy: { connect: { id: req.user.sub } }, }, }); }); diff --git a/src/controllers/employee-other-info-controller.ts b/src/controllers/employee-other-info-controller.ts index a479de7..023f392 100644 --- a/src/controllers/employee-other-info-controller.ts +++ b/src/controllers/employee-other-info-controller.ts @@ -51,18 +51,14 @@ export class EmployeeOtherInfo extends Controller { @Body() body: EmployeeOtherInfoPayload, ) { if (!(await prisma.employee.findUnique({ where: { id: employeeId } }))) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Employee cannot be found.", - "employeeBadReq", - ); + throw new HttpError(HttpStatus.BAD_REQUEST, "Employee cannot be found.", "employeeBadReq"); const record = await prisma.employeeOtherInfo.create({ data: { ...body, employee: { connect: { id: employeeId } }, - createdBy: req.user.name, - updatedBy: req.user.name, + createdBy: { connect: { id: req.user.sub } }, + updatedBy: { connect: { id: req.user.sub } }, }, }); @@ -88,7 +84,7 @@ export class EmployeeOtherInfo extends Controller { const record = await prisma.employeeOtherInfo.update({ where: { id: otherInfoId, employeeId }, - data: { ...body, createdBy: req.user.name, updatedBy: req.user.name }, + data: { ...body, updatedByUserId: req.user.sub }, }); this.setStatus(HttpStatus.CREATED); diff --git a/src/controllers/employee-work-controller.ts b/src/controllers/employee-work-controller.ts index 85c12f4..de90d1d 100644 --- a/src/controllers/employee-work-controller.ts +++ b/src/controllers/employee-work-controller.ts @@ -46,7 +46,11 @@ export class EmployeeWorkController extends Controller { where: { id: workId, employeeId }, }); if (!record) { - throw new HttpError(HttpStatus.NOT_FOUND, "Employee work cannot be found.", "employeeWorkNotFound"); + throw new HttpError( + HttpStatus.NOT_FOUND, + "Employee work cannot be found.", + "employeeWorkNotFound", + ); } return record; } @@ -58,18 +62,14 @@ export class EmployeeWorkController extends Controller { @Body() body: EmployeeWorkPayload, ) { if (!(await prisma.employee.findUnique({ where: { id: employeeId } }))) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Employee cannot be found.", - "employeeBadReq", - ); + throw new HttpError(HttpStatus.BAD_REQUEST, "Employee cannot be found.", "employeeBadReq"); const record = await prisma.employeeWork.create({ data: { ...body, employee: { connect: { id: employeeId } }, - createdBy: req.user.name, - updatedBy: req.user.name, + createdBy: { connect: { id: req.user.sub } }, + updatedBy: { connect: { id: req.user.sub } }, }, }); @@ -86,12 +86,16 @@ export class EmployeeWorkController extends Controller { @Body() body: EmployeeWorkPayload, ) { if (!(await prisma.employeeWork.findUnique({ where: { id: workId, employeeId } }))) { - throw new HttpError(HttpStatus.NOT_FOUND, "Employee work cannot be found.", "employeeWorkNotFound"); + throw new HttpError( + HttpStatus.NOT_FOUND, + "Employee work cannot be found.", + "employeeWorkNotFound", + ); } const record = await prisma.employeeWork.update({ where: { id: workId, employeeId }, - data: { ...body, createdBy: req.user.name, updatedBy: req.user.name }, + data: { ...body, updatedByUserId: req.user.sub }, }); this.setStatus(HttpStatus.CREATED); @@ -104,7 +108,11 @@ export class EmployeeWorkController extends Controller { const record = await prisma.employeeWork.findFirst({ where: { id: workId, employeeId } }); if (!record) { - throw new HttpError(HttpStatus.NOT_FOUND, "Employee work cannot be found.", "employeeWorkNotFound"); + throw new HttpError( + HttpStatus.NOT_FOUND, + "Employee work cannot be found.", + "employeeWorkNotFound", + ); } return await prisma.employeeWork.delete({ where: { id: workId, employeeId } }); diff --git a/src/controllers/keycloak-controller.ts b/src/controllers/keycloak-controller.ts index 69ed2de..6b827c8 100644 --- a/src/controllers/keycloak-controller.ts +++ b/src/controllers/keycloak-controller.ts @@ -4,7 +4,7 @@ import { createUser, deleteUser, editUser, - getRoles, + listRole, removeUserRoles, } from "../services/keycloak"; @@ -38,7 +38,7 @@ export class KeycloakController extends Controller { @Get("role") async getRole() { - const role = await getRoles(); + const role = await listRole(); if (Array.isArray(role)) return role.filter( (a) => @@ -49,7 +49,7 @@ export class KeycloakController extends Controller { @Post("{userId}/role") async addRole(@Path() userId: string, @Body() body: { role: string[] }) { - const list = await getRoles(); + const list = await listRole(); if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server."); @@ -63,7 +63,7 @@ export class KeycloakController extends Controller { @Delete("{userId}/role/{roleId}") async deleteRole(@Path() userId: string, @Path() roleId: string) { - const list = await getRoles(); + const list = await listRole(); if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server."); diff --git a/src/controllers/product/group-controller.ts b/src/controllers/product/group-controller.ts index 2d676c4..2d93e22 100644 --- a/src/controllers/product/group-controller.ts +++ b/src/controllers/product/group-controller.ts @@ -143,8 +143,8 @@ export class ProductGroup extends Controller { ...body, statusOrder: +(body.status === "INACTIVE"), code: `G${last.value.toString().padStart(2, "0")}`, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, }, }); }, @@ -171,7 +171,7 @@ export class ProductGroup extends Controller { } const record = await prisma.productGroup.update({ - data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedBy: req.user.name }, + data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedByUserId: req.user.sub }, where: { id: groupId }, }); diff --git a/src/controllers/product/product-controller.ts b/src/controllers/product/product-controller.ts index 340ab9f..e8d8e75 100644 --- a/src/controllers/product/product-controller.ts +++ b/src/controllers/product/product-controller.ts @@ -173,8 +173,8 @@ export class ProductController extends Controller { ...body, statusOrder: +(body.status === "INACTIVE"), code: `${body.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, }, }); }, @@ -230,7 +230,7 @@ export class ProductController extends Controller { } const record = await prisma.product.update({ - data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedBy: req.user.name }, + data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedByUserId: req.user.sub }, where: { id: productId }, }); diff --git a/src/controllers/product/type-controller.ts b/src/controllers/product/type-controller.ts index 786c1da..36a9a12 100644 --- a/src/controllers/product/type-controller.ts +++ b/src/controllers/product/type-controller.ts @@ -132,8 +132,8 @@ export class ProductType extends Controller { ...body, statusOrder: +(body.status === "INACTIVE"), code: `T${productGroup.code}${last.value.toString().padStart(2, "0")}`, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, }, }); }, @@ -178,7 +178,7 @@ export class ProductType extends Controller { } const record = await prisma.productType.update({ - data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedBy: req.user.name }, + data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedByUserId: req.user.sub }, where: { id: typeId }, }); diff --git a/src/controllers/service/service-controller.ts b/src/controllers/service/service-controller.ts index a565620..c99d1af 100644 --- a/src/controllers/service/service-controller.ts +++ b/src/controllers/service/service-controller.ts @@ -245,8 +245,8 @@ export class ServiceController extends Controller { statusOrder: +(body.status === "INACTIVE"), code: `${body.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`, work: { connect: workList.map((v) => ({ id: v.id })) }, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, }, }); }, @@ -308,7 +308,7 @@ export class ServiceController extends Controller { deleteMany: {}, connect: workList.map((v) => ({ id: v.id })), }, - updatedBy: req.user.name, + updatedByUserId: req.user.sub, }, where: { id: serviceId }, }); diff --git a/src/controllers/user-controller.ts b/src/controllers/user-controller.ts index 9f88d73..89855ab 100644 --- a/src/controllers/user-controller.ts +++ b/src/controllers/user-controller.ts @@ -24,7 +24,7 @@ import { createUser, deleteUser, editUser, - getRoles, + listRole, getUserRoles, removeUserRoles, } from "../services/keycloak"; @@ -256,7 +256,7 @@ export class UserController extends Controller { const { provinceId, districtId, subDistrictId, username, ...rest } = body; - let list = await getRoles(); + let list = await listRole(); if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server."); if (Array.isArray(list)) { @@ -297,8 +297,8 @@ export class UserController extends Controller { province: { connect: provinceId ? { id: provinceId } : undefined }, district: { connect: districtId ? { id: districtId } : undefined }, subDistrict: { connect: subDistrictId ? { id: subDistrictId } : undefined }, - createdBy: req.user.name, - updatedBy: req.user.name, + createdBy: { connect: { id: req.user.sub } }, + updatedBy: { connect: { id: req.user.sub } }, }, }); @@ -355,7 +355,7 @@ export class UserController extends Controller { let userRole: string | undefined; if (body.userRole) { - let list = await getRoles(); + let list = await listRole(); if (!Array.isArray(list)) throw new Error("Failed. Cannot get role(s) data from the server."); if (Array.isArray(list)) { @@ -438,7 +438,7 @@ export class UserController extends Controller { connect: subDistrictId ? { id: subDistrictId } : undefined, disconnect: subDistrictId === null || undefined, }, - updatedBy: req.user.name, + updatedBy: { connect: { id: req.user.sub } }, }, where: { id: userId }, }); diff --git a/src/controllers/work/work-controller.ts b/src/controllers/work/work-controller.ts index 24577b7..f73f017 100644 --- a/src/controllers/work/work-controller.ts +++ b/src/controllers/work/work-controller.ts @@ -183,13 +183,13 @@ export class WorkController extends Controller { data: productId.map((v, i) => ({ order: i + 1, productId: v, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, })), }, }, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, }, }); @@ -281,13 +281,13 @@ export class WorkController extends Controller { create: { order: i + 1, productId: v, - createdBy: req.user.name, - updatedBy: req.user.name, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, }, })), } : undefined, - updatedBy: req.user.name, + updatedByUserId: req.user.sub, }, }); diff --git a/src/services/keycloak.ts b/src/services/keycloak.ts index 984817f..4690e1f 100644 --- a/src/services/keycloak.ts +++ b/src/services/keycloak.ts @@ -59,6 +59,61 @@ export async function getToken() { return token; } +/** + * Get keycloak user list + * + * @returns user list if success, false otherwise. + */ +export async function listUser(search = "", page = 1, pageSize = 30) { + const res = await fetch( + `${KC_URL}/admin/realms/${KC_REALM}/users?first=${(page - 1) * pageSize}&max=${pageSize}`.concat( + !!search ? `&search=${search}` : "", + ), + { + headers: { + authorization: `Bearer ${await getToken()}`, + "content-type": `application/json`, + }, + }, + ).catch((e) => console.log("Keycloak Error: ", e)); + + if (!res) return; + if (!res.ok) { + return console.error("Keycloak Error Response: ", await res.json()); + } + return ((await res.json()) as any[]).map((v: Record) => ({ + id: v.id, + username: v.username, + firstName: v.firstName, + lastName: v.lastName, + email: v.email, + attributes: v.attributes, + })); +} + +/** + * Count user in the system. Can be use for pagination purpose. + * + * @returns numer of user on success. + */ +export async function countUser(search = "") { + const res = await fetch( + `${KC_URL}/admin/realms/${KC_REALM}/users/count`.concat(!!search ? `?search=${search}` : ""), + { + headers: { + authorization: `Bearer ${await getToken()}`, + "content-type": `application/json`, + }, + }, + ).catch((e) => console.log("Keycloak Error: ", e)); + + if (!res) return; + if (!res.ok) { + return console.error("Keycloak Error Response: ", await res.json()); + } + return (await res.json()) as number; +} + /** * Create keycloak user by given username and password with roles * @@ -151,36 +206,42 @@ export async function deleteUser(userId: string) { /** * Get roles list or specific role data - * - * Client must have permission to get realms roles - * - * @returns role's info (array if not specify name) if success, null if not found, false otherwise. */ -export async function getRoles(name?: string) { - const res = await fetch( - `${KC_URL}/admin/realms/${KC_REALM}/roles`.concat((name && `/${name}`) || ""), - { - // prettier-ignore - headers: { - "authorization": `Bearer ${await getToken()}`, +export async function listRole() { + const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/roles`, { + headers: { + authorization: `Bearer ${await getToken()}`, }, - }, - ).catch((e) => console.log(e)); + }).catch((e) => console.log(e)); - if (!res) return false; + if (!res) return; if (!res.ok && res.status !== 404) { - return Boolean(console.error("Keycloak Error Response: ", await res.json())); + return console.error("Keycloak Error Response: ", await res.json()); } if (res.status === 404) { return null; } - const data = await res.json(); + const data = (await res.json()) as any[]; - if (Array.isArray(data)) { - return data.map((v: Record) => ({ id: v.id, name: v.name })); + return data.map((v: Record) => ({ id: v.id, name: v.name })); +} + +export async function getRoleByName(name: string) { + const res = await fetch(`${KC_URL}/admin/realms/${KC_REALM}/roles`.concat(`/${name}`), { + headers: { + authorization: `Bearer ${await getToken()}`, + }, + }).catch((e) => console.log(e)); + + if (!res) return; + if (!res.ok && res.status !== 404) { + return console.error("Keycloak Error Response: ", await res.json()); } + if (res.status === 404) return null; + + const data = (await res.json()) as any; return { id: data.id, @@ -285,7 +346,7 @@ export async function removeUserRoles(userId: string, roles: { id: string; name: export default { createUser, - getRoles, + listRole, addUserRoles, removeUserRoles, }; From 9f3b8cd290ac355149d0419f5009d3e30d15f17c Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 1 Jul 2024 14:38:07 +0700 Subject: [PATCH 003/102] feat: add user relation on query --- src/controllers/branch-contact-controller.ts | 6 +++ src/controllers/branch-controller.ts | 10 ++++ src/controllers/customer-branch-controller.ts | 47 ++++++++++++------- src/controllers/customer-controller.ts | 8 ++++ .../employee-checkup-controller.ts | 24 ++++++++-- src/controllers/employee-controller.ts | 16 ++++++- .../employee-other-info-controller.ts | 20 +++++++- src/controllers/employee-work-controller.ts | 24 +++++++++- src/controllers/product/group-controller.ts | 18 ++++++- src/controllers/product/product-controller.ts | 32 ++++++++++++- src/controllers/product/type-controller.ts | 24 +++++++++- src/controllers/service/service-controller.ts | 20 +++++++- src/controllers/user-controller.ts | 34 +++++++++----- src/controllers/work/work-controller.ts | 17 ++++++- 14 files changed, 259 insertions(+), 41 deletions(-) diff --git a/src/controllers/branch-contact-controller.ts b/src/controllers/branch-contact-controller.ts index b46e8cb..b2cab4e 100644 --- a/src/controllers/branch-contact-controller.ts +++ b/src/controllers/branch-contact-controller.ts @@ -38,6 +38,7 @@ export class BranchContactController extends Controller { ) { const [result, total] = await prisma.$transaction([ prisma.branchContact.findMany({ + include: { createdBy: true, updatedBy: true }, orderBy: { createdAt: "asc" }, where: { branchId }, take: pageSize, @@ -79,6 +80,10 @@ export class BranchContactController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "Branch cannot be found.", "branchBadReq"); } const record = await prisma.branchContact.create({ + include: { + createdBy: true, + updatedBy: true, + }, data: { ...body, branchId, createdByUserId: req.user.sub, updatedByUserId: req.user.sub }, }); @@ -107,6 +112,7 @@ export class BranchContactController extends Controller { } const record = await prisma.branchContact.update({ + include: { createdBy: true, updatedBy: true }, data: { ...body, updatedByUserId: req.user.sub }, where: { id: contactId, branchId }, }); diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index e5516a4..e2f176b 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -173,6 +173,8 @@ export class BranchController extends Controller { subDistrict: true, }, }, + createdBy: true, + updatedBy: true, }, where, take: pageSize, @@ -195,6 +197,8 @@ export class BranchController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, branch: includeSubBranch && { include: { province: true, @@ -276,6 +280,8 @@ export class BranchController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, }, data: { ...rest, @@ -442,6 +448,8 @@ export class BranchController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, }, where: { id: branchId }, }); @@ -466,6 +474,8 @@ export class BranchController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, }, where: { id: branchId }, }); diff --git a/src/controllers/customer-branch-controller.ts b/src/controllers/customer-branch-controller.ts index 6692cb8..020d5fb 100644 --- a/src/controllers/customer-branch-controller.ts +++ b/src/controllers/customer-branch-controller.ts @@ -158,6 +158,8 @@ export class CustomerBranchController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, _count: true, }, where, @@ -177,6 +179,8 @@ export class CustomerBranchController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, }, where: { id: branchId }, }); @@ -213,6 +217,8 @@ export class CustomerBranchController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, }, where, take: pageSize, @@ -284,6 +290,8 @@ export class CustomerBranchController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, }, data: { ...rest, @@ -363,6 +371,8 @@ export class CustomerBranchController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, }, data: { ...rest, @@ -411,27 +421,32 @@ export class CustomerBranchController extends Controller { ); } - return await prisma.customerBranch.delete({ where: { id: branchId } }).then((v) => { - new Promise((resolve, reject) => { - const item: string[] = []; + return await prisma.customerBranch + .delete({ + include: { createdBy: true, updatedBy: true }, + where: { id: branchId }, + }) + .then((v) => { + new Promise((resolve, reject) => { + const item: string[] = []; - const stream = minio.listObjectsV2( - MINIO_BUCKET, - `${attachmentLocation(record.customerId, branchId)}/`, - ); + const stream = minio.listObjectsV2( + MINIO_BUCKET, + `${attachmentLocation(record.customerId, branchId)}/`, + ); - stream.on("data", (v) => v && v.name && item.push(v.name)); - stream.on("end", () => resolve(item)); - stream.on("error", () => reject(new Error("MinIO error."))); - }).then((list) => { - list.map(async (v) => { - await minio.removeObject(MINIO_BUCKET, v, { - forceDelete: true, + stream.on("data", (v) => v && v.name && item.push(v.name)); + stream.on("end", () => resolve(item)); + stream.on("error", () => reject(new Error("MinIO error."))); + }).then((list) => { + list.map(async (v) => { + await minio.removeObject(MINIO_BUCKET, v, { + forceDelete: true, + }); }); }); + return v; }); - return v; - }); } } diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index ad4b756..2b55fa4 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -174,6 +174,8 @@ export class CustomerController extends Controller { }, } : undefined, + createdBy: true, + updatedBy: true, }, orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], where, @@ -211,6 +213,8 @@ export class CustomerController extends Controller { subDistrict: true, }, }, + createdBy: true, + updatedBy: true, }, where: { id: customerId }, }); @@ -293,6 +297,8 @@ export class CustomerController extends Controller { subDistrict: true, }, }, + createdBy: true, + updatedBy: true, }, data: { ...payload, @@ -417,6 +423,8 @@ export class CustomerController extends Controller { subDistrict: true, }, }, + createdBy: true, + updatedBy: true, }, where: { id: customerId }, data: { diff --git a/src/controllers/employee-checkup-controller.ts b/src/controllers/employee-checkup-controller.ts index e3ee273..c04bb13 100644 --- a/src/controllers/employee-checkup-controller.ts +++ b/src/controllers/employee-checkup-controller.ts @@ -37,6 +37,10 @@ export class EmployeeCheckupController extends Controller { @Get() async list(@Path() employeeId: string) { return prisma.employeeCheckup.findMany({ + include: { + createdBy: true, + updatedBy: true, + }, orderBy: { createdAt: "asc" }, where: { employeeId }, }); @@ -45,6 +49,10 @@ export class EmployeeCheckupController extends Controller { @Get("{checkupId}") async getById(@Path() employeeId: string, @Path() checkupId: string) { const record = await prisma.employeeCheckup.findFirst({ + include: { + createdBy: true, + updatedBy: true, + }, where: { id: checkupId, employeeId }, }); if (!record) { @@ -85,7 +93,7 @@ export class EmployeeCheckupController extends Controller { const { provinceId, ...rest } = body; const record = await prisma.employeeCheckup.create({ - include: { province: true }, + include: { province: true, createdBy: true, updatedBy: true }, data: { ...rest, province: { connect: provinceId ? { id: provinceId } : undefined }, @@ -128,7 +136,12 @@ export class EmployeeCheckupController extends Controller { const { provinceId, ...rest } = body; - if (!(await prisma.employeeCheckup.findUnique({ where: { id: checkupId, employeeId } }))) { + if ( + !(await prisma.employeeCheckup.findUnique({ + include: { createdBy: true, updatedBy: true }, + where: { id: checkupId, employeeId }, + })) + ) { throw new HttpError( HttpStatus.NOT_FOUND, "Employee checkup cannot be found.", @@ -137,7 +150,7 @@ export class EmployeeCheckupController extends Controller { } const record = await prisma.employeeCheckup.update({ - include: { province: true }, + include: { province: true, createdBy: true, updatedBy: true }, where: { id: checkupId, employeeId }, data: { ...rest, @@ -163,6 +176,9 @@ export class EmployeeCheckupController extends Controller { ); } - return await prisma.employeeCheckup.delete({ where: { id: checkupId, employeeId } }); + return await prisma.employeeCheckup.delete({ + include: { createdBy: true, updatedBy: true }, + where: { id: checkupId, employeeId }, + }); } } diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts index 0739099..e46973b 100644 --- a/src/controllers/employee-controller.ts +++ b/src/controllers/employee-controller.ts @@ -258,6 +258,8 @@ export class EmployeeController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, }, where, take: pageSize, @@ -290,6 +292,8 @@ export class EmployeeController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, }, where: { id: employeeId }, }); @@ -392,6 +396,8 @@ export class EmployeeController extends Controller { }, }, employeeWork: true, + createdBy: true, + updatedBy: true, }, data: { ...rest, @@ -561,6 +567,8 @@ export class EmployeeController extends Controller { }, }, employeeWork: true, + createdBy: true, + updatedBy: true, }, data: { ...rest, @@ -694,7 +702,13 @@ export class EmployeeController extends Controller { throw new HttpError(HttpStatus.FORBIDDEN, "Employee is in used.", "employeeInUsed"); } - return await prisma.employee.delete({ where: { id: employeeId } }); + return await prisma.employee.delete({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: employeeId }, + }); } @Get("{employeeId}/edit-history") diff --git a/src/controllers/employee-other-info-controller.ts b/src/controllers/employee-other-info-controller.ts index 023f392..adc530d 100644 --- a/src/controllers/employee-other-info-controller.ts +++ b/src/controllers/employee-other-info-controller.ts @@ -39,6 +39,10 @@ export class EmployeeOtherInfo extends Controller { @Get() async list(@Path() employeeId: string) { return prisma.employeeOtherInfo.findFirst({ + include: { + createdBy: true, + updatedBy: true, + }, orderBy: { createdAt: "asc" }, where: { employeeId }, }); @@ -54,6 +58,10 @@ export class EmployeeOtherInfo extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "Employee cannot be found.", "employeeBadReq"); const record = await prisma.employeeOtherInfo.create({ + include: { + createdBy: true, + updatedBy: true, + }, data: { ...body, employee: { connect: { id: employeeId } }, @@ -83,6 +91,10 @@ export class EmployeeOtherInfo extends Controller { } const record = await prisma.employeeOtherInfo.update({ + include: { + createdBy: true, + updatedBy: true, + }, where: { id: otherInfoId, employeeId }, data: { ...body, updatedByUserId: req.user.sub }, }); @@ -106,6 +118,12 @@ export class EmployeeOtherInfo extends Controller { ); } - return await prisma.employeeOtherInfo.delete({ where: { id: otherInfoId, employeeId } }); + return await prisma.employeeOtherInfo.delete({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: otherInfoId, employeeId }, + }); } } diff --git a/src/controllers/employee-work-controller.ts b/src/controllers/employee-work-controller.ts index de90d1d..95d9625 100644 --- a/src/controllers/employee-work-controller.ts +++ b/src/controllers/employee-work-controller.ts @@ -35,6 +35,10 @@ export class EmployeeWorkController extends Controller { @Get() async list(@Path() employeeId: string) { return prisma.employeeWork.findMany({ + include: { + createdBy: true, + updatedBy: true, + }, orderBy: { createdAt: "asc" }, where: { employeeId }, }); @@ -43,6 +47,10 @@ export class EmployeeWorkController extends Controller { @Get("{workId}") async getById(@Path() employeeId: string, @Path() workId: string) { const record = await prisma.employeeWork.findFirst({ + include: { + createdBy: true, + updatedBy: true, + }, where: { id: workId, employeeId }, }); if (!record) { @@ -65,6 +73,10 @@ export class EmployeeWorkController extends Controller { throw new HttpError(HttpStatus.BAD_REQUEST, "Employee cannot be found.", "employeeBadReq"); const record = await prisma.employeeWork.create({ + include: { + createdBy: true, + updatedBy: true, + }, data: { ...body, employee: { connect: { id: employeeId } }, @@ -94,6 +106,10 @@ export class EmployeeWorkController extends Controller { } const record = await prisma.employeeWork.update({ + include: { + createdBy: true, + updatedBy: true, + }, where: { id: workId, employeeId }, data: { ...body, updatedByUserId: req.user.sub }, }); @@ -105,7 +121,13 @@ export class EmployeeWorkController extends Controller { @Delete("{workId}") async deleteById(@Path() employeeId: string, @Path() workId: string) { - const record = await prisma.employeeWork.findFirst({ where: { id: workId, employeeId } }); + const record = await prisma.employeeWork.findFirst({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: workId, employeeId }, + }); if (!record) { throw new HttpError( diff --git a/src/controllers/product/group-controller.ts b/src/controllers/product/group-controller.ts index 2d93e22..0968394 100644 --- a/src/controllers/product/group-controller.ts +++ b/src/controllers/product/group-controller.ts @@ -72,6 +72,8 @@ export class ProductGroup extends Controller { type: true, }, }, + createdBy: true, + updatedBy: true, }, orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], where, @@ -139,6 +141,10 @@ export class ProductGroup extends Controller { }); return await tx.productGroup.create({ + include: { + createdBy: true, + updatedBy: true, + }, data: { ...body, statusOrder: +(body.status === "INACTIVE"), @@ -171,6 +177,10 @@ export class ProductGroup extends Controller { } const record = await prisma.productGroup.update({ + include: { + createdBy: true, + updatedBy: true, + }, data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedByUserId: req.user.sub }, where: { id: groupId }, }); @@ -194,6 +204,12 @@ export class ProductGroup extends Controller { throw new HttpError(HttpStatus.FORBIDDEN, "Product group is in used.", "productGroupInUsed"); } - return await prisma.productGroup.delete({ where: { id: groupId } }); + return await prisma.productGroup.delete({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: groupId }, + }); } } diff --git a/src/controllers/product/product-controller.ts b/src/controllers/product/product-controller.ts index e8d8e75..e953333 100644 --- a/src/controllers/product/product-controller.ts +++ b/src/controllers/product/product-controller.ts @@ -89,6 +89,10 @@ export class ProductController extends Controller { const [result, total] = await prisma.$transaction([ prisma.product.findMany({ + include: { + createdBy: true, + updatedBy: true, + }, orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], where, take: pageSize, @@ -118,6 +122,10 @@ export class ProductController extends Controller { @Security("keycloak") async getProductById(@Path() productId: string) { const record = await prisma.product.findFirst({ + include: { + createdBy: true, + updatedBy: true, + }, where: { id: productId }, }); @@ -145,6 +153,10 @@ export class ProductController extends Controller { @Security("keycloak") async createProduct(@Request() req: RequestWithUser, @Body() body: ProductCreate) { const productType = await prisma.productType.findFirst({ + include: { + createdBy: true, + updatedBy: true, + }, where: { id: body.productTypeId }, }); @@ -169,6 +181,10 @@ export class ProductController extends Controller { update: { value: { increment: 1 } }, }); return await prisma.product.create({ + include: { + createdBy: true, + updatedBy: true, + }, data: { ...body, statusOrder: +(body.status === "INACTIVE"), @@ -185,6 +201,10 @@ export class ProductController extends Controller { if (productType.status === "CREATED") { await prisma.productType.update({ + include: { + createdBy: true, + updatedBy: true, + }, where: { id: body.productTypeId }, data: { status: Status.ACTIVE }, }); @@ -230,6 +250,10 @@ export class ProductController extends Controller { } const record = await prisma.product.update({ + include: { + createdBy: true, + updatedBy: true, + }, data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedByUserId: req.user.sub }, where: { id: productId }, }); @@ -268,6 +292,12 @@ export class ProductController extends Controller { throw new HttpError(HttpStatus.FORBIDDEN, "Product is in used.", "productInUsed"); } - return await prisma.product.delete({ where: { id: productId } }); + return await prisma.product.delete({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: productId }, + }); } } diff --git a/src/controllers/product/type-controller.ts b/src/controllers/product/type-controller.ts index 36a9a12..8e13c90 100644 --- a/src/controllers/product/type-controller.ts +++ b/src/controllers/product/type-controller.ts @@ -72,6 +72,8 @@ export class ProductType extends Controller { _count: { select: { product: true }, }, + createdBy: true, + updatedBy: true, }, orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], where, @@ -128,6 +130,10 @@ export class ProductType extends Controller { }); return await tx.productType.create({ + include: { + createdBy: true, + updatedBy: true, + }, data: { ...body, statusOrder: +(body.status === "INACTIVE"), @@ -178,7 +184,15 @@ export class ProductType extends Controller { } const record = await prisma.productType.update({ - data: { ...body, statusOrder: +(body.status === "INACTIVE"), updatedByUserId: req.user.sub }, + include: { + createdBy: true, + updatedBy: true, + }, + data: { + ...body, + statusOrder: +(body.status === "INACTIVE"), + updatedByUserId: req.user.sub, + }, where: { id: typeId }, }); @@ -208,6 +222,12 @@ export class ProductType extends Controller { throw new HttpError(HttpStatus.FORBIDDEN, "Product type is in used.", "productTypeInUsed"); } - return await prisma.productType.delete({ where: { id: typeId } }); + return await prisma.productType.delete({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: typeId }, + }); } } diff --git a/src/controllers/service/service-controller.ts b/src/controllers/service/service-controller.ts index c99d1af..441824b 100644 --- a/src/controllers/service/service-controller.ts +++ b/src/controllers/service/service-controller.ts @@ -95,6 +95,8 @@ export class ServiceController extends Controller { prisma.service.findMany({ include: { work: true, + createdBy: true, + updatedBy: true, }, orderBy: [{ statusOrder: "asc" }, { createdAt: "asc" }], where, @@ -135,6 +137,8 @@ export class ServiceController extends Controller { }, }, }, + createdBy: true, + updatedBy: true, }, where: { id: serviceId }, }); @@ -168,6 +172,8 @@ export class ServiceController extends Controller { }, orderBy: { order: "asc" }, }, + createdBy: true, + updatedBy: true, }, where, take: pageSize, @@ -239,6 +245,8 @@ export class ServiceController extends Controller { }, }, }, + createdBy: true, + updatedBy: true, }, data: { ...payload, @@ -301,6 +309,10 @@ export class ServiceController extends Controller { ); return await tx.service.update({ + include: { + createdBy: true, + updatedBy: true, + }, data: { ...payload, statusOrder: +(payload.status === "INACTIVE"), @@ -341,6 +353,12 @@ export class ServiceController extends Controller { throw new HttpError(HttpStatus.FORBIDDEN, "Service is in used.", "serviceInUsed"); } - return await prisma.service.delete({ where: { id: serviceId } }); + return await prisma.service.delete({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: serviceId }, + }); } } diff --git a/src/controllers/user-controller.ts b/src/controllers/user-controller.ts index 89855ab..af6e112 100644 --- a/src/controllers/user-controller.ts +++ b/src/controllers/user-controller.ts @@ -173,6 +173,8 @@ export class UserController extends Controller { district: true, subDistrict: true, branch: { include: { branch: includeBranch } }, + createdBy: true, + updatedBy: true, }, where, take: pageSize, @@ -207,6 +209,8 @@ export class UserController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, }, where: { id: userId }, }); @@ -287,7 +291,13 @@ export class UserController extends Controller { } const record = await prisma.user.create({ - include: { province: true, district: true, subDistrict: true }, + include: { + province: true, + district: true, + subDistrict: true, + createdBy: true, + updatedBy: true, + }, data: { id: userId, ...rest, @@ -417,7 +427,13 @@ export class UserController extends Controller { })); const record = await prisma.user.update({ - include: { province: true, district: true, subDistrict: true }, + include: { + province: true, + district: true, + subDistrict: true, + createdBy: true, + updatedBy: true, + }, data: { ...rest, statusOrder: +(rest.status === "INACTIVE"), @@ -465,6 +481,8 @@ export class UserController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, }, where: { id: userId }, }); @@ -502,6 +520,8 @@ export class UserController extends Controller { province: true, district: true, subDistrict: true, + createdBy: true, + updatedBy: true, }, where: { id: userId }, }); @@ -519,11 +539,6 @@ export class UserAttachmentController extends Controller { @Get() async listAttachment(@Path() userId: string) { const record = await prisma.user.findFirst({ - include: { - province: true, - district: true, - subDistrict: true, - }, where: { id: userId }, }); @@ -552,11 +567,6 @@ export class UserAttachmentController extends Controller { @Post() async addAttachment(@Path() userId: string, @Body() payload: { file: string[] }) { const record = await prisma.user.findFirst({ - include: { - province: true, - district: true, - subDistrict: true, - }, where: { id: userId }, }); diff --git a/src/controllers/work/work-controller.ts b/src/controllers/work/work-controller.ts index f73f017..4359410 100644 --- a/src/controllers/work/work-controller.ts +++ b/src/controllers/work/work-controller.ts @@ -63,6 +63,8 @@ export class WorkController extends Controller { order: "asc", }, }, + createdBy: true, + updatedBy: true, }, orderBy: { createdAt: "asc" }, where, @@ -106,6 +108,10 @@ export class WorkController extends Controller { const [result, total] = await prisma.$transaction([ prisma.product.findMany({ + include: { + createdBy: true, + updatedBy: true, + }, where, take: pageSize, skip: (page - 1) * pageSize, @@ -129,6 +135,8 @@ export class WorkController extends Controller { product: true, }, }, + createdBy: true, + updatedBy: true, }, where: { productOnWork: { @@ -175,6 +183,8 @@ export class WorkController extends Controller { order: "asc", }, }, + createdBy: true, + updatedBy: true, }, data: { ...payload, @@ -261,6 +271,8 @@ export class WorkController extends Controller { order: "asc", }, }, + createdBy: true, + updatedBy: true, }, where: { id: workId }, data: { @@ -296,7 +308,10 @@ export class WorkController extends Controller { @Delete("{workId}") async deleteWork(@Path() workId: string) { - const record = await prisma.work.findFirst({ where: { id: workId } }); + const record = await prisma.work.findFirst({ + include: { createdBy: true, updatedBy: true }, + where: { id: workId }, + }); if (!record) { throw new HttpError(HttpStatus.NOT_FOUND, "Work cannot be found.", "workNotFound"); From d23e40419612c36e3e5882e93bd3dbfee08721f2 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:06:05 +0700 Subject: [PATCH 004/102] feat: add count gender by query --- src/controllers/employee-controller.ts | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts index e46973b..eb3f62b 100644 --- a/src/controllers/employee-controller.ts +++ b/src/controllers/employee-controller.ts @@ -210,12 +210,31 @@ export class EmployeeController extends Controller { } @Get("stats/gender") - async getEmployeeStatsGender(@Query() customerBranchId?: string) { + async getEmployeeStatsGender( + @Query() customerBranchId?: string, + @Query() status?: Status, + @Query() query: string = "", + ) { + const filterStatus = (val?: Status) => { + if (!val) return {}; + + return val !== Status.CREATED && val !== Status.ACTIVE + ? { status: val } + : { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] }; + }; + return await prisma.employee .groupBy({ _count: true, by: ["gender"], - where: { customerBranchId }, + where: { + OR: [ + { firstName: { contains: query }, customerBranchId, ...filterStatus(status) }, + { firstNameEN: { contains: query }, customerBranchId, ...filterStatus(status) }, + { lastName: { contains: query }, customerBranchId, ...filterStatus(status) }, + { lastNameEN: { contains: query }, customerBranchId, ...filterStatus(status) }, + ], + }, }) .then((res) => res.reduce>((a, c) => { From 9a824f3ddfc50f1c412195a0c7b869e239f32305 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:25:35 +0700 Subject: [PATCH 005/102] fix: unused field --- .../migration.sql | 15 +++++++++++++++ prisma/schema.prisma | 6 ------ src/controllers/employee-controller.ts | 1 - 3 files changed, 15 insertions(+), 7 deletions(-) create mode 100644 prisma/migrations/20240702022506_remove_unneccessary_field/migration.sql diff --git a/prisma/migrations/20240702022506_remove_unneccessary_field/migration.sql b/prisma/migrations/20240702022506_remove_unneccessary_field/migration.sql new file mode 100644 index 0000000..13cfd17 --- /dev/null +++ b/prisma/migrations/20240702022506_remove_unneccessary_field/migration.sql @@ -0,0 +1,15 @@ +/* + Warnings: + + - You are about to drop the column `createdAt` on the `EmployeeHistory` table. All the data in the column will be lost. + - You are about to drop the column `createdByUserId` on the `EmployeeHistory` table. All the data in the column will be lost. + - You are about to drop the column `timestamp` on the `EmployeeHistory` table. All the data in the column will be lost. + +*/ +-- DropForeignKey +ALTER TABLE "EmployeeHistory" DROP CONSTRAINT "EmployeeHistory_createdByUserId_fkey"; + +-- AlterTable +ALTER TABLE "EmployeeHistory" DROP COLUMN "createdAt", +DROP COLUMN "createdByUserId", +DROP COLUMN "timestamp"; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index f60e04e..0a12981 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -352,7 +352,6 @@ model User { customerBranchUpdated CustomerBranch[] @relation("CustomerBranchUpdatedByUser") emplyeeCreated Employee[] @relation("EmployeeCreatedByUser") employeUpdated Employee[] @relation("EmployeeUpdatedByUser") - employeeHistoryCreated EmployeeHistory[] @relation("EmployeeHistoryCreatedByUser") employeeHistoryUpdated EmployeeHistory[] @relation("EmployeeHistoryUpdatedByUser") employeeCheckupCreated EmployeeCheckup[] @relation("EmployeeCheckupCreatedByUser") employeeCheckupUpdated EmployeeCheckup[] @relation("EmployeeCheckupUpdatedByUser") @@ -531,11 +530,6 @@ model EmployeeHistory { valueBefore Json valueAfter Json - timestamp DateTime @default(now()) - - createdAt DateTime @default(now()) - createdBy User? @relation(name: "EmployeeHistoryCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) - createdByUserId String? updatedAt DateTime @updatedAt updatedBy User? @relation(name: "EmployeeHistoryUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) updatedByUserId String? diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts index eb3f62b..abe169c 100644 --- a/src/controllers/employee-controller.ts +++ b/src/controllers/employee-controller.ts @@ -690,7 +690,6 @@ export class EmployeeController extends Controller { data: historyEntries.map((v) => ({ ...v, updatedByUserId: req.user.sub, - updatedBy: req.user.preferred_username, masterId: employee.id, })), }); From 5e8dd1ddad7cd2da1b1bad272258e975a8c8621b Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:27:05 +0700 Subject: [PATCH 006/102] feat: include relation --- src/controllers/employee-controller.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts index abe169c..e3186c0 100644 --- a/src/controllers/employee-controller.ts +++ b/src/controllers/employee-controller.ts @@ -732,6 +732,9 @@ export class EmployeeController extends Controller { @Get("{employeeId}/edit-history") async editHistory(@Path() employeeId: string) { return await prisma.employeeHistory.findMany({ + include: { + updatedBy: true, + }, where: { masterId: employeeId }, }); } From c464da622830d524b95d784ce0fc4d4384023c95 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:41:21 +0700 Subject: [PATCH 007/102] fix: raw query relation --- src/controllers/product-service-controller.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/controllers/product-service-controller.ts b/src/controllers/product-service-controller.ts index 8671c3d..da06d9e 100644 --- a/src/controllers/product-service-controller.ts +++ b/src/controllers/product-service-controller.ts @@ -27,9 +27,9 @@ export class ProductServiceController extends Controller { "status", "statusOrder", "productTypeId", - "createdBy", + "createdByUserId", "createdAt", - "updatedBy", + "updatedByUserId", "updatedAt", 'product' as "type" FROM "Product" @@ -47,9 +47,9 @@ export class ProductServiceController extends Controller { "status", "statusOrder", null as "productTypeId", - "createdBy", + "createdByUserId", "createdAt", - "updatedBy", + "updatedByUserId", "updatedAt", 'service' as "type" FROM "Service" From 9618c261e315bbf0bbd627f6976f714f08d72122 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 13:46:22 +0700 Subject: [PATCH 008/102] feat: permmission branch --- src/controllers/branch-controller.ts | 66 +++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index e2f176b..e8b23ae 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -80,9 +80,9 @@ function branchImageLoc(id: string) { @Route("api/v1/branch") @Tags("Branch") -@Security("keycloak") export class BranchController extends Controller { @Get("stats") + @Security("keycloak") async getStats() { const list = await prisma.branch.groupBy({ _count: true, @@ -99,14 +99,27 @@ export class BranchController extends Controller { } @Get("user-stats") - async getUserStat(@Query() userType?: UserType) { + @Security("keycloak") + async getUserStat(@Request() req: RequestWithUser, @Query() userType?: UserType) { const list = await prisma.branchUser.groupBy({ _count: true, - where: { user: { userType } }, + where: { + userId: !["system", "head_of_admin", "admin"].some((v) => req.user.role?.includes(v)) + ? req.user.sub + : undefined, + user: { + userType, + }, + }, by: "branchId", }); const record = await prisma.branch.findMany({ + where: { + user: !["system", "head_of_admin", "admin"].some((v) => req.user.role?.includes(v)) + ? { some: { userId: req.user.sub } } + : undefined, + }, select: { id: true, headOfficeId: true, @@ -136,7 +149,9 @@ export class BranchController extends Controller { } @Get() + @Security("keycloak") async getBranch( + @Request() req: RequestWithUser, @Query() zipCode?: string, @Query() filter?: "head" | "sub", @Query() headOfficeId?: string, @@ -147,6 +162,9 @@ export class BranchController extends Controller { ) { const where = { AND: { + user: !["system", "head_of_admin", "admin"].some((v) => req.user.role?.includes(v)) + ? { some: { userId: req.user.sub } } + : undefined, headOfficeId: headOfficeId ?? (filter === "head" || tree ? null : undefined), NOT: { headOfficeId: filter === "sub" && !headOfficeId ? null : undefined }, }, @@ -187,6 +205,7 @@ export class BranchController extends Controller { } @Get("{branchId}") + @Security("keycloak") async getBranchById( @Path() branchId: string, @Query() includeSubBranch?: boolean, @@ -222,6 +241,7 @@ export class BranchController extends Controller { } @Post() + @Security("keycloak", ["system", "head_of_admin", "admin"]) async createBranch(@Request() req: RequestWithUser, @Body() body: BranchCreate) { const [province, district, subDistrict, head] = await prisma.$transaction([ prisma.province.findFirst({ where: { id: body.provinceId || undefined } }), @@ -336,6 +356,7 @@ export class BranchController extends Controller { } @Put("{branchId}") + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_admin", "branch_manager"]) async editBranch( @Request() req: RequestWithUser, @Body() body: BranchUpdate, @@ -383,10 +404,30 @@ export class BranchController extends Controller { const { provinceId, districtId, subDistrictId, headOfficeId, contact, ...rest } = body; - if (!(await prisma.branch.findUnique({ where: { id: branchId } }))) { + const branch = await prisma.branch.findUnique({ + include: { + user: { where: { id: req.user.sub } }, + }, + where: { id: branchId }, + }); + + if (!branch) { throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound"); } + if (!["system", "head_of_admin", "admin"].some((v) => req.user.role?.includes(v))) { + if ( + branch?.createdByUserId !== req.user.sub && + !branch?.user.find((v) => v.userId === req.user.sub) + ) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } + } + const record = await prisma.branch.update({ include: { province: true, district: true, subDistrict: true }, data: { @@ -442,7 +483,8 @@ export class BranchController extends Controller { } @Delete("{branchId}") - async deleteBranch(@Path() branchId: string) { + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_manager"]) + async deleteBranch(@Request() req: RequestWithUser, @Path() branchId: string) { const record = await prisma.branch.findFirst({ include: { province: true, @@ -450,10 +492,24 @@ export class BranchController extends Controller { subDistrict: true, createdBy: true, updatedBy: true, + user: { where: { id: req.user.sub } }, }, where: { id: branchId }, }); + if (!["system", "head_of_admin", "admin"].some((v) => req.user.role?.includes(v))) { + if ( + record?.createdByUserId !== req.user.sub && + !record?.user.find((v) => v.userId === req.user.sub) + ) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } + } + if (!record) { throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound"); } From 4f1afea7fb8896591a5fd1615e3e9af3c9513f6f Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:19:38 +0700 Subject: [PATCH 009/102] feat: add product code --- src/controllers/product/product-controller.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/controllers/product/product-controller.ts b/src/controllers/product/product-controller.ts index e953333..7e3d2f4 100644 --- a/src/controllers/product/product-controller.ts +++ b/src/controllers/product/product-controller.ts @@ -28,7 +28,21 @@ const MINIO_BUCKET = process.env.MINIO_BUCKET; type ProductCreate = { status?: Status; - code: "AC" | "DO" | "ac" | "do"; + code: + | "DOE" + | "IMM" + | "TM" + | "HP" + | "MOUC" + | "MOUL" + | "AC" + | "doe" + | "imm" + | "tm" + | "hp" + | "mouc" + | "moul" + | "ac"; name: string; detail: string; process: number; From 6ee2e9c4dad700f144d49b4988658d02d5d31de4 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:21:27 +0700 Subject: [PATCH 010/102] feat: remove scoped branch (change soon) --- src/controllers/branch-controller.ts | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index e8b23ae..0aa653e 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -151,7 +151,6 @@ export class BranchController extends Controller { @Get() @Security("keycloak") async getBranch( - @Request() req: RequestWithUser, @Query() zipCode?: string, @Query() filter?: "head" | "sub", @Query() headOfficeId?: string, @@ -162,9 +161,6 @@ export class BranchController extends Controller { ) { const where = { AND: { - user: !["system", "head_of_admin", "admin"].some((v) => req.user.role?.includes(v)) - ? { some: { userId: req.user.sub } } - : undefined, headOfficeId: headOfficeId ?? (filter === "head" || tree ? null : undefined), NOT: { headOfficeId: filter === "sub" && !headOfficeId ? null : undefined }, }, @@ -415,17 +411,15 @@ export class BranchController extends Controller { throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound"); } - if (!["system", "head_of_admin", "admin"].some((v) => req.user.role?.includes(v))) { - if ( - branch?.createdByUserId !== req.user.sub && - !branch?.user.find((v) => v.userId === req.user.sub) - ) { - throw new HttpError( - HttpStatus.FORBIDDEN, - "You do not have permission to perform this action.", - "noPermission", - ); - } + if ( + !["system", "head_of_admin", "admin"].some((v) => req.user.role?.includes(v)) && + !branch?.user.find((v) => v.userId === req.user.sub) + ) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); } const record = await prisma.branch.update({ From 25a1f3c4a44ee7a266a32d505b2676833080c3a3 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 16:54:19 +0700 Subject: [PATCH 011/102] feat: product related permission --- src/controllers/product/group-controller.ts | 7 ++++++- src/controllers/product/type-controller.ts | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/controllers/product/group-controller.ts b/src/controllers/product/group-controller.ts index 0968394..d3dff29 100644 --- a/src/controllers/product/group-controller.ts +++ b/src/controllers/product/group-controller.ts @@ -35,14 +35,15 @@ type ProductGroupUpdate = { @Route("api/v1/product-group") @Tags("Product Group") -@Security("keycloak") export class ProductGroup extends Controller { @Get("stats") + @Security("keycloak") async getProductGroupStats() { return await prisma.productGroup.count(); } @Get() + @Security("keycloak") async getProductGroup( @Query() query: string = "", @Query() status?: Status, @@ -110,6 +111,7 @@ export class ProductGroup extends Controller { } @Get("{groupId}") + @Security("keycloak") async getProductGroupById(@Path() groupId: string) { const record = await prisma.productGroup.findFirst({ where: { id: groupId }, @@ -126,6 +128,7 @@ export class ProductGroup extends Controller { } @Post() + @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) async createProductGroup(@Request() req: RequestWithUser, @Body() body: ProductGroupCreate) { const record = await prisma.$transaction( async (tx) => { @@ -163,6 +166,7 @@ export class ProductGroup extends Controller { } @Put("{groupId}") + @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) async editProductGroup( @Request() req: RequestWithUser, @Body() body: ProductGroupUpdate, @@ -189,6 +193,7 @@ export class ProductGroup extends Controller { } @Delete("{groupId}") + @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) async deleteProductGroup(@Path() groupId: string) { const record = await prisma.productGroup.findFirst({ where: { id: groupId } }); diff --git a/src/controllers/product/type-controller.ts b/src/controllers/product/type-controller.ts index 8e13c90..8f6e2b6 100644 --- a/src/controllers/product/type-controller.ts +++ b/src/controllers/product/type-controller.ts @@ -37,7 +37,6 @@ type ProductTypeUpdate = { @Route("api/v1/product-type") @Tags("Product Type") -@Security("keycloak") export class ProductType extends Controller { @Get("stats") async getProductTypeStats() { @@ -45,6 +44,7 @@ export class ProductType extends Controller { } @Get() + @Security("keycloak") async getProductType( @Query() query: string = "", @Query() productGroupId?: string, @@ -87,6 +87,7 @@ export class ProductType extends Controller { } @Get("{typeId}") + @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) async getProductTypeById(@Path() typeId: string) { const record = await prisma.productType.findFirst({ where: { id: typeId }, @@ -103,6 +104,7 @@ export class ProductType extends Controller { } @Post() + @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) async createProductType(@Request() req: RequestWithUser, @Body() body: ProductTypeCreate) { const productGroup = await prisma.productGroup.findFirst({ where: { id: body.productGroupId }, @@ -159,6 +161,7 @@ export class ProductType extends Controller { } @Put("{typeId}") + @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) async editProductType( @Request() req: RequestWithUser, @Body() body: ProductTypeUpdate, @@ -207,6 +210,7 @@ export class ProductType extends Controller { } @Delete("{typeId}") + @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) async deleteProductType(@Path() typeId: string) { const record = await prisma.productType.findFirst({ where: { id: typeId } }); From 2450ddb7eef2f6290f3f57655dd3d7c09e32d53e Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:29:03 +0700 Subject: [PATCH 012/102] fix: wrong key --- src/controllers/branch-controller.ts | 8 ++++---- src/interfaces/user.ts | 2 +- src/middlewares/role.ts | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index 0aa653e..aa02782 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -104,7 +104,7 @@ export class BranchController extends Controller { const list = await prisma.branchUser.groupBy({ _count: true, where: { - userId: !["system", "head_of_admin", "admin"].some((v) => req.user.role?.includes(v)) + userId: !["system", "head_of_admin", "admin"].some((v) => req.user.roles?.includes(v)) ? req.user.sub : undefined, user: { @@ -116,7 +116,7 @@ export class BranchController extends Controller { const record = await prisma.branch.findMany({ where: { - user: !["system", "head_of_admin", "admin"].some((v) => req.user.role?.includes(v)) + user: !["system", "head_of_admin", "admin"].some((v) => req.user.roles?.includes(v)) ? { some: { userId: req.user.sub } } : undefined, }, @@ -412,7 +412,7 @@ export class BranchController extends Controller { } if ( - !["system", "head_of_admin", "admin"].some((v) => req.user.role?.includes(v)) && + !["system", "head_of_admin", "admin"].some((v) => req.user.roles?.includes(v)) && !branch?.user.find((v) => v.userId === req.user.sub) ) { throw new HttpError( @@ -491,7 +491,7 @@ export class BranchController extends Controller { where: { id: branchId }, }); - if (!["system", "head_of_admin", "admin"].some((v) => req.user.role?.includes(v))) { + if (!["system", "head_of_admin", "admin"].some((v) => req.user.roles?.includes(v))) { if ( record?.createdByUserId !== req.user.sub && !record?.user.find((v) => v.userId === req.user.sub) diff --git a/src/interfaces/user.ts b/src/interfaces/user.ts index a35cdc4..3bf51f2 100644 --- a/src/interfaces/user.ts +++ b/src/interfaces/user.ts @@ -8,6 +8,6 @@ export type RequestWithUser = Request & { familiy_name: string; preferred_username: string; email: string; - role: string[]; + roles: string[]; }; }; diff --git a/src/middlewares/role.ts b/src/middlewares/role.ts index dd2ec2f..be5c419 100644 --- a/src/middlewares/role.ts +++ b/src/middlewares/role.ts @@ -8,10 +8,10 @@ export function role( errorMessage: string = "You do not have permission to access this resource.", ) { return (req: RequestWithUser, _res: Response, next: NextFunction) => { - if (!Array.isArray(role) && !req.user.role.includes(role) && !req.user.role.includes("*")) { + if (!Array.isArray(role) && !req.user.roles.includes(role) && !req.user.roles.includes("*")) { throw new HttpError(HttpStatus.FORBIDDEN, errorMessage, "noPermissionToAccess"); } - if (role !== "*" && !req.user.role.some((v) => role.includes(v))) { + if (role !== "*" && !req.user.roles.some((v) => role.includes(v))) { throw new HttpError(HttpStatus.FORBIDDEN, errorMessage, "noPermissionToAccess"); } return next(); From 6113b56f93699631175d92b1df0e10bd590e255a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:29:31 +0700 Subject: [PATCH 013/102] chore: update upload time --- src/controllers/branch-controller.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index aa02782..3ba1ec7 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -337,12 +337,12 @@ export class BranchController extends Controller { return Object.assign(record, { contact: await prisma.branchContact.findMany({ where: { branchId: record.id } }), imageUrl: await minio.presignedGetObject(MINIO_BUCKET, branchImageLoc(record.id)), - imageUploadUrl: await minio.presignedPutObject(MINIO_BUCKET, branchImageLoc(record.id)), - qrCodeImageUrl: await minio.presignedGetObject( + imageUploadUrl: await minio.presignedPutObject( MINIO_BUCKET, - lineImageLoc(record.id), + branchImageLoc(record.id), 12 * 60 * 60, ), + qrCodeImageUrl: await minio.presignedGetObject(MINIO_BUCKET, lineImageLoc(record.id)), qrCodeImageUploadUrl: await minio.presignedPutObject( MINIO_BUCKET, lineImageLoc(record.id), From 6c521c1b0926bf0b0392d2706d41a60f570ad398 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:29:51 +0700 Subject: [PATCH 014/102] feat: customer permission role --- src/controllers/customer-controller.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index 2b55fa4..f97ca6d 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -117,9 +117,9 @@ function imageLocation(id: string) { @Route("api/v1/customer") @Tags("Customer") -@Security("keycloak") export class CustomerController extends Controller { @Get("type-stats") + @Security("keycloak") async stat() { const list = await prisma.customer.groupBy({ by: "customerType", @@ -139,6 +139,7 @@ export class CustomerController extends Controller { } @Get() + @Security("keycloak") async list( @Query() customerType?: CustomerType, @Query() query: string = "", @@ -203,6 +204,7 @@ export class CustomerController extends Controller { } @Get("{customerId}") + @Security("keycloak") async getById(@Path() customerId: string) { const record = await prisma.customer.findFirst({ include: { @@ -230,6 +232,7 @@ export class CustomerController extends Controller { } @Post() + @Security("keycloak", ["system", "head_of_admin", "admin", "head_of_sale", "sale"]) async create(@Request() req: RequestWithUser, @Body() body: CustomerCreate) { const { customerBranch, ...payload } = body; @@ -341,6 +344,7 @@ export class CustomerController extends Controller { } @Put("{customerId}") + @Security("keycloak", ["system", "head_of_admin", "admin", "head_of_sale", "sale"]) async editById( @Path() customerId: string, @Request() req: RequestWithUser, @@ -500,6 +504,7 @@ export class CustomerController extends Controller { } @Delete("{customerId}") + @Security("keycloak", ["system", "head_of_admin", "admin", "head_of_sale", "sale"]) async deleteById(@Path() customerId: string) { const record = await prisma.customer.findFirst({ where: { id: customerId } }); From 33d5e6ef4c1e829ee0be27cf017a2c0f330d15b8 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:31:02 +0700 Subject: [PATCH 015/102] fixup! chore: update upload time --- src/controllers/branch-controller.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index 3ba1ec7..9023411 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -462,12 +462,12 @@ export class BranchController extends Controller { return Object.assign(record, { imageUrl: await minio.presignedGetObject(MINIO_BUCKET, branchImageLoc(record.id)), - imageUploadUrl: await minio.presignedPutObject(MINIO_BUCKET, branchImageLoc(record.id)), - qrCodeImageUrl: await minio.presignedGetObject( + imageUploadUrl: await minio.presignedPutObject( MINIO_BUCKET, - lineImageLoc(record.id), + branchImageLoc(record.id), 12 * 60 * 60, ), + qrCodeImageUrl: await minio.presignedGetObject(MINIO_BUCKET, lineImageLoc(record.id)), qrCodeImageUploadUrl: await minio.presignedPutObject( MINIO_BUCKET, lineImageLoc(record.id), From 7782772e50892947fef5e9f9fbe409c5c4041b90 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:45:21 +0700 Subject: [PATCH 016/102] fix: code not return --- src/middlewares/auth-provider/keycloak.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/middlewares/auth-provider/keycloak.ts b/src/middlewares/auth-provider/keycloak.ts index 5201c36..f105bfc 100644 --- a/src/middlewares/auth-provider/keycloak.ts +++ b/src/middlewares/auth-provider/keycloak.ts @@ -47,7 +47,11 @@ export async function keycloakAuth(request: Express.Request, roles?: string[]) { if (Array.isArray(payload.roles) && Array.isArray(roles) && roles.length > 0) { if (!roles.some((a: string) => payload.roles.includes(a))) { - throw new HttpError(HttpStatus.FORBIDDEN, "คุณไม่มีสิทธิในการเข้าถึงข้อมูลดังกล่าว"); + throw new HttpError( + HttpStatus.FORBIDDEN, + "คุณไม่มีสิทธิในการเข้าถึงข้อมูลดังกล่าว", + "noPermission", + ); } } From dbf32a987d9af7b8673835270b4fc7b43db19b19 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:57:14 +0700 Subject: [PATCH 017/102] fix: wrong relation included --- src/controllers/branch-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index 9023411..5c4f7a7 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -402,7 +402,7 @@ export class BranchController extends Controller { const branch = await prisma.branch.findUnique({ include: { - user: { where: { id: req.user.sub } }, + user: { where: { userId: req.user.sub } }, }, where: { id: branchId }, }); From 500e5a8fbe05a447fe40bd27b3356634da8ebee3 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:10:12 +0700 Subject: [PATCH 018/102] fix: wrong instance used --- src/controllers/branch-user-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/branch-user-controller.ts b/src/controllers/branch-user-controller.ts index 2d8100c..4be9ac3 100644 --- a/src/controllers/branch-user-controller.ts +++ b/src/controllers/branch-user-controller.ts @@ -39,7 +39,7 @@ async function userBranchCodeGen(branch: Branch, user: User[]) { update: { value: { increment: 1 } }, }); - await prisma.user.update({ + await tx.user.update({ where: { id: usr.id }, data: { code: `${last.key.slice(7)}${last.value.toString().padStart(4, "0")}`, From 8e18546b449387a77772bcb4a2f4b0200219c844 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 09:51:19 +0700 Subject: [PATCH 019/102] feat: add more role to each endpoint --- src/controllers/product/group-controller.ts | 6 +++--- src/controllers/product/product-controller.ts | 6 +++--- src/controllers/product/type-controller.ts | 8 ++++---- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/controllers/product/group-controller.ts b/src/controllers/product/group-controller.ts index d3dff29..bc135fb 100644 --- a/src/controllers/product/group-controller.ts +++ b/src/controllers/product/group-controller.ts @@ -128,7 +128,7 @@ export class ProductGroup extends Controller { } @Post() - @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) async createProductGroup(@Request() req: RequestWithUser, @Body() body: ProductGroupCreate) { const record = await prisma.$transaction( async (tx) => { @@ -166,7 +166,7 @@ export class ProductGroup extends Controller { } @Put("{groupId}") - @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) async editProductGroup( @Request() req: RequestWithUser, @Body() body: ProductGroupUpdate, @@ -193,7 +193,7 @@ export class ProductGroup extends Controller { } @Delete("{groupId}") - @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) async deleteProductGroup(@Path() groupId: string) { const record = await prisma.productGroup.findFirst({ where: { id: groupId } }); diff --git a/src/controllers/product/product-controller.ts b/src/controllers/product/product-controller.ts index 7e3d2f4..7fb7cd1 100644 --- a/src/controllers/product/product-controller.ts +++ b/src/controllers/product/product-controller.ts @@ -164,7 +164,7 @@ export class ProductController extends Controller { } @Post() - @Security("keycloak") + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) async createProduct(@Request() req: RequestWithUser, @Body() body: ProductCreate) { const productType = await prisma.productType.findFirst({ include: { @@ -241,7 +241,7 @@ export class ProductController extends Controller { } @Put("{productId}") - @Security("keycloak") + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) async editProduct( @Request() req: RequestWithUser, @Body() body: ProductUpdate, @@ -294,7 +294,7 @@ export class ProductController extends Controller { } @Delete("{productId}") - @Security("keycloak") + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) async deleteProduct(@Path() productId: string) { const record = await prisma.product.findFirst({ where: { id: productId } }); diff --git a/src/controllers/product/type-controller.ts b/src/controllers/product/type-controller.ts index 8f6e2b6..886eb5c 100644 --- a/src/controllers/product/type-controller.ts +++ b/src/controllers/product/type-controller.ts @@ -87,7 +87,7 @@ export class ProductType extends Controller { } @Get("{typeId}") - @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) async getProductTypeById(@Path() typeId: string) { const record = await prisma.productType.findFirst({ where: { id: typeId }, @@ -104,7 +104,7 @@ export class ProductType extends Controller { } @Post() - @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) async createProductType(@Request() req: RequestWithUser, @Body() body: ProductTypeCreate) { const productGroup = await prisma.productGroup.findFirst({ where: { id: body.productGroupId }, @@ -161,7 +161,7 @@ export class ProductType extends Controller { } @Put("{typeId}") - @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) async editProductType( @Request() req: RequestWithUser, @Body() body: ProductTypeUpdate, @@ -210,7 +210,7 @@ export class ProductType extends Controller { } @Delete("{typeId}") - @Security("keycloak", ["system", "head_of_admin", "admin", "accountant"]) + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) async deleteProductType(@Path() typeId: string) { const record = await prisma.productType.findFirst({ where: { id: typeId } }); From 648f101fd76fab77e3f73e5bb49c55099837a0a6 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:32:32 +0700 Subject: [PATCH 020/102] feat: add user and assign to branch at the same time --- .../migration.sql | 21 ++ prisma/schema.prisma | 165 ++++++----- src/controllers/branch-user-controller.ts | 20 +- src/controllers/user-controller.ts | 266 +++++++++++++----- 4 files changed, 330 insertions(+), 142 deletions(-) create mode 100644 prisma/migrations/20240703043154_update_structure/migration.sql diff --git a/prisma/migrations/20240703043154_update_structure/migration.sql b/prisma/migrations/20240703043154_update_structure/migration.sql new file mode 100644 index 0000000..a6692a4 --- /dev/null +++ b/prisma/migrations/20240703043154_update_structure/migration.sql @@ -0,0 +1,21 @@ +-- AlterTable +ALTER TABLE "Customer" ADD COLUMN "registeredBranchId" TEXT; + +-- AlterTable +ALTER TABLE "Product" ADD COLUMN "registeredBranchId" TEXT; + +-- AlterTable +ALTER TABLE "Service" ADD COLUMN "productTypeId" TEXT, +ADD COLUMN "registeredBranchId" TEXT; + +-- AddForeignKey +ALTER TABLE "Customer" ADD CONSTRAINT "Customer_registeredBranchId_fkey" FOREIGN KEY ("registeredBranchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Product" ADD CONSTRAINT "Product_registeredBranchId_fkey" FOREIGN KEY ("registeredBranchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Service" ADD CONSTRAINT "Service_productTypeId_fkey" FOREIGN KEY ("productTypeId") REFERENCES "ProductType"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Service" ADD CONSTRAINT "Service_registeredBranchId_fkey" FOREIGN KEY ("registeredBranchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0a12981..bd0bc3b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -230,6 +230,10 @@ model Branch { branch Branch[] @relation(name: "HeadOfficeRelation") contact BranchContact[] user BranchUser[] + + productRegistration Product[] + serviceRegistration Service[] + customerRegistration Customer[] } model BranchContact { @@ -391,6 +395,9 @@ model Customer { status Status @default(CREATED) statusOrder Int @default(0) + registeredBranchId String? + registeredBranch Branch? @relation(fields: [registeredBranchId], references: [id]) + createdAt DateTime @default(now()) createdBy User? @relation(name: "CustomerCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) createdByUserId String? @@ -616,6 +623,84 @@ model EmployeeOtherInfo { updatedByUserId String? } +model ProductGroup { + id String @id @default(uuid()) + + code String + name String + detail String + remark String + + status Status @default(CREATED) + statusOrder Int @default(0) + + createdAt DateTime @default(now()) + createdBy User? @relation(name: "ProductGroupCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "ProductGroupUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? + + type ProductType[] +} + +model ProductType { + id String @id @default(uuid()) + + code String + name String + detail String + remark String + + status Status @default(CREATED) + statusOrder Int @default(0) + + createdAt DateTime @default(now()) + createdBy User? @relation(name: "ProductTypeCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "ProductTypeUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? + + productGroup ProductGroup @relation(fields: [productGroupId], references: [id], onDelete: Cascade) + productGroupId String + + product Product[] + service Service[] +} + +model Product { + id String @id @default(uuid()) + + code String + name String + detail String + process Int + price Float + agentPrice Float + serviceCharge Float + + status Status @default(CREATED) + statusOrder Int @default(0) + + remark String? + + productType ProductType? @relation(fields: [productTypeId], references: [id], onDelete: SetNull) + productTypeId String? + + registeredBranchId String? + registeredBranch Branch? @relation(fields: [registeredBranchId], references: [id]) + + workProduct WorkProduct[] + + createdAt DateTime @default(now()) + createdBy User? @relation(name: "ProductCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "ProductUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? +} + model Service { id String @id @default(uuid()) @@ -629,6 +714,12 @@ model Service { work Work[] + productType ProductType? @relation(fields: [productTypeId], references: [id], onDelete: SetNull) + productTypeId String? + + registeredBranchId String? + registeredBranch Branch? @relation(fields: [registeredBranchId], references: [id]) + createdAt DateTime @default(now()) createdBy User? @relation(name: "ServiceCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) createdByUserId String? @@ -676,77 +767,3 @@ model WorkProduct { @@id([workId, productId]) } - -model ProductGroup { - id String @id @default(uuid()) - - code String - name String - detail String - remark String - - status Status @default(CREATED) - statusOrder Int @default(0) - - createdAt DateTime @default(now()) - createdBy User? @relation(name: "ProductGroupCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) - createdByUserId String? - updatedAt DateTime @updatedAt - updatedBy User? @relation(name: "ProductGroupUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) - updatedByUserId String? - - type ProductType[] -} - -model ProductType { - id String @id @default(uuid()) - - code String - name String - detail String - remark String - - status Status @default(CREATED) - statusOrder Int @default(0) - - createdAt DateTime @default(now()) - createdBy User? @relation(name: "ProductTypeCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) - createdByUserId String? - updatedAt DateTime @updatedAt - updatedBy User? @relation(name: "ProductTypeUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) - updatedByUserId String? - - productGroup ProductGroup @relation(fields: [productGroupId], references: [id], onDelete: Cascade) - productGroupId String - - product Product[] -} - -model Product { - id String @id @default(uuid()) - - code String - name String - detail String - process Int - price Float - agentPrice Float - serviceCharge Float - - status Status @default(CREATED) - statusOrder Int @default(0) - - remark String? - - productType ProductType? @relation(fields: [productTypeId], references: [id], onDelete: SetNull) - productTypeId String? - - workProduct WorkProduct[] - - createdAt DateTime @default(now()) - createdBy User? @relation(name: "ProductCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) - createdByUserId String? - updatedAt DateTime @updatedAt - updatedBy User? @relation(name: "ProductUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) - updatedByUserId String? -} diff --git a/src/controllers/branch-user-controller.ts b/src/controllers/branch-user-controller.ts index 4be9ac3..7fa62f4 100644 --- a/src/controllers/branch-user-controller.ts +++ b/src/controllers/branch-user-controller.ts @@ -53,9 +53,9 @@ async function userBranchCodeGen(branch: Branch, user: User[]) { @Route("api/v1/branch/{branchId}/user") @Tags("Branch User") -@Security("keycloak") export class BranchUserController extends Controller { @Get() + @Security("keycloak") async getBranchUser( @Path() branchId: string, @Query() zipCode?: string, @@ -97,6 +97,7 @@ export class BranchUserController extends Controller { } @Post() + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_admin", "branch_manager"]) async createBranchUser( @Request() req: RequestWithUser, @Path() branchId: string, @@ -104,6 +105,11 @@ export class BranchUserController extends Controller { ) { const [branch, user] = await prisma.$transaction([ prisma.branch.findUnique({ + include: { + user: { + where: { userId: req.user.sub }, + }, + }, where: { id: branchId }, }), prisma.user.findMany({ @@ -112,6 +118,18 @@ export class BranchUserController extends Controller { }), ]); + if ( + !["system", "head_of_admin", "admin"].some((v) => req.user.roles?.includes(v)) && + branch?.createdByUserId !== req.user.sub && + !branch?.user.find((v) => v.userId === req.user.sub) + ) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } + if (!branch) { throw new HttpError(HttpStatus.BAD_REQUEST, "Branch cannot be found.", "branchBadReq"); } diff --git a/src/controllers/user-controller.ts b/src/controllers/user-controller.ts index af6e112..5a1fa45 100644 --- a/src/controllers/user-controller.ts +++ b/src/controllers/user-controller.ts @@ -12,7 +12,7 @@ import { Security, Tags, } from "tsoa"; -import { Prisma, Status, UserType } from "@prisma/client"; +import { Branch, Prisma, Status, User, UserType } from "@prisma/client"; import prisma from "../db"; import minio, { presignedGetObjectIfExist } from "../services/minio"; @@ -73,6 +73,8 @@ type UserCreate = { subDistrictId?: string | null; districtId?: string | null; provinceId?: string | null; + + branchId: string | string[]; }; type UserUpdate = { @@ -113,8 +115,37 @@ type UserUpdate = { subDistrictId?: string | null; districtId?: string | null; provinceId?: string | null; + + branchId?: string | string[]; }; +async function userBranchCodeGen(user: User, branch: Branch) { + return await prisma.$transaction( + async (tx) => { + const typ = user.userType; + + const last = await tx.runningNo.upsert({ + where: { + key: `BR_USR_${branch.code.slice(4).padEnd(3, "0")}${typ !== "USER" ? typ.charAt(0).toLocaleUpperCase() : ""}`, + }, + create: { + key: `BR_USR_${branch.code.slice(4).padEnd(3, "0")}${typ !== "USER" ? typ.charAt(0).toLocaleUpperCase() : ""}`, + value: 1, + }, + update: { value: { increment: 1 } }, + }); + + return await tx.user.update({ + where: { id: user.id }, + data: { + code: `${last.key.slice(7)}${last.value.toString().padStart(4, "0")}`, + }, + }); + }, + { isolationLevel: Prisma.TransactionIsolationLevel.Serializable }, + ); +} + function imageLocation(id: string) { return `user/profile-img-${id}`; } @@ -227,38 +258,58 @@ export class UserController extends Controller { } @Post() - @Security("keycloak") + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_admin"]) async createUser(@Request() req: RequestWithUser, @Body() body: UserCreate) { - if (body.provinceId || body.districtId || body.subDistrictId) { - const [province, district, subDistrict] = await prisma.$transaction([ - prisma.province.findFirst({ where: { id: body.provinceId ?? undefined } }), - prisma.district.findFirst({ where: { id: body.districtId ?? undefined } }), - prisma.subDistrict.findFirst({ where: { id: body.subDistrictId ?? undefined } }), - ]); - if (body.provinceId && !province) { - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Province cannot be found.", - "relationProvinceNotFound", - ); - } - if (body.districtId && !district) { - throw new HttpError( - HttpStatus.BAD_REQUEST, - "District cannot be found.", - "relationDistrictNotFound", - ); - } - if (body.subDistrictId && !subDistrict) { - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Sub-district cannot be found.", - "relationSubDistrictNotFound", - ); - } + const [province, district, subDistrict, branch] = await prisma.$transaction([ + prisma.province.findFirst({ where: { id: body.provinceId ?? undefined } }), + prisma.district.findFirst({ where: { id: body.districtId ?? undefined } }), + prisma.subDistrict.findFirst({ where: { id: body.subDistrictId ?? undefined } }), + prisma.branch.findMany({ + include: { user: { where: { userId: req.user.sub } } }, + where: { id: { in: Array.isArray(body.branchId) ? body.branchId : [body.branchId] } }, + }), + ]); + if (body.provinceId && !province) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Province cannot be found.", + "relationProvinceNotFound", + ); + } + if (body.districtId && !district) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "District cannot be found.", + "relationDistrictNotFound", + ); + } + if (body.subDistrictId && !subDistrict) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Sub-district cannot be found.", + "relationSubDistrictNotFound", + ); + } + if (branch.length === 0) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Require at least one branch for a user.", + "minimumBranchNotMet", + ); + } + if ( + !["system", "head_of_admin", "admin"].some((v) => req.user.roles?.includes(v)) && + branch?.some((v) => !v.user.find((v) => v.userId === req.user.sub)) + ) { + console.log(req.user.roles); + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); } - const { provinceId, districtId, subDistrictId, username, ...rest } = body; + const { branchId, provinceId, districtId, subDistrictId, username, ...rest } = body; let list = await listRole(); @@ -312,6 +363,26 @@ export class UserController extends Controller { }, }); + await prisma.branchUser.createMany({ + data: Array.isArray(branchId) + ? branchId.map((v) => ({ + branchId: v, + userId: record.id, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, + })) + : { + branchId, + userId: record.id, + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, + }, + }); + + const updated = await userBranchCodeGen(record, branch[0]); // only generate code by using first branch only + + record.code = updated.code; + this.setStatus(HttpStatus.CREATED); return Object.assign(record, { @@ -329,37 +400,66 @@ export class UserController extends Controller { } @Put("{userId}") - @Security("keycloak") + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_admin", "branch_manager"]) async editUser( @Request() req: RequestWithUser, @Body() body: UserUpdate, @Path() userId: string, ) { - if (body.subDistrictId || body.districtId || body.provinceId) { - const [province, district, subDistrict] = await prisma.$transaction([ - prisma.province.findFirst({ where: { id: body.provinceId || undefined } }), - prisma.district.findFirst({ where: { id: body.districtId || undefined } }), - prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }), - ]); - - if (body.provinceId && !province) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Province cannot be found.", - "missing_or_invalid_parameter", - ); - if (body.districtId && !district) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "District cannot be found.", - "missing_or_invalid_parameter", - ); - if (body.subDistrictId && !subDistrict) - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Sub-district cannot be found.", - "missing_or_invalid_parameter", - ); + const [province, district, subDistrict, user, branch] = await prisma.$transaction([ + prisma.province.findFirst({ where: { id: body.provinceId || undefined } }), + prisma.district.findFirst({ where: { id: body.districtId || undefined } }), + prisma.subDistrict.findFirst({ where: { id: body.subDistrictId || undefined } }), + prisma.user.findFirst({ + include: { branch: true }, + where: { id: userId }, + }), + prisma.branch.findMany({ + include: { user: { where: { id: req.user.sub } } }, + where: { + id: { + in: Array.isArray(body.branchId) ? body.branchId : body.branchId ? [body.branchId] : [], + }, + }, + }), + ]); + if (!user) { + throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "userNotFound"); + } + if (body.provinceId && !province) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Province cannot be found.", + "missing_or_invalid_parameter", + ); + if (body.districtId && !district) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "District cannot be found.", + "missing_or_invalid_parameter", + ); + if (body.subDistrictId && !subDistrict) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Sub-district cannot be found.", + "missing_or_invalid_parameter", + ); + if (body.branchId && branch.length === 0) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Require at least one branch for a user.", + "minimumBranchNotMet", + ); + } + if ( + !["system", "head_of_admin", "admin"].some((v) => req.user.roles?.includes(v)) && + branch?.some((v) => !v.user.find((v) => v.userId === req.user.sub)) + ) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); } let userRole: string | undefined; @@ -404,15 +504,7 @@ export class UserController extends Controller { await editUser(userId, { username: body.username, enabled: body.status !== "INACTIVE" }); } - const { provinceId, districtId, subDistrictId, ...rest } = body; - - const user = await prisma.user.findFirst({ - where: { id: userId }, - }); - - if (!user) { - throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound"); - } + const { provinceId, districtId, subDistrictId, branchId, ...rest } = body; const lastUserOfType = body.userType && @@ -459,6 +551,30 @@ export class UserController extends Controller { where: { id: userId }, }); + if (branchId) { + await prisma.$transaction([ + prisma.branchUser.deleteMany({ + where: { + userId, + branchId: { not: { in: Array.isArray(branchId) ? branchId : [branchId] } }, + }, + }), + prisma.branchUser.createMany({ + data: (Array.isArray(branchId) ? branchId : [branchId]) + .filter((a) => !user.branch.some((b) => a === b.branchId)) + .map((v) => ({ + userId, + branchId: v, + })), + }), + ]); + + if (branch[0]?.id !== user.branch[0]?.id) { + const updated = await userBranchCodeGen(user, branch[0]); + record.code = updated.code; + } + } + return Object.assign(record, { profileImageUrl: await minio.presignedGetObject( MINIO_BUCKET, @@ -474,8 +590,8 @@ export class UserController extends Controller { } @Delete("{userId}") - @Security("keycloak") - async deleteUser(@Path() userId: string) { + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_admin", "branch_manager"]) + async deleteUser(@Request() req: RequestWithUser, @Path() userId: string) { const record = await prisma.user.findFirst({ include: { province: true, @@ -483,10 +599,26 @@ export class UserController extends Controller { subDistrict: true, createdBy: true, updatedBy: true, + branch: { + where: { + userId: req.user.sub, + }, + }, }, where: { id: userId }, }); + if ( + !["system", "head_of_admin", "admin"].some((v) => req.user.roles?.includes(v)) && + !record?.branch.some((v) => v.userId === req.user.sub) + ) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } + if (!record) { throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "userNotFound"); } From 68bed318008f5dabd2abd235533613cb0e383d34 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:33:12 +0700 Subject: [PATCH 021/102] feat: add product type to service --- src/controllers/product-service-controller.ts | 4 ++-- src/controllers/service/service-controller.ts | 21 ++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/controllers/product-service-controller.ts b/src/controllers/product-service-controller.ts index da06d9e..f81f813 100644 --- a/src/controllers/product-service-controller.ts +++ b/src/controllers/product-service-controller.ts @@ -46,7 +46,7 @@ export class ProductServiceController extends Controller { null as "remark", "status", "statusOrder", - null as "productTypeId", + "productTypeId", "createdByUserId", "createdAt", "updatedByUserId", @@ -61,7 +61,7 @@ export class ProductServiceController extends Controller { if (query) or.push(Prisma.sql`"name" LIKE ${`%${query}%`}`); if (status) and.push(Prisma.sql`"status" = ${status}::"Status"`); if (productTypeId) { - and.push(Prisma.sql`("productTypeId" = ${productTypeId} OR ("type" = 'service'))`); + and.push(Prisma.sql`("productTypeId" = ${productTypeId})`); } const where = Prisma.sql` diff --git a/src/controllers/service/service-controller.ts b/src/controllers/service/service-controller.ts index 441824b..f1fb84c 100644 --- a/src/controllers/service/service-controller.ts +++ b/src/controllers/service/service-controller.ts @@ -39,6 +39,7 @@ type ServiceCreate = { productId: string[]; attributes?: { [key: string]: any }; }[]; + productTypeId: string; }; type ServiceUpdate = { @@ -53,6 +54,7 @@ type ServiceUpdate = { productId: string[]; attributes?: { [key: string]: any }; }[]; + productTypeId?: string; }; function imageLocation(id: string) { @@ -198,7 +200,23 @@ export class ServiceController extends Controller { @Post() @Security("keycloak") async createService(@Request() req: RequestWithUser, @Body() body: ServiceCreate) { - const { work, ...payload } = body; + const { work, productTypeId, ...payload } = body; + + const productType = await prisma.productType.findFirst({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: body.productTypeId }, + }); + + if (!productType) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Product Type cannot be found.", + "relationProductTypeNotFound", + ); + } const record = await prisma.$transaction( async (tx) => { @@ -250,6 +268,7 @@ export class ServiceController extends Controller { }, data: { ...payload, + productTypeId, statusOrder: +(body.status === "INACTIVE"), code: `${body.code.toLocaleUpperCase()}${last.value.toString().padStart(3, "0")}`, work: { connect: workList.map((v) => ({ id: v.id })) }, From 14004b81c982016912809f898541ae10362a6ba4 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:33:27 +0700 Subject: [PATCH 022/102] feat: add count of service under group / type --- src/controllers/product/group-controller.ts | 10 +++++++--- src/controllers/product/type-controller.ts | 5 ++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/controllers/product/group-controller.ts b/src/controllers/product/group-controller.ts index bc135fb..8a453bc 100644 --- a/src/controllers/product/group-controller.ts +++ b/src/controllers/product/group-controller.ts @@ -84,9 +84,9 @@ export class ProductGroup extends Controller { prisma.productGroup.count({ where }), ]); - const statsProduct = await prisma.productType.findMany({ + const statsDeep = await prisma.productType.findMany({ include: { - _count: { select: { product: true } }, + _count: { select: { product: true, service: true } }, }, where: { productGroupId: { in: result.map((v) => v.id) }, @@ -98,10 +98,14 @@ export class ProductGroup extends Controller { ...v, _count: { ...v._count, - product: statsProduct.reduce( + product: statsDeep.reduce( (a, c) => (c.productGroupId === v.id ? a + c._count.product : a), 0, ), + service: statsDeep.reduce( + (a, c) => (c.productGroupId === v.id ? a + c._count.service : a), + 0, + ), }, })), page, diff --git a/src/controllers/product/type-controller.ts b/src/controllers/product/type-controller.ts index 886eb5c..42172ad 100644 --- a/src/controllers/product/type-controller.ts +++ b/src/controllers/product/type-controller.ts @@ -70,7 +70,10 @@ export class ProductType extends Controller { prisma.productType.findMany({ include: { _count: { - select: { product: true }, + select: { + product: true, + service: true, + }, }, createdBy: true, updatedBy: true, From 6d367aa61025fda68c455924c517cf6edb7a6996 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 11:35:03 +0700 Subject: [PATCH 023/102] feat: query service by product type --- src/controllers/service/service-controller.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/controllers/service/service-controller.ts b/src/controllers/service/service-controller.ts index f1fb84c..4ca1326 100644 --- a/src/controllers/service/service-controller.ts +++ b/src/controllers/service/service-controller.ts @@ -77,6 +77,7 @@ export class ServiceController extends Controller { @Query() page: number = 1, @Query() pageSize: number = 30, @Query() status?: Status, + @Query() productTypeId?: string, ) { const filterStatus = (val?: Status) => { if (!val) return {}; @@ -88,8 +89,8 @@ export class ServiceController extends Controller { const where = { OR: [ - { name: { contains: query }, ...filterStatus(status) }, - { detail: { contains: query }, ...filterStatus(status) }, + { name: { contains: query }, productTypeId, ...filterStatus(status) }, + { detail: { contains: query }, productTypeId, ...filterStatus(status) }, ], } satisfies Prisma.ServiceWhereInput; From c3e16d058068da0f59f1c5850dc35f508168bf56 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:36:11 +0700 Subject: [PATCH 024/102] refactor: adjust code --- src/controllers/customer-controller.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index f97ca6d..2a51ad1 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -24,8 +24,11 @@ if (!process.env.MINIO_BUCKET) { } const MINIO_BUCKET = process.env.MINIO_BUCKET; +const MANAGE_ROLES = ["system", "head_of_admin", "admin", "head_of_sale", "sale"]; export type CustomerCreate = { + registeredBranchId?: string; + status?: Status; personName: string; personNameEN?: string; @@ -68,6 +71,8 @@ export type CustomerCreate = { }; export type CustomerUpdate = { + registeredBranchId?: string; + status?: "ACTIVE" | "INACTIVE"; personName?: string; personNameEN?: string; @@ -232,7 +237,7 @@ export class CustomerController extends Controller { } @Post() - @Security("keycloak", ["system", "head_of_admin", "admin", "head_of_sale", "sale"]) + @Security("keycloak", MANAGE_ROLES) async create(@Request() req: RequestWithUser, @Body() body: CustomerCreate) { const { customerBranch, ...payload } = body; @@ -250,10 +255,11 @@ export class CustomerController extends Controller { return acc; }, []); - const [province, district, subDistrict] = await prisma.$transaction([ + const [province, district, subDistrict, branch] = await prisma.$transaction([ prisma.province.findMany({ where: { id: { in: provinceId } } }), prisma.district.findMany({ where: { id: { in: districtId } } }), prisma.subDistrict.findMany({ where: { id: { in: subDistrictId } } }), + prisma.branch.findFirst({ where: { id: body.registeredBranchId } }), ]); if (provinceId && province.length !== provinceId?.length) { @@ -277,6 +283,13 @@ export class CustomerController extends Controller { "relationSubDistrictNotFound", ); } + if (body.registeredBranchId && !branch) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Branch cannot be found.", + "relationBranchNotFound", + ); + } const record = await prisma.$transaction( async (tx) => { @@ -344,7 +357,7 @@ export class CustomerController extends Controller { } @Put("{customerId}") - @Security("keycloak", ["system", "head_of_admin", "admin", "head_of_sale", "sale"]) + @Security("keycloak", MANAGE_ROLES) async editById( @Path() customerId: string, @Request() req: RequestWithUser, @@ -504,7 +517,7 @@ export class CustomerController extends Controller { } @Delete("{customerId}") - @Security("keycloak", ["system", "head_of_admin", "admin", "head_of_sale", "sale"]) + @Security("keycloak", MANAGE_ROLES) async deleteById(@Path() customerId: string) { const record = await prisma.customer.findFirst({ where: { id: customerId } }); From 20c74144078bf3aff6d2ef86dd809613c2ea32ed Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 14:40:07 +0700 Subject: [PATCH 025/102] feat: add more role to endpoints --- src/controllers/customer-controller.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index 2a51ad1..9509f37 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -24,7 +24,15 @@ if (!process.env.MINIO_BUCKET) { } const MINIO_BUCKET = process.env.MINIO_BUCKET; -const MANAGE_ROLES = ["system", "head_of_admin", "admin", "head_of_sale", "sale"]; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "branch_admin", + "branch_manager", + "head_of_sale", + "sale", +]; export type CustomerCreate = { registeredBranchId?: string; From 9a310420e51f22f33d40bdf1d5d7898fa9ec5f13 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 15:42:49 +0700 Subject: [PATCH 026/102] feat: protect endpoints with role --- src/controllers/employee-checkup-controller.ts | 16 +++++++++++++++- src/controllers/employee-controller.ts | 17 ++++++++++++++++- .../employee-other-info-controller.ts | 15 ++++++++++++++- src/controllers/employee-work-controller.ts | 16 +++++++++++++++- 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/controllers/employee-checkup-controller.ts b/src/controllers/employee-checkup-controller.ts index c04bb13..309e858 100644 --- a/src/controllers/employee-checkup-controller.ts +++ b/src/controllers/employee-checkup-controller.ts @@ -16,6 +16,16 @@ import prisma from "../db"; import HttpStatus from "../interfaces/http-status"; import HttpError from "../interfaces/http-error"; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "branch_admin", + "branch_manager", + "head_of_sale", + "sale", +]; + type EmployeeCheckupPayload = { checkupType?: string | null; checkupResult?: string | null; @@ -32,9 +42,9 @@ type EmployeeCheckupPayload = { @Route("api/v1/employee/{employeeId}/checkup") @Tags("Employee Checkup") -@Security("keycloak") export class EmployeeCheckupController extends Controller { @Get() + @Security("keycloak") async list(@Path() employeeId: string) { return prisma.employeeCheckup.findMany({ include: { @@ -47,6 +57,7 @@ export class EmployeeCheckupController extends Controller { } @Get("{checkupId}") + @Security("keycloak") async getById(@Path() employeeId: string, @Path() checkupId: string) { const record = await prisma.employeeCheckup.findFirst({ include: { @@ -66,6 +77,7 @@ export class EmployeeCheckupController extends Controller { } @Post() + @Security("keycloak", MANAGE_ROLES) async create( @Request() req: RequestWithUser, @Path() employeeId: string, @@ -109,6 +121,7 @@ export class EmployeeCheckupController extends Controller { } @Put("{checkupId}") + @Security("keycloak", MANAGE_ROLES) async editById( @Request() req: RequestWithUser, @Path() employeeId: string, @@ -165,6 +178,7 @@ export class EmployeeCheckupController extends Controller { } @Delete("{checkupId}") + @Security("keycloak", MANAGE_ROLES) async deleteById(@Path() employeeId: string, @Path() checkupId: string) { const record = await prisma.employeeCheckup.findFirst({ where: { id: checkupId, employeeId } }); diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts index e3186c0..1d29af2 100644 --- a/src/controllers/employee-controller.ts +++ b/src/controllers/employee-controller.ts @@ -24,6 +24,15 @@ if (!process.env.MINIO_BUCKET) { } const MINIO_BUCKET = process.env.MINIO_BUCKET; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "branch_admin", + "branch_manager", + "head_of_sale", + "sale", +]; function imageLocation(id: string) { return `employee/${id}/profile-image`; @@ -200,9 +209,9 @@ type EmployeeUpdate = { @Route("api/v1/employee") @Tags("Employee") -@Security("keycloak") export class EmployeeController extends Controller { @Get("stats") + @Security("keycloak") async getEmployeeStats(@Query() customerBranchId?: string) { return await prisma.employee.count({ where: { customerBranchId }, @@ -210,6 +219,7 @@ export class EmployeeController extends Controller { } @Get("stats/gender") + @Security("keycloak") async getEmployeeStatsGender( @Query() customerBranchId?: string, @Query() status?: Status, @@ -245,6 +255,7 @@ export class EmployeeController extends Controller { } @Get() + @Security("keycloak") async list( @Query() zipCode?: string, @Query() gender?: string, @@ -305,6 +316,7 @@ export class EmployeeController extends Controller { } @Get("{employeeId}") + @Security("keycloak") async getById(@Path() employeeId: string) { const record = await prisma.employee.findFirst({ include: { @@ -325,6 +337,7 @@ export class EmployeeController extends Controller { } @Post() + @Security("keycloak", MANAGE_ROLES) async create(@Request() req: RequestWithUser, @Body() body: EmployeeCreate) { const [province, district, subDistrict, customerBranch] = await prisma.$transaction([ prisma.province.findFirst({ where: { id: body.provinceId || undefined } }), @@ -483,6 +496,7 @@ export class EmployeeController extends Controller { } @Put("{employeeId}") + @Security("keycloak", MANAGE_ROLES) async editById( @Request() req: RequestWithUser, @Body() body: EmployeeUpdate, @@ -709,6 +723,7 @@ export class EmployeeController extends Controller { } @Delete("{employeeId}") + @Security("keycloak", MANAGE_ROLES) async delete(@Path() employeeId: string) { const record = await prisma.employee.findFirst({ where: { id: employeeId } }); diff --git a/src/controllers/employee-other-info-controller.ts b/src/controllers/employee-other-info-controller.ts index adc530d..7867eb7 100644 --- a/src/controllers/employee-other-info-controller.ts +++ b/src/controllers/employee-other-info-controller.ts @@ -17,6 +17,16 @@ import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { RequestWithUser } from "../interfaces/user"; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "branch_admin", + "branch_manager", + "head_of_sale", + "sale", +]; + type EmployeeOtherInfoPayload = { citizenId?: string | null; fatherFirstName?: string | null; @@ -34,9 +44,9 @@ type EmployeeOtherInfoPayload = { @Route("api/v1/employee/{employeeId}/other-info") @Tags("Employee Other Info") -@Security("keycloak") export class EmployeeOtherInfo extends Controller { @Get() + @Security("keycloak") async list(@Path() employeeId: string) { return prisma.employeeOtherInfo.findFirst({ include: { @@ -49,6 +59,7 @@ export class EmployeeOtherInfo extends Controller { } @Post() + @Security("keycloak", MANAGE_ROLES) async create( @Request() req: RequestWithUser, @Path() employeeId: string, @@ -76,6 +87,7 @@ export class EmployeeOtherInfo extends Controller { } @Put("{otherInfoId}") + @Security("keycloak", MANAGE_ROLES) async editById( @Request() req: RequestWithUser, @Path() employeeId: string, @@ -105,6 +117,7 @@ export class EmployeeOtherInfo extends Controller { } @Delete("{otherInfoId}") + @Security("keycloak", MANAGE_ROLES) async deleteById(@Path() employeeId: string, @Path() otherInfoId: string) { const record = await prisma.employeeOtherInfo.findFirst({ where: { id: otherInfoId, employeeId }, diff --git a/src/controllers/employee-work-controller.ts b/src/controllers/employee-work-controller.ts index 95d9625..cab015b 100644 --- a/src/controllers/employee-work-controller.ts +++ b/src/controllers/employee-work-controller.ts @@ -16,6 +16,16 @@ import prisma from "../db"; import HttpStatus from "../interfaces/http-status"; import HttpError from "../interfaces/http-error"; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "branch_admin", + "branch_manager", + "head_of_sale", + "sale", +]; + type EmployeeWorkPayload = { ownerName?: string | null; positionName?: string | null; @@ -30,9 +40,9 @@ type EmployeeWorkPayload = { @Route("api/v1/employee/{employeeId}/work") @Tags("Employee Work") -@Security("keycloak") export class EmployeeWorkController extends Controller { @Get() + @Security("keycloak") async list(@Path() employeeId: string) { return prisma.employeeWork.findMany({ include: { @@ -45,6 +55,7 @@ export class EmployeeWorkController extends Controller { } @Get("{workId}") + @Security("keycloak") async getById(@Path() employeeId: string, @Path() workId: string) { const record = await prisma.employeeWork.findFirst({ include: { @@ -64,6 +75,7 @@ export class EmployeeWorkController extends Controller { } @Post() + @Security("keycloak", MANAGE_ROLES) async create( @Request() req: RequestWithUser, @Path() employeeId: string, @@ -91,6 +103,7 @@ export class EmployeeWorkController extends Controller { } @Put("{workId}") + @Security("keycloak", MANAGE_ROLES) async editById( @Request() req: RequestWithUser, @Path() employeeId: string, @@ -120,6 +133,7 @@ export class EmployeeWorkController extends Controller { } @Delete("{workId}") + @Security("keycloak", MANAGE_ROLES) async deleteById(@Path() employeeId: string, @Path() workId: string) { const record = await prisma.employeeWork.findFirst({ include: { From edb9e34a2b8cc901dc1a8d9c771b39b614726442 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:05:31 +0700 Subject: [PATCH 027/102] refactor: accept branchNo instead of auto --- src/controllers/customer-controller.ts | 27 ++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index 9509f37..6b1d207 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -49,6 +49,7 @@ export type CustomerCreate = { legalPersonNo: string; + branchNo: number; taxNo: string | null; name: string; nameEN: string; @@ -95,6 +96,7 @@ export type CustomerUpdate = { legalPersonNo: string; + branchNo: number; taxNo: string | null; name: string; nameEN: string; @@ -331,10 +333,9 @@ export class CustomerController extends Controller { branch: { createMany: { data: - customerBranch?.map((v, i) => ({ + customerBranch?.map((v) => ({ ...v, - branchNo: i + 1, - code: `${last.key.slice(9)}${last.value.toString().padStart(6, "0")}-${(i + 1).toString().padStart(2, "0")}`, + code: `${last.key.slice(9)}${last.value.toString().padStart(6, "0")}-${v.branchNo.toString().padStart(2, "0")}`, createdByUserId: req.user.sub, updatedByUserId: req.user.sub, })) || [], @@ -438,6 +439,17 @@ export class CustomerController extends Controller { ); } + if ( + customerBranch && + relation.find((a) => !customerBranch.find((b) => a.id !== b.id && a.branchNo === b.branchNo)) + ) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Branch cannot have same number.", + "oneOrMoreBranchNoExist", + ); + } + const record = await prisma.customer .update({ include: { @@ -463,20 +475,19 @@ export class CustomerController extends Controller { }, status: Status.CREATED, }, - upsert: customerBranch.map((v, i) => ({ + upsert: customerBranch.map((v) => ({ where: { id: v.id || "" }, create: { ...v, - branchNo: i + 1, - code: `${customer.code}-${(i + 1).toString().padStart(2, "0")}`, + code: `${customer.code}-${v.branchNo.toString().padStart(2, "0")}`, createdByUserId: req.user.sub, updatedByUserId: req.user.sub, id: undefined, }, update: { ...v, - branchNo: i + 1, - code: `${customer.code}-${(i + 1).toString().padStart(2, "0")}`, + code: undefined, + branchNo: undefined, updatedByUserId: req.user.sub, }, })), From 1ac03f3f024193b8bb9826af51b4168a6a7dc1fa Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:44:33 +0700 Subject: [PATCH 028/102] fix: error wrong cond --- src/controllers/customer-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index 6b1d207..29571cc 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -441,7 +441,7 @@ export class CustomerController extends Controller { if ( customerBranch && - relation.find((a) => !customerBranch.find((b) => a.id !== b.id && a.branchNo === b.branchNo)) + relation.find((a) => customerBranch.find((b) => a.id !== b.id && a.branchNo === b.branchNo)) ) { throw new HttpError( HttpStatus.BAD_REQUEST, From 390b27716b30adb2fdc85f861c9d6f44b3916d69 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:51:48 +0700 Subject: [PATCH 029/102] feat: scope permission in separated customer branch endpoint --- src/controllers/customer-branch-controller.ts | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/controllers/customer-branch-controller.ts b/src/controllers/customer-branch-controller.ts index 020d5fb..5a8de8a 100644 --- a/src/controllers/customer-branch-controller.ts +++ b/src/controllers/customer-branch-controller.ts @@ -24,6 +24,15 @@ if (!process.env.MINIO_BUCKET) { } const MINIO_BUCKET = process.env.MINIO_BUCKET; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "branch_admin", + "branch_manager", + "head_of_sale", + "sale", +]; function imageLocation(id: string) { return `employee/profile-img-${id}`; @@ -40,6 +49,7 @@ export type CustomerBranchCreate = { legalPersonNo: string; + branchNo: number; taxNo: string | null; name: string; nameEN: string; @@ -105,9 +115,9 @@ export type CustomerBranchUpdate = { @Route("api/v1/customer-branch") @Tags("Customer Branch") -@Security("keycloak") export class CustomerBranchController extends Controller { @Get() + @Security("keycloak") async list( @Query() zipCode?: string, @Query() customerId?: string, @@ -173,6 +183,7 @@ export class CustomerBranchController extends Controller { } @Get("{branchId}") + @Security("keycloak") async getById(@Path() branchId: string) { const record = await prisma.customerBranch.findFirst({ include: { @@ -193,6 +204,7 @@ export class CustomerBranchController extends Controller { } @Get("{branchId}/employee") + @Security("keycloak") async listEmployee( @Path() branchId: string, @Query() zipCode?: string, @@ -245,6 +257,7 @@ export class CustomerBranchController extends Controller { } @Post() + @Security("keycloak", MANAGE_ROLES) async create(@Request() req: RequestWithUser, @Body() body: CustomerBranchCreate) { const [province, district, subDistrict, customer] = await prisma.$transaction([ prisma.province.findFirst({ where: { id: body.provinceId || undefined } }), @@ -281,9 +294,16 @@ export class CustomerBranchController extends Controller { const record = await prisma.$transaction( async (tx) => { - const count = await tx.customerBranch.count({ - where: { customerId }, + const conflict = await tx.customerBranch.findFirst({ + where: { customerId, branchNo: rest.branchNo }, }); + if (conflict) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Branch with current no already exists.", + "branchSameNoExist", + ); + } return await tx.customerBranch.create({ include: { @@ -296,8 +316,7 @@ export class CustomerBranchController extends Controller { data: { ...rest, statusOrder: +(rest.status === "INACTIVE"), - branchNo: count + 1, - code: `${customer.code}-${(count + 1).toString().padStart(2, "0")}`, + code: `${customer.code}-${rest.branchNo.toString().padStart(2, "0")}`, customer: { connect: { id: customerId } }, province: { connect: provinceId ? { id: provinceId } : undefined }, district: { connect: districtId ? { id: districtId } : undefined }, @@ -321,6 +340,7 @@ export class CustomerBranchController extends Controller { } @Put("{branchId}") + @Security("keycloak", MANAGE_ROLES) async editById( @Request() req: RequestWithUser, @Body() body: CustomerBranchUpdate, @@ -400,6 +420,7 @@ export class CustomerBranchController extends Controller { } @Delete("{branchId}") + @Security("keycloak", MANAGE_ROLES) async delete(@Path() branchId: string) { const record = await prisma.customerBranch.findFirst({ where: { id: branchId }, From 694576139796d188c20221c209909532deb313df Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 3 Jul 2024 17:28:00 +0700 Subject: [PATCH 030/102] refactor/product-service-permission --- src/controllers/product/product-controller.ts | 128 ++++++++++++++--- src/controllers/service/service-controller.ts | 134 ++++++++++++++++-- 2 files changed, 233 insertions(+), 29 deletions(-) diff --git a/src/controllers/product/product-controller.ts b/src/controllers/product/product-controller.ts index 7fb7cd1..0c28c47 100644 --- a/src/controllers/product/product-controller.ts +++ b/src/controllers/product/product-controller.ts @@ -25,6 +25,15 @@ if (!process.env.MINIO_BUCKET) { } const MINIO_BUCKET = process.env.MINIO_BUCKET; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "branch_admin", + "branch_manager", + "accountant", + "branch_accountant", +]; type ProductCreate = { status?: Status; @@ -51,6 +60,8 @@ type ProductCreate = { serviceCharge: number; productTypeId: string; remark?: string; + + registeredBranchId?: string; }; type ProductUpdate = { @@ -63,12 +74,20 @@ type ProductUpdate = { serviceCharge?: number; remark?: string; productTypeId?: string; + + registeredBranchId?: string; }; function imageLocation(id: string) { return `product/${id}/image`; } +function globalAllow(roles?: string[]) { + return ["system", "head_of_admin", "admin", "branch_admin", "branch_manager", "accountant"].some( + (v) => roles?.includes(v), + ); +} + @Route("api/v1/product") @Tags("Product") export class ProductController extends Controller { @@ -85,6 +104,7 @@ export class ProductController extends Controller { @Query() query: string = "", @Query() page: number = 1, @Query() pageSize: number = 30, + @Query() branchId?: string, ) { const filterStatus = (val?: Status) => { if (!val) return {}; @@ -99,6 +119,9 @@ export class ProductController extends Controller { { name: { contains: query }, productTypeId, ...filterStatus(status) }, { detail: { contains: query }, productTypeId, ...filterStatus(status) }, ], + AND: { + OR: [{ registeredBranchId: branchId }, { registeredBranchId: null }], + }, } satisfies Prisma.ProductWhereInput; const [result, total] = await prisma.$transaction([ @@ -164,15 +187,29 @@ export class ProductController extends Controller { } @Post() - @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) + @Security("keycloak", MANAGE_ROLES) async createProduct(@Request() req: RequestWithUser, @Body() body: ProductCreate) { - const productType = await prisma.productType.findFirst({ - include: { - createdBy: true, - updatedBy: true, - }, - where: { id: body.productTypeId }, - }); + const [productType, branch] = await prisma.$transaction([ + prisma.productType.findFirst({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: body.productTypeId }, + }), + prisma.branch.findFirst({ + include: { user: { where: { id: req.user.sub } } }, + where: { id: body.registeredBranchId }, + }), + ]); + + if (!globalAllow(req.user.roles) && !branch?.user.find((v) => v.userId === req.user.sub)) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } if (!productType) { throw new HttpError( @@ -182,6 +219,14 @@ export class ProductController extends Controller { ); } + if (body.registeredBranchId && !branch) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Branch cannot be found.", + "relationBranchNotFound", + ); + } + const record = await prisma.$transaction( async (tx) => { const last = await tx.runningNo.upsert({ @@ -241,19 +286,44 @@ export class ProductController extends Controller { } @Put("{productId}") - @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) + @Security("keycloak", MANAGE_ROLES) async editProduct( @Request() req: RequestWithUser, @Body() body: ProductUpdate, @Path() productId: string, ) { - if (!(await prisma.product.findUnique({ where: { id: productId } }))) { + const [product, productType, branch] = await prisma.$transaction([ + prisma.product.findUnique({ + include: { + registeredBranch: { + where: { + user: { some: { userId: req.user.sub } }, + }, + }, + }, + where: { id: productId }, + }), + prisma.productType.findFirst({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: body.productTypeId }, + }), + prisma.branch.findFirst({ where: { id: body.registeredBranchId } }), + ]); + + if (!product) { throw new HttpError(HttpStatus.NOT_FOUND, "Product cannot be found.", "productNotFound"); } - const productType = await prisma.productType.findFirst({ - where: { id: body.productTypeId }, - }); + if (!globalAllow(req.user.roles) && !product.registeredBranch) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } if (!productType) { throw new HttpError( @@ -263,6 +333,14 @@ export class ProductController extends Controller { ); } + if (body.registeredBranchId && !branch) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Branch cannot be found.", + "relationBranchNotFound", + ); + } + const record = await prisma.product.update({ include: { createdBy: true, @@ -294,14 +372,32 @@ export class ProductController extends Controller { } @Delete("{productId}") - @Security("keycloak", ["system", "head_of_admin", "admin", "branch_accountant", "accountant"]) - async deleteProduct(@Path() productId: string) { - const record = await prisma.product.findFirst({ where: { id: productId } }); + @Security("keycloak", MANAGE_ROLES) + async deleteProduct(@Request() req: RequestWithUser, @Path() productId: string) { + const record = await prisma.product.findFirst({ + include: { + registeredBranch: { + where: { + user: { some: { userId: req.user.sub } }, + }, + }, + }, + + where: { id: productId }, + }); if (!record) { throw new HttpError(HttpStatus.NOT_FOUND, "Product cannot be found.", "productNotFound"); } + if (!globalAllow(req.user.roles) && !record.registeredBranch) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } + if (record.status !== Status.CREATED) { throw new HttpError(HttpStatus.FORBIDDEN, "Product is in used.", "productInUsed"); } diff --git a/src/controllers/service/service-controller.ts b/src/controllers/service/service-controller.ts index 4ca1326..1170ab7 100644 --- a/src/controllers/service/service-controller.ts +++ b/src/controllers/service/service-controller.ts @@ -25,6 +25,15 @@ if (!process.env.MINIO_BUCKET) { } const MINIO_BUCKET = process.env.MINIO_BUCKET; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "branch_admin", + "branch_manager", + "accountant", + "branch_accountant", +]; type ServiceCreate = { code: "MOU" | "mou"; @@ -40,6 +49,7 @@ type ServiceCreate = { attributes?: { [key: string]: any }; }[]; productTypeId: string; + registeredBranchId?: string; }; type ServiceUpdate = { @@ -55,12 +65,17 @@ type ServiceUpdate = { attributes?: { [key: string]: any }; }[]; productTypeId?: string; + registeredBranchId?: string; }; function imageLocation(id: string) { return `service/${id}/service-image`; } +function globalAllow(roles?: string[]) { + return ["system", "head_of_admin", "admin", "accountant"].some((v) => roles?.includes(v)); +} + @Route("api/v1/service") @Tags("Service") export class ServiceController extends Controller { @@ -78,6 +93,7 @@ export class ServiceController extends Controller { @Query() pageSize: number = 30, @Query() status?: Status, @Query() productTypeId?: string, + @Query() branchId?: string, ) { const filterStatus = (val?: Status) => { if (!val) return {}; @@ -92,6 +108,9 @@ export class ServiceController extends Controller { { name: { contains: query }, productTypeId, ...filterStatus(status) }, { detail: { contains: query }, productTypeId, ...filterStatus(status) }, ], + AND: { + OR: [{ registeredBranchId: branchId }, { registeredBranchId: null }], + }, } satisfies Prisma.ServiceWhereInput; const [result, total] = await prisma.$transaction([ @@ -199,17 +218,31 @@ export class ServiceController extends Controller { } @Post() - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) async createService(@Request() req: RequestWithUser, @Body() body: ServiceCreate) { const { work, productTypeId, ...payload } = body; - const productType = await prisma.productType.findFirst({ - include: { - createdBy: true, - updatedBy: true, - }, - where: { id: body.productTypeId }, - }); + const [productType, branch] = await prisma.$transaction([ + prisma.productType.findFirst({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: body.productTypeId }, + }), + prisma.branch.findFirst({ + include: { user: { where: { id: req.user.sub } } }, + where: { id: body.registeredBranchId }, + }), + ]); + + if (!globalAllow(req.user.roles) && !branch?.user.find((v) => v.userId === req.user.sub)) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } if (!productType) { throw new HttpError( @@ -219,6 +252,14 @@ export class ServiceController extends Controller { ); } + if (body.registeredBranchId && !branch) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Branch cannot be found.", + "relationBranchNotFound", + ); + } + const record = await prisma.$transaction( async (tx) => { const last = await tx.runningNo.upsert({ @@ -297,7 +338,7 @@ export class ServiceController extends Controller { } @Put("{serviceId}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) async editService( @Request() req: RequestWithUser, @Body() body: ServiceUpdate, @@ -306,7 +347,57 @@ export class ServiceController extends Controller { if (!(await prisma.service.findUnique({ where: { id: serviceId } }))) { throw new HttpError(HttpStatus.NOT_FOUND, "Service cannot be found.", "serviceNotFound"); } - const { work, ...payload } = body; + const { work, productTypeId, ...payload } = body; + + const [service, productType, branch] = await prisma.$transaction([ + prisma.service.findUnique({ + include: { + registeredBranch: { + where: { + user: { some: { userId: req.user.sub } }, + }, + }, + }, + where: { id: serviceId }, + }), + prisma.productType.findFirst({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: body.productTypeId }, + }), + prisma.branch.findFirst({ where: { id: body.registeredBranchId } }), + ]); + + if (!service) { + throw new HttpError(HttpStatus.NOT_FOUND, "Service cannot be found.", "serviceNotFound"); + } + + if (!globalAllow(req.user.roles) && !service.registeredBranch) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } + + if (!productType) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Product Type cannot be found.", + "relationProductTypeNotFound", + ); + } + + if (body.registeredBranchId && !branch) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Branch cannot be found.", + "relationBranchNotFound", + ); + } + const record = await prisma.$transaction(async (tx) => { const workList = await Promise.all( (work || []).map(async (w, wIdx) => @@ -361,14 +452,31 @@ export class ServiceController extends Controller { } @Delete("{serviceId}") - @Security("keycloak") - async deleteService(@Path() serviceId: string) { - const record = await prisma.service.findFirst({ where: { id: serviceId } }); + @Security("keycloak", MANAGE_ROLES) + async deleteService(@Request() req: RequestWithUser, @Path() serviceId: string) { + const record = await prisma.service.findFirst({ + include: { + registeredBranch: { + where: { + user: { some: { userId: req.user.sub } }, + }, + }, + }, + where: { id: serviceId }, + }); if (!record) { throw new HttpError(HttpStatus.NOT_FOUND, "Service cannot be found.", "serviceNotFound"); } + if (!globalAllow(req.user.roles) && !record.registeredBranch) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } + if (record.status !== Status.CREATED) { throw new HttpError(HttpStatus.FORBIDDEN, "Service is in used.", "serviceInUsed"); } From ebdd5924a6e14c7f8824b360fdaee040e1e09a84 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 4 Jul 2024 11:27:19 +0700 Subject: [PATCH 031/102] fix: condition --- src/controllers/product/product-controller.ts | 10 ++++++---- src/controllers/service/service-controller.ts | 10 ++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/src/controllers/product/product-controller.ts b/src/controllers/product/product-controller.ts index 0c28c47..c2e6e88 100644 --- a/src/controllers/product/product-controller.ts +++ b/src/controllers/product/product-controller.ts @@ -104,7 +104,7 @@ export class ProductController extends Controller { @Query() query: string = "", @Query() page: number = 1, @Query() pageSize: number = 30, - @Query() branchId?: string, + @Query() registeredBranchId?: string, ) { const filterStatus = (val?: Status) => { if (!val) return {}; @@ -119,9 +119,11 @@ export class ProductController extends Controller { { name: { contains: query }, productTypeId, ...filterStatus(status) }, { detail: { contains: query }, productTypeId, ...filterStatus(status) }, ], - AND: { - OR: [{ registeredBranchId: branchId }, { registeredBranchId: null }], - }, + AND: registeredBranchId + ? { + OR: [{ registeredBranchId: registeredBranchId }, { registeredBranchId: null }], + } + : undefined, } satisfies Prisma.ProductWhereInput; const [result, total] = await prisma.$transaction([ diff --git a/src/controllers/service/service-controller.ts b/src/controllers/service/service-controller.ts index 1170ab7..cb1b4c5 100644 --- a/src/controllers/service/service-controller.ts +++ b/src/controllers/service/service-controller.ts @@ -93,7 +93,7 @@ export class ServiceController extends Controller { @Query() pageSize: number = 30, @Query() status?: Status, @Query() productTypeId?: string, - @Query() branchId?: string, + @Query() registeredBranchId?: string, ) { const filterStatus = (val?: Status) => { if (!val) return {}; @@ -108,9 +108,11 @@ export class ServiceController extends Controller { { name: { contains: query }, productTypeId, ...filterStatus(status) }, { detail: { contains: query }, productTypeId, ...filterStatus(status) }, ], - AND: { - OR: [{ registeredBranchId: branchId }, { registeredBranchId: null }], - }, + AND: registeredBranchId + ? { + OR: [{ registeredBranchId: registeredBranchId }, { registeredBranchId: null }], + } + : undefined, } satisfies Prisma.ServiceWhereInput; const [result, total] = await prisma.$transaction([ From cdceac85687d91c13ed4c71a3294193669b394d9 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 4 Jul 2024 11:27:26 +0700 Subject: [PATCH 032/102] feat: add query string --- src/controllers/product-service-controller.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/controllers/product-service-controller.ts b/src/controllers/product-service-controller.ts index f81f813..0e278e0 100644 --- a/src/controllers/product-service-controller.ts +++ b/src/controllers/product-service-controller.ts @@ -12,6 +12,7 @@ export class ProductServiceController extends Controller { @Query() productTypeId?: string, @Query() page: number = 1, @Query() pageSize: number = 30, + @Query() registeredBranchId?: string, ) { const union = Prisma.sql` SELECT @@ -27,6 +28,7 @@ export class ProductServiceController extends Controller { "status", "statusOrder", "productTypeId", + "registeredBranchId", "createdByUserId", "createdAt", "updatedByUserId", @@ -47,6 +49,7 @@ export class ProductServiceController extends Controller { "status", "statusOrder", "productTypeId", + "registeredBranchId", "createdByUserId", "createdAt", "updatedByUserId", @@ -63,6 +66,11 @@ export class ProductServiceController extends Controller { if (productTypeId) { and.push(Prisma.sql`("productTypeId" = ${productTypeId})`); } + if (registeredBranchId) { + and.push( + Prisma.sql`("registeredBranchId" = ${registeredBranchId} OR "registeredBranchId" = null)`, + ); + } const where = Prisma.sql` ${or.length > 0 || and.length > 0 ? Prisma.sql`WHERE ` : Prisma.empty} From a4c220c700f7b8dfdbfe45273c7adb1bd8836567 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 5 Jul 2024 11:02:18 +0700 Subject: [PATCH 033/102] fix: sql query --- src/controllers/product-service-controller.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/controllers/product-service-controller.ts b/src/controllers/product-service-controller.ts index 0e278e0..c60ccba 100644 --- a/src/controllers/product-service-controller.ts +++ b/src/controllers/product-service-controller.ts @@ -64,11 +64,11 @@ export class ProductServiceController extends Controller { if (query) or.push(Prisma.sql`"name" LIKE ${`%${query}%`}`); if (status) and.push(Prisma.sql`"status" = ${status}::"Status"`); if (productTypeId) { - and.push(Prisma.sql`("productTypeId" = ${productTypeId})`); + and.push(Prisma.sql`"productTypeId" = ${productTypeId}`); } if (registeredBranchId) { and.push( - Prisma.sql`("registeredBranchId" = ${registeredBranchId} OR "registeredBranchId" = null)`, + Prisma.sql`("registeredBranchId" = ${registeredBranchId} OR "registeredBranchId" IS NULL)`, ); } @@ -78,6 +78,7 @@ export class ProductServiceController extends Controller { ${or.length > 0 && and.length > 0 ? Prisma.sql` AND ` : Prisma.empty} ${and.length > 0 ? Prisma.join(and, " AND ", "(", ")") : Prisma.empty} `; + console.log(where.sql); const [result, [{ total }]] = await prisma.$transaction([ prisma.$queryRaw<((Product & { type: "product" }) | (Service & { type: "service" }))[]>` From 3d0e6a2069dcf60ec649b88ec9ee616cc52f8fd9 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 5 Jul 2024 14:09:15 +0700 Subject: [PATCH 034/102] feat: order relation --- src/controllers/customer-controller.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index 29571cc..06e2f20 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -188,6 +188,9 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, + orderBy: { + branchNo: "asc", + }, } : undefined, createdBy: true, From b9cf8d3af36295af7c29059936c43333e851fc32 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 8 Jul 2024 14:39:36 +0700 Subject: [PATCH 035/102] feat: add relation to employee --- src/controllers/employee-controller.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts index 1d29af2..3d0905a 100644 --- a/src/controllers/employee-controller.ts +++ b/src/controllers/employee-controller.ts @@ -288,6 +288,9 @@ export class EmployeeController extends Controller { province: true, district: true, subDistrict: true, + customerBranch: { + include: { customer: true }, + }, createdBy: true, updatedBy: true, }, From 29e2933390eaf3840487be978ae88c8ba06b60a6 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 8 Jul 2024 17:27:25 +0700 Subject: [PATCH 036/102] fix: add image link --- src/controllers/employee-controller.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts index 3d0905a..918314f 100644 --- a/src/controllers/employee-controller.ts +++ b/src/controllers/employee-controller.ts @@ -336,7 +336,13 @@ export class EmployeeController extends Controller { throw new HttpError(HttpStatus.NOT_FOUND, "Employee cannot be found.", "employeeNotFound"); } - return record; + return Object.assign(record, { + profileImageUrl: await presignedGetObjectIfExist( + MINIO_BUCKET, + imageLocation(employeeId), + 12 * 60 * 60, + ), + }); } @Post() From 28122752fa0d4084e154d6f5fe7f2864e61ec92d Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 10 Jul 2024 10:16:56 +0700 Subject: [PATCH 037/102] feat: add count branch --- src/controllers/branch-controller.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index 5c4f7a7..a4b5f10 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -187,6 +187,9 @@ export class BranchController extends Controller { subDistrict: true, }, }, + _count: { + select: { branch: true }, + }, createdBy: true, updatedBy: true, }, From 7ab828ab5001441143fe9df60a68e3c7886c4b76 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 10 Jul 2024 15:27:36 +0700 Subject: [PATCH 038/102] feat: change format --- src/middlewares/log.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/middlewares/log.ts b/src/middlewares/log.ts index d7ab8db..992e1d4 100644 --- a/src/middlewares/log.ts +++ b/src/middlewares/log.ts @@ -28,7 +28,7 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { return originalJson.call(this, v); }; - const timestamp = new Date().toString(); + const timestamp = new Date().toISOString(); const start = performance.now(); req.app.locals.logData = {}; @@ -46,7 +46,7 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { logType: res.statusCode >= 500 ? "error" : res.statusCode >= 400 ? "warning" : "info", systemName: "JWS-SOS", startTimeStamp: timestamp, - endTimeStamp: new Date().toString(), + endTimeStamp: new Date().toISOString(), processTime: performance.now() - start, host: req.hostname, sessionId: req.headers["x-session-id"], From 52305bc4e60bc96f7c7c05ce031db11071d1966f Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 11 Jul 2024 10:27:51 +0700 Subject: [PATCH 039/102] fix: typo --- src/middlewares/auth.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/middlewares/auth.ts b/src/middlewares/auth.ts index 1809e73..d0d820e 100644 --- a/src/middlewares/auth.ts +++ b/src/middlewares/auth.ts @@ -12,7 +12,8 @@ export async function expressAuthentication( case "keycloak": const authData = await keycloakAuth(request, scopes); request.app.locals.logData.sessionId = authData.session_state; - request.app.locals.logData.user = authData.preffered_username; + request.app.locals.logData.user = authData.preferred_username; + request.app.locals.logData.userId = authData.sub; return authData; default: throw new HttpError( From df1cb471d22ed961eef60043c62be6b9c1d53288 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:56:11 +0700 Subject: [PATCH 040/102] feat: add user real name to log --- src/middlewares/auth.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/middlewares/auth.ts b/src/middlewares/auth.ts index d0d820e..37038d7 100644 --- a/src/middlewares/auth.ts +++ b/src/middlewares/auth.ts @@ -13,6 +13,7 @@ export async function expressAuthentication( const authData = await keycloakAuth(request, scopes); request.app.locals.logData.sessionId = authData.session_state; request.app.locals.logData.user = authData.preferred_username; + request.app.locals.logData.userName = authData.name; request.app.locals.logData.userId = authData.sub; return authData; default: From 74ed6fb32126e7c2b566a9e00e325baca2cc4165 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 17 Jul 2024 09:00:45 +0700 Subject: [PATCH 041/102] refactor: change field to null able --- .../20240717020017_change_field_to_optional/migration.sql | 2 ++ prisma/schema.prisma | 2 +- src/controllers/employee-controller.ts | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 prisma/migrations/20240717020017_change_field_to_optional/migration.sql diff --git a/prisma/migrations/20240717020017_change_field_to_optional/migration.sql b/prisma/migrations/20240717020017_change_field_to_optional/migration.sql new file mode 100644 index 0000000..0e89990 --- /dev/null +++ b/prisma/migrations/20240717020017_change_field_to_optional/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Employee" ALTER COLUMN "nrcNo" DROP NOT NULL; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index bd0bc3b..6431837 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -469,7 +469,7 @@ model Employee { id String @id @default(uuid()) code String - nrcNo String + nrcNo String? firstName String firstNameEN String lastName String diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts index 918314f..eacba8d 100644 --- a/src/controllers/employee-controller.ts +++ b/src/controllers/employee-controller.ts @@ -43,7 +43,7 @@ type EmployeeCreate = { status?: Status; - nrcNo: string; + nrcNo?: string; dateOfBirth: Date; gender: string; From 053eb8b04a9820a7da22b18b20c234b06bf17dda Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 18 Jul 2024 16:21:57 +0700 Subject: [PATCH 042/102] feat: add query param to include full detail in service --- src/controllers/service/service-controller.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/controllers/service/service-controller.ts b/src/controllers/service/service-controller.ts index cb1b4c5..2946add 100644 --- a/src/controllers/service/service-controller.ts +++ b/src/controllers/service/service-controller.ts @@ -94,6 +94,7 @@ export class ServiceController extends Controller { @Query() status?: Status, @Query() productTypeId?: string, @Query() registeredBranchId?: string, + @Query() fullDetail?: boolean, ) { const filterStatus = (val?: Status) => { if (!val) return {}; @@ -118,7 +119,17 @@ export class ServiceController extends Controller { const [result, total] = await prisma.$transaction([ prisma.service.findMany({ include: { - work: true, + work: fullDetail + ? { + orderBy: { order: "asc" }, + include: { + productOnWork: { + include: { product: true }, + orderBy: { order: "asc" }, + }, + }, + } + : true, createdBy: true, updatedBy: true, }, From 994b9ced2ad774b16e3bc0db713cb15562415a34 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:22:53 +0700 Subject: [PATCH 043/102] refactor: update auth setting --- src/middlewares/auth-provider/keycloak.ts | 21 +++++++++++++++------ tsoa.json | 7 ++++--- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/src/middlewares/auth-provider/keycloak.ts b/src/middlewares/auth-provider/keycloak.ts index f105bfc..c111e57 100644 --- a/src/middlewares/auth-provider/keycloak.ts +++ b/src/middlewares/auth-provider/keycloak.ts @@ -19,10 +19,14 @@ const jwtDecode = createDecoder(); export async function keycloakAuth(request: Express.Request, roles?: string[]) { const token = request.headers["authorization"]?.includes("Bearer ") ? request.headers["authorization"].split(" ")[1] - : request.headers["authorization"]; + : ""; if (!token) { - throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่พบข้อมูลสำหรับยืนยันตัวตน"); + throw new HttpError( + HttpStatus.UNAUTHORIZED, + "authorization data not found.", + "authDataNotFound", + ); } let payload: Record = {}; @@ -49,7 +53,7 @@ export async function keycloakAuth(request: Express.Request, roles?: string[]) { if (!roles.some((a: string) => payload.roles.includes(a))) { throw new HttpError( HttpStatus.FORBIDDEN, - "คุณไม่มีสิทธิในการเข้าถึงข้อมูลดังกล่าว", + "You do not have permission to access this resource.", "noPermission", ); } @@ -61,7 +65,7 @@ export async function keycloakAuth(request: Express.Request, roles?: string[]) { async function verifyOffline(token: string) { const payload = await jwtVerify(token).catch((_) => null); if (!payload) { - throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่สามารถยืนยันตัวตนได้"); + throw new HttpError(HttpStatus.UNAUTHORIZED, "Unauthorized.", "authFailed"); } return payload; } @@ -74,9 +78,14 @@ async function verifyOnline(token: string) { }, ).catch((e) => console.error(e)); - if (!res) throw new Error("ไม่สามารถเข้าถึงระบบยืนยันตัวตน"); + if (!res) + throw new HttpError( + HttpStatus.INTERNAL_SERVER_ERROR, + "Error authentication service.", + "authFailedFatal", + ); if (!res.ok) { - throw new HttpError(HttpStatus.UNAUTHORIZED, "ไม่สามารถยืนยันตัวตนได้"); + throw new HttpError(HttpStatus.UNAUTHORIZED, "Unauthorized.", "authFailed"); } return await jwtDecode(token); diff --git a/tsoa.json b/tsoa.json index b6acbc9..ca7209f 100644 --- a/tsoa.json +++ b/tsoa.json @@ -7,10 +7,10 @@ "specVersion": 3, "securityDefinitions": { "keycloak": { - "type": "apiKey", + "type": "http", "name": "Authorization", "description": "Keycloak Bearer Token", - "in": "header" + "scheme": "bearer" } }, "spec": { @@ -33,7 +33,8 @@ { "name": "Product Type" }, { "name": "Product" }, { "name": "Work" }, - { "name": "Service" } + { "name": "Service" }, + { "name": "Quotation" } ] } }, From bd425554a16fa45a55ad6c95cd729ed588dfa1a3 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:23:42 +0700 Subject: [PATCH 044/102] chore: add migration --- .../migration.sql | 119 ++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 prisma/migrations/20240723032953_add_quotation_table/migration.sql diff --git a/prisma/migrations/20240723032953_add_quotation_table/migration.sql b/prisma/migrations/20240723032953_add_quotation_table/migration.sql new file mode 100644 index 0000000..c4263ea --- /dev/null +++ b/prisma/migrations/20240723032953_add_quotation_table/migration.sql @@ -0,0 +1,119 @@ +-- CreateEnum +CREATE TYPE "PayCondition" AS ENUM ('Full', 'Split', 'BillFull', 'BillSplit'); + +-- CreateTable +CREATE TABLE "Quotation" ( + "id" TEXT NOT NULL, + "customerId" TEXT NOT NULL, + "customerBranchId" TEXT NOT NULL, + "status" "Status" NOT NULL DEFAULT 'CREATED', + "statusOrder" INTEGER NOT NULL DEFAULT 0, + "code" TEXT NOT NULL, + "date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "payCondition" "PayCondition" NOT NULL, + "paySplitCount" INTEGER, + "payBillDate" TIMESTAMP(3), + "workerCount" INTEGER NOT NULL, + "urgent" BOOLEAN NOT NULL DEFAULT false, + "totalPrice" DOUBLE PRECISION NOT NULL, + "totalDiscount" DOUBLE PRECISION NOT NULL, + "vat" DOUBLE PRECISION NOT NULL, + "vatExcluded" DOUBLE PRECISION NOT NULL, + "finalPrice" DOUBLE PRECISION NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "createdByUserId" TEXT, + "updatedAt" TIMESTAMP(3) NOT NULL, + "updatedByUserId" TEXT, + + CONSTRAINT "Quotation_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "QuotationPaySplit" ( + "id" TEXT NOT NULL, + "no" INTEGER NOT NULL, + "date" TIMESTAMP(3) NOT NULL, + "quotationId" TEXT, + + CONSTRAINT "QuotationPaySplit_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "QuotationWorker" ( + "id" TEXT NOT NULL, + "no" INTEGER NOT NULL, + "code" TEXT NOT NULL, + "employeeId" TEXT NOT NULL, + "quotationId" TEXT NOT NULL, + + CONSTRAINT "QuotationWorker_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "QuotationService" ( + "id" TEXT NOT NULL, + "code" TEXT NOT NULL, + "name" TEXT NOT NULL, + "detail" TEXT NOT NULL, + "attributes" JSONB, + "quotationId" TEXT NOT NULL, + + CONSTRAINT "QuotationService_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "QuotationServiceWork" ( + "id" TEXT NOT NULL, + "order" INTEGER NOT NULL, + "name" TEXT NOT NULL, + "attributes" JSONB, + "serviceId" TEXT NOT NULL, + + CONSTRAINT "QuotationServiceWork_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "QuotationServiceWorkProduct" ( + "order" INTEGER NOT NULL, + "workId" TEXT NOT NULL, + "productId" TEXT NOT NULL, + "vat" DOUBLE PRECISION NOT NULL, + "amount" INTEGER NOT NULL, + "discount" DOUBLE PRECISION NOT NULL, + "pricePerUnit" DOUBLE PRECISION NOT NULL, + + CONSTRAINT "QuotationServiceWorkProduct_pkey" PRIMARY KEY ("workId","productId") +); + +-- AddForeignKey +ALTER TABLE "Quotation" ADD CONSTRAINT "Quotation_customerId_fkey" FOREIGN KEY ("customerId") REFERENCES "Customer"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Quotation" ADD CONSTRAINT "Quotation_customerBranchId_fkey" FOREIGN KEY ("customerBranchId") REFERENCES "CustomerBranch"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Quotation" ADD CONSTRAINT "Quotation_createdByUserId_fkey" FOREIGN KEY ("createdByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Quotation" ADD CONSTRAINT "Quotation_updatedByUserId_fkey" FOREIGN KEY ("updatedByUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuotationPaySplit" ADD CONSTRAINT "QuotationPaySplit_quotationId_fkey" FOREIGN KEY ("quotationId") REFERENCES "Quotation"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuotationWorker" ADD CONSTRAINT "QuotationWorker_employeeId_fkey" FOREIGN KEY ("employeeId") REFERENCES "Employee"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuotationWorker" ADD CONSTRAINT "QuotationWorker_quotationId_fkey" FOREIGN KEY ("quotationId") REFERENCES "Quotation"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuotationService" ADD CONSTRAINT "QuotationService_quotationId_fkey" FOREIGN KEY ("quotationId") REFERENCES "Quotation"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuotationServiceWork" ADD CONSTRAINT "QuotationServiceWork_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "QuotationService"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuotationServiceWorkProduct" ADD CONSTRAINT "QuotationServiceWorkProduct_workId_fkey" FOREIGN KEY ("workId") REFERENCES "QuotationServiceWork"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuotationServiceWorkProduct" ADD CONSTRAINT "QuotationServiceWorkProduct_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE CASCADE ON UPDATE CASCADE; From be8f8d462f8b62fda879558454c43ca511912c39 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:27:56 +0700 Subject: [PATCH 045/102] fix: code always update on customer type CORP --- src/controllers/employee-controller.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts index eacba8d..3863413 100644 --- a/src/controllers/employee-controller.ts +++ b/src/controllers/employee-controller.ts @@ -582,7 +582,11 @@ export class EmployeeController extends Controller { const record = await prisma.$transaction(async (tx) => { let code: string | undefined; - if (customerBranch && customerBranch.id !== employee.customerBranchId) { + if ( + customerBranchId !== undefined && + customerBranch && + customerBranch.id !== employee.customerBranchId + ) { const last = await tx.runningNo.upsert({ where: { key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`, From d0e207de7e9e5a1727812ff19a74fa598e895c33 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:23:41 +0700 Subject: [PATCH 046/102] feat: add quotation structure --- prisma/schema.prisma | 127 +++++++++++++++++++++++- src/controllers/quotation-controller.ts | 108 ++++++++++++++++++++ 2 files changed, 231 insertions(+), 4 deletions(-) create mode 100644 src/controllers/quotation-controller.ts diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6431837..28d6c2b 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -375,6 +375,8 @@ model User { productTypeUpdated ProductType[] @relation("ProductTypeUpdatedByUser") productCreated Product[] @relation("ProductCreatedByUser") productUpdated Product[] @relation("ProductUpdatedByUser") + quotationCreated Quotation[] @relation("QuotationCreatedByUser") + quotationUpdated Quotation[] @relation("QuotationUpdatedByUser") } enum CustomerType { @@ -405,7 +407,8 @@ model Customer { updatedBy User? @relation(name: "CustomerUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) updatedByUserId String? - branch CustomerBranch[] + branch CustomerBranch[] + quotation Quotation[] } model CustomerBranch { @@ -462,7 +465,8 @@ model CustomerBranch { updatedBy User? @relation(name: "CustomerBranchUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) updatedByUserId String? - employee Employee[] + employee Employee[] + quotation Quotation[] } model Employee { @@ -528,7 +532,8 @@ model Employee { employeeWork EmployeeWork[] employeeOtherInfo EmployeeOtherInfo[] - editHistory EmployeeHistory[] + editHistory EmployeeHistory[] + quotationWorker QuotationWorker[] } model EmployeeHistory { @@ -691,7 +696,8 @@ model Product { registeredBranchId String? registeredBranch Branch? @relation(fields: [registeredBranchId], references: [id]) - workProduct WorkProduct[] + workProduct WorkProduct[] + quotationServiceWorkProduct QuotationServiceWorkProduct[] createdAt DateTime @default(now()) createdBy User? @relation(name: "ProductCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) @@ -767,3 +773,116 @@ model WorkProduct { @@id([workId, productId]) } + +enum PayCondition { + Full + Split + BillFull + BillSplit +} + +model Quotation { + id String @id @default(uuid()) + + customerId String + customer Customer @relation(fields: [customerId], references: [id]) + + customerBranchId String + customerBranch CustomerBranch @relation(fields: [customerBranchId], references: [id]) + + code String + date DateTime @default(now()) + payCondition PayCondition + + paySplitCount Int? + paySplit QuotationPaySplit[] + + payBillDate DateTime? + + workerCount Int + worker QuotationWorker[] + + service QuotationService[] + + urgent Boolean @default(false) + + totalPrice Float + totalDiscount Float + vat Int + vatExcluded Int + + createdAt DateTime @default(now()) + createdBy User? @relation(name: "QuotationCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) + createdByUserId String? + updatedAt DateTime @updatedAt + updatedBy User? @relation(name: "QuotationUpdatedByUser", fields: [updatedByUserId], references: [id], onDelete: SetNull) + updatedByUserId String? +} + +model QuotationPaySplit { + id String @id @default(uuid()) + + no Int + date DateTime + + quotation Quotation? @relation(fields: [quotationId], references: [id]) + quotationId String? +} + +model QuotationWorker { + id String @id @default(uuid()) + + no Int + code String + employee Employee @relation(fields: [employeeId], references: [id]) + employeeId String + quotation Quotation @relation(fields: [quotationId], references: [id]) + quotationId String +} + +model QuotationService { + id String @id @default(uuid()) + + code String + name String + detail String + attributes Json? + + status Status @default(CREATED) + statusOrder Int @default(0) + + work QuotationServiceWork[] + + quotation Quotation @relation(fields: [quotationId], references: [id]) + quotationId String +} + +model QuotationServiceWork { + id String @id @default(uuid()) + + order Int + name String + attributes Json? + + status Status @default(CREATED) + statusOrder Int @default(0) + + service QuotationService @relation(fields: [serviceId], references: [id]) + serviceId String + + productOnWork QuotationServiceWorkProduct[] +} + +model QuotationServiceWorkProduct { + order Int + work QuotationServiceWork @relation(fields: [workId], references: [id]) + workId String + product Product @relation(fields: [productId], references: [id], onDelete: Cascade) + productId String + + amount Int + discount Float + pricePerUnit Float + + @@id([workId, productId]) +} diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts new file mode 100644 index 0000000..77dd9c7 --- /dev/null +++ b/src/controllers/quotation-controller.ts @@ -0,0 +1,108 @@ +import { PayCondition, Status } from "@prisma/client"; +import { + Body, + Controller, + Delete, + Get, + Path, + Post, + Put, + Query, + Request, + Route, + Security, + Tags, +} from "tsoa"; +import { RequestWithUser } from "../interfaces/user"; + +type QuotationCreate = { + status?: Status; + + payCondition: PayCondition; + + paySplitCount?: number; + paySplit?: Date[]; + + payBillDate?: number; + + workerCount: number; + workerId: string[]; // EmployeeId + + urgent?: boolean; + + service: { + id: string; + // Other fields will come from original data + work?: { + // Name field will come from original data + product: { + id: string; + amount: number; + discount: number; + pricePerUnit: number; + }[]; + }[]; + }; +}; + +type QuotationUpdate = { + status?: "ACTIVE" | "INACTIVE"; + + payCondition: PayCondition; + + paySplitCount?: number; + paySplit?: Date[]; + + payBillDate?: number; + + workerCount: number; + workerId: string[]; // EmployeeId + + urgent?: boolean; + + service: { + id: string; + // Other fields will come from original data + work?: { + // Name field will come from original data + product: { + id: string; + amount: number; + discount: number; + pricePerUnit: number; + }[]; + }[]; + }; +}; + +@Route("/api/v1/quotation") +@Tags("Quotation") +export class QuotationController extends Controller { + @Get("{quotationId}") + @Security("keycloak") + async getQuotationById( + @Path() quotationId: string, + @Query() page: number = 1, + @Query() pageSize: number = 30, + ) {} + + @Get() + @Security("keycloak") + async getQuotationList(@Query() page: number = 1, @Query() pageSize: number = 30) {} + + @Post() + @Security("keycloak") + async createQuotation(@Request() req: RequestWithUser, @Body() body: QuotationCreate) {} + + @Put("{quotationId}") + @Security("keycloak") + async editQuotation( + @Request() req: RequestWithUser, + @Path() quotationId: string, + @Body() body: QuotationUpdate, + ) {} + + @Delete("{quotationId}") + @Security("keycloak") + async deleteQuotationById(@Request() req: RequestWithUser, @Path() quotationId: string) {} +} From fe50a31e080d1adeac88c99aa227a2b845db59b9 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:41:37 +0700 Subject: [PATCH 047/102] feat: add get quotation by id endpoint --- src/controllers/quotation-controller.ts | 103 +++++++++++++++++++++--- 1 file changed, 92 insertions(+), 11 deletions(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index 77dd9c7..f3622df 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -14,6 +14,9 @@ import { Tags, } from "tsoa"; import { RequestWithUser } from "../interfaces/user"; +import prisma from "../db"; +import HttpError from "../interfaces/http-error"; +import HttpStatus from "../interfaces/http-status"; type QuotationCreate = { status?: Status; @@ -26,14 +29,42 @@ type QuotationCreate = { payBillDate?: number; workerCount: number; - workerId: string[]; // EmployeeId + // EmployeeId or Create new employee + worker: ( + | string + | { + dateOfBirth: Date; + gender: string; + nationality: string; + + firstName: string; + firstNameEN: string; + lastName: string; + lastNameEN: string; + + addressEN: string; + address: string; + zipCode: string; + + passportType: string; + passportNumber: string; + passportIssueDate: Date; + passportExpiryDate: Date; + passportIssuingCountry: string; + passportIssuingPlace: string; + previousPassportReference?: string; + } + )[]; + + customerBranchId: string; + customerId: string; urgent?: boolean; service: { id: string; // Other fields will come from original data - work?: { + work: { // Name field will come from original data product: { id: string; @@ -42,7 +73,7 @@ type QuotationCreate = { pricePerUnit: number; }[]; }[]; - }; + }[]; }; type QuotationUpdate = { @@ -56,14 +87,42 @@ type QuotationUpdate = { payBillDate?: number; workerCount: number; - workerId: string[]; // EmployeeId + // EmployeeId or Create new employee + worker?: ( + | string + | { + dateOfBirth: Date; + gender: string; + nationality: string; + + firstName: string; + firstNameEN: string; + lastName: string; + lastNameEN: string; + + addressEN: string; + address: string; + zipCode: string; + + passportType: string; + passportNumber: string; + passportIssueDate: Date; + passportExpiryDate: Date; + passportIssuingCountry: string; + passportIssuingPlace: string; + previousPassportReference?: string; + } + )[]; + + customerBranchId: string; + customerId: string; urgent?: boolean; service: { id: string; // Other fields will come from original data - work?: { + work: { // Name field will come from original data product: { id: string; @@ -72,7 +131,7 @@ type QuotationUpdate = { pricePerUnit: number; }[]; }[]; - }; + }[]; }; @Route("/api/v1/quotation") @@ -80,11 +139,33 @@ type QuotationUpdate = { export class QuotationController extends Controller { @Get("{quotationId}") @Security("keycloak") - async getQuotationById( - @Path() quotationId: string, - @Query() page: number = 1, - @Query() pageSize: number = 30, - ) {} + async getQuotationById(@Path() quotationId: string) { + const record = await prisma.quotation.findUnique({ + include: { + worker: true, + service: { + include: { + _count: { select: { work: true } }, + work: { + include: { + _count: { select: { productOnWork: true } }, + productOnWork: { + include: { product: true }, + }, + }, + }, + }, + }, + }, + where: { id: quotationId }, + }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Quotation not found.", "quotationNotFound"); + } + + return record; + } @Get() @Security("keycloak") From a2dbaf29fb1201603b71cc4c6429d25b3fe51568 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:46:28 +0700 Subject: [PATCH 048/102] feat: add quotation list endpoint (no query) --- src/controllers/quotation-controller.ts | 26 ++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index f3622df..dfa1554 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -169,7 +169,31 @@ export class QuotationController extends Controller { @Get() @Security("keycloak") - async getQuotationList(@Query() page: number = 1, @Query() pageSize: number = 30) {} + async getQuotationList(@Query() page: number = 1, @Query() pageSize: number = 30) { + const [result, total] = await prisma.$transaction([ + prisma.quotation.findMany({ + include: { + worker: true, + service: { + include: { + _count: { select: { work: true } }, + work: { + include: { + _count: { select: { productOnWork: true } }, + productOnWork: { + include: { product: true }, + }, + }, + }, + }, + }, + }, + }), + prisma.quotation.count(), + ]); + + return { result: result, page, pageSize, total }; + } @Post() @Security("keycloak") From 1cf1915209a94fc8fbac59d12f801f8a76e3f94d Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 19 Jul 2024 10:49:38 +0700 Subject: [PATCH 049/102] refactor: update structure --- prisma/schema.prisma | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 28d6c2b..1c4aec5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -790,6 +790,9 @@ model Quotation { customerBranchId String customerBranch CustomerBranch @relation(fields: [customerBranchId], references: [id]) + status Status @default(CREATED) + statusOrder Int @default(0) + code String date DateTime @default(now()) payCondition PayCondition @@ -848,9 +851,6 @@ model QuotationService { detail String attributes Json? - status Status @default(CREATED) - statusOrder Int @default(0) - work QuotationServiceWork[] quotation Quotation @relation(fields: [quotationId], references: [id]) @@ -864,9 +864,6 @@ model QuotationServiceWork { name String attributes Json? - status Status @default(CREATED) - statusOrder Int @default(0) - service QuotationService @relation(fields: [serviceId], references: [id]) serviceId String From 0319990232e6b62614bea1458a1e87b49ea80d56 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 19 Jul 2024 17:59:35 +0700 Subject: [PATCH 050/102] feat: structure input before insert to database --- src/controllers/quotation-controller.ts | 181 +++++++++++++++++++++++- 1 file changed, 175 insertions(+), 6 deletions(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index dfa1554..57f7853 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -26,7 +26,7 @@ type QuotationCreate = { paySplitCount?: number; paySplit?: Date[]; - payBillDate?: number; + payBillDate?: Date; workerCount: number; // EmployeeId or Create new employee @@ -65,12 +65,12 @@ type QuotationCreate = { id: string; // Other fields will come from original data work: { + id: string; // Name field will come from original data product: { id: string; amount: number; discount: number; - pricePerUnit: number; }[]; }[]; }[]; @@ -84,7 +84,7 @@ type QuotationUpdate = { paySplitCount?: number; paySplit?: Date[]; - payBillDate?: number; + payBillDate?: Date; workerCount: number; // EmployeeId or Create new employee @@ -123,12 +123,12 @@ type QuotationUpdate = { id: string; // Other fields will come from original data work: { + id: string; // Name field will come from original data product: { id: string; amount: number; discount: number; - pricePerUnit: number; }[]; }[]; }[]; @@ -197,7 +197,168 @@ export class QuotationController extends Controller { @Post() @Security("keycloak") - async createQuotation(@Request() req: RequestWithUser, @Body() body: QuotationCreate) {} + async createQuotation(@Request() req: RequestWithUser, @Body() body: QuotationCreate) { + const existingEmployee = body.worker.filter((v) => typeof v === "string"); + const serviceIdList = body.service.map((v) => v.id); + const productIdList = body.service.flatMap((a) => + a.work.flatMap((b) => b.product.map((c) => c.id)), + ); + + const [customer, customerBranch, employee, service, product] = await prisma.$transaction([ + prisma.customer.findUnique({ + where: { id: body.customerId }, + }), + prisma.customerBranch.findUnique({ + include: { customer: true }, + where: { id: body.customerBranchId }, + }), + prisma.employee.findMany({ + where: { id: { in: existingEmployee } }, + }), + prisma.service.findMany({ + include: { + work: true, + }, + where: { id: { in: serviceIdList } }, + }), + prisma.product.findMany({ + where: { id: { in: productIdList } }, + }), + ]); + + if (!customer) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Customer cannot be found.", + "relationCustomerNotFound", + ); + if (!customerBranch) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Customer Branch cannot be found.", + "relationCustomerBranchNotFound", + ); + + const { service: _service, worker: _worker, ...rest } = body; + + await prisma.$transaction(async (tx) => { + const nonExistEmployee = body.worker.filter((v) => typeof v !== "string"); + const lastEmployee = await tx.runningNo.upsert({ + where: { + key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`, + }, + create: { + key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`, + value: 1, + }, + update: { value: { increment: nonExistEmployee.length } }, + }); + const newEmployee = await Promise.all( + nonExistEmployee.map(async (v, i) => + tx.employee.create({ + data: { + ...v, + code: `${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}${(lastEmployee.value + i).toString().padStart(4, "0")}`, + customerBranchId: customerBranch.id, + }, + }), + ), + ); + const sortedEmployeeId: string[] = []; + + while (body.worker.length > 0) { + const popExist = body.worker.shift(); + if (typeof popExist === "string") sortedEmployeeId.push(popExist); + else { + const popNew = newEmployee.shift(); + popNew && sortedEmployeeId.push(popNew.id); + } + } + + const price = { totalPrice: 0, totalDiscount }; + const restructureService = body.service.flatMap((a) => { + const currentService = service.find((b) => b.id === a.id); + + if (!currentService) return []; // should not possible + + return { + id: currentService.id, + name: currentService.name, + code: currentService.code, + detail: currentService.detail, + work: a.work.flatMap((c) => { + const currentWork = currentService.work.find((d) => d.id === c.id); + + if (!currentWork) return []; // should not possible + + return { + id: currentWork.id, + order: currentWork.order, + name: currentWork.name, + product: c.product.flatMap((e) => { + const currentProduct = product.find((f) => f.id === e.id); + + if (!currentProduct) return []; // should not possible + + return { ...e, pricePerUnit: currentProduct.price }; + }), + }; + }), + }; + }); + + const quotation = await tx.quotation.create({ + data: { + ...rest, + statusOrder: +(rest.status === "INACTIVE"), + code: "", + worker: { + createMany: { + data: sortedEmployeeId.map((v, i) => ({ + no: i, + code: "", + employeeId: v, + })), + }, + }, + service: { + createMany: { + data: body.service.flatMap((a) => { + const src = service.find((b) => b.id == a.id); + return src + ? { + id: a.id, + name: src.name, + code: src.code, + detail: src.detail, + } + : []; // should not be possible to not found. + }), + }, + }, + totalPrice, + totalDiscount, + vatExcluded: 0, + vat: 0, + paySplit: { + createMany: { + data: (rest.paySplit || []).map((v, i) => ({ + no: i + 1, + date: v, + })), + }, + }, + }, + }); + // await tx.quotationServiceWork.createMany({ + // data: service.flatMap((a) => + // a.work.map((b) => ({ id: b.id, order: b.order, name: b.name, serviceId: a.id })), + // ), + // }); + + throw new Error("Test Quotation Structure"); + }); + } @Put("{quotationId}") @Security("keycloak") @@ -205,7 +366,15 @@ export class QuotationController extends Controller { @Request() req: RequestWithUser, @Path() quotationId: string, @Body() body: QuotationUpdate, - ) {} + ) { + const record = await prisma.quotation.findUnique({ + where: { id: quotationId }, + }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Quotation not found.", "quotationNotFound"); + } + } @Delete("{quotationId}") @Security("keycloak") From d2e7a89211f94dafab5dd9a4d828bd48c750d40a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 19 Jul 2024 17:59:44 +0700 Subject: [PATCH 051/102] refactor: structure --- prisma/schema.prisma | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 1c4aec5..0fa9294 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -811,8 +811,9 @@ model Quotation { totalPrice Float totalDiscount Float - vat Int - vatExcluded Int + vat Float + vatExcluded Float + finalPrice Float createdAt DateTime @default(now()) createdBy User? @relation(name: "QuotationCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) @@ -877,6 +878,7 @@ model QuotationServiceWorkProduct { product Product @relation(fields: [productId], references: [id], onDelete: Cascade) productId String + vat Int amount Int discount Float pricePerUnit Float From e509cd1fd5b2c2c1c256afc776ec971b9b600552 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 23 Jul 2024 10:28:47 +0700 Subject: [PATCH 052/102] feat: create quotation endpoint (test required) --- prisma/schema.prisma | 2 +- src/controllers/quotation-controller.ts | 176 ++++++++++++++++++++---- 2 files changed, 147 insertions(+), 31 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0fa9294..8faedfe 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -878,7 +878,7 @@ model QuotationServiceWorkProduct { product Product @relation(fields: [productId], references: [id], onDelete: Cascade) productId String - vat Int + vat Float amount Int discount Float pricePerUnit Float diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index 57f7853..d988a27 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -1,4 +1,4 @@ -import { PayCondition, Status } from "@prisma/client"; +import { PayCondition, Prisma, Status } from "@prisma/client"; import { Body, Controller, @@ -70,7 +70,16 @@ type QuotationCreate = { product: { id: string; amount: number; + /** + * @maximum 1 + * @minimum 0 + */ discount: number; + /** + * @maximum 1 + * @minimum 0 + */ + vat?: number; }[]; }[]; }[]; @@ -127,8 +136,20 @@ type QuotationUpdate = { // Name field will come from original data product: { id: string; + /** + * @isInt + */ amount: number; + /** + * @maximum 1 + * @minimum 0 + */ discount: number; + /** + * @maximum 1 + * @minimum 0 + */ + vat: number; }[]; }[]; }[]; @@ -216,9 +237,7 @@ export class QuotationController extends Controller { where: { id: { in: existingEmployee } }, }), prisma.service.findMany({ - include: { - work: true, - }, + include: { work: true }, where: { id: { in: serviceIdList } }, }), prisma.product.findMany({ @@ -226,6 +245,20 @@ export class QuotationController extends Controller { }), ]); + if (serviceIdList.length !== service.length) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Some service cannot be found.", + "relationServiceNotFound", + ); + } + if (productIdList.length !== product.length) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Some product cannot be found.", + "relationProductNotFound", + ); + } if (!customer) throw new HttpError( HttpStatus.BAD_REQUEST, @@ -238,6 +271,12 @@ export class QuotationController extends Controller { "Customer Branch cannot be found.", "relationCustomerBranchNotFound", ); + if (customerBranch.customerId !== customer.id) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Customer conflict with customer branch.", + "customerConflictCustomerBranch", + ); const { service: _service, worker: _worker, ...rest } = body; @@ -275,7 +314,8 @@ export class QuotationController extends Controller { } } - const price = { totalPrice: 0, totalDiscount }; + const price = { totalPrice: 0, totalDiscount: 0, totalVat: 0 }; + const restructureService = body.service.flatMap((a) => { const currentService = service.find((b) => b.id === a.id); @@ -286,6 +326,7 @@ export class QuotationController extends Controller { name: currentService.name, code: currentService.code, detail: currentService.detail, + attributes: currentService.attributes as Prisma.JsonObject, work: a.work.flatMap((c) => { const currentWork = currentService.work.find((d) => d.id === c.id); @@ -295,12 +336,27 @@ export class QuotationController extends Controller { id: currentWork.id, order: currentWork.order, name: currentWork.name, + attributes: currentWork.attributes as Prisma.JsonObject, product: c.product.flatMap((e) => { const currentProduct = product.find((f) => f.id === e.id); if (!currentProduct) return []; // should not possible - return { ...e, pricePerUnit: currentProduct.price }; + price.totalPrice += currentProduct.price; + price.totalDiscount += Math.round(currentProduct.price * e.discount * 100) / 100; + price.totalVat += + Math.round( + (currentProduct.price - currentProduct.price * e.discount) * + (e.vat === undefined ? 0.07 : e.vat) * + 100, + ) / 100; + + console.log(e.vat); + return { + ...e, + vat: e.vat === undefined ? 0.07 : e.vat, + pricePerUnit: currentProduct.price, + }; }), }; }), @@ -308,6 +364,9 @@ export class QuotationController extends Controller { }); const quotation = await tx.quotation.create({ + include: { + service: true, + }, data: { ...rest, statusOrder: +(rest.status === "INACTIVE"), @@ -321,25 +380,14 @@ export class QuotationController extends Controller { })), }, }, - service: { - createMany: { - data: body.service.flatMap((a) => { - const src = service.find((b) => b.id == a.id); - return src - ? { - id: a.id, - name: src.name, - code: src.code, - detail: src.detail, - } - : []; // should not be possible to not found. - }), - }, - }, - totalPrice, - totalDiscount, + totalPrice: price.totalPrice, + totalDiscount: price.totalDiscount, + + vat: price.totalVat, vatExcluded: 0, - vat: 0, + + finalPrice: price.totalPrice - price.totalDiscount, + paySplit: { createMany: { data: (rest.paySplit || []).map((v, i) => ({ @@ -348,15 +396,83 @@ export class QuotationController extends Controller { })), }, }, + + createdByUserId: req.user.sub, + updatedByUserId: req.user.sub, }, }); - // await tx.quotationServiceWork.createMany({ - // data: service.flatMap((a) => - // a.work.map((b) => ({ id: b.id, order: b.order, name: b.name, serviceId: a.id })), - // ), - // }); - throw new Error("Test Quotation Structure"); + await Promise.all( + restructureService.map(async (a) => { + await tx.quotationService.create({ + data: { + id: a.id, + code: a.code, + name: a.name, + detail: a.detail, + attributes: a.attributes, + quotationId: quotation.id, + }, + }); + + await Promise.all( + a.work.map(async (b) => { + await tx.quotationServiceWork.create({ + data: { + id: b.id, + order: b.order, + name: b.name, + attributes: b.attributes, + serviceId: a.id, + productOnWork: { + createMany: { + data: b.product.map((v, i) => ({ + productId: v.id, + order: i + 1, + vat: v.vat, + amount: v.amount, + discount: v.discount, + pricePerUnit: v.pricePerUnit, + })), + }, + }, + }, + }); + }), + ); + }), + ); + + const result = await tx.quotation.findUnique({ + include: { + service: { + include: { + work: { + include: { + productOnWork: { + include: { product: true }, + }, + }, + }, + }, + }, + paySplit: true, + worker: true, + customerBranch: { + include: { customer: true }, + }, + _count: { + select: { service: true }, + }, + }, + where: { id: quotation.id }, + }); + + console.log(JSON.stringify(result, null, 2)); + + // console.log("Re-Structure:", JSON.stringify(restructureService, null, 2)); + // console.log("Price:", JSON.stringify(price, null, 2)); + throw new Error(""); }); } From 7d7ac384c3e318beb4df60bdde6675b314d1c672 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:23:12 +0700 Subject: [PATCH 053/102] fix: error fk on empty payload registeredBranch field --- src/controllers/customer-controller.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index 06e2f20..ca10da2 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -296,13 +296,16 @@ export class CustomerController extends Controller { "relationSubDistrictNotFound", ); } - if (body.registeredBranchId && !branch) { + if (!!body.registeredBranchId && !branch) { throw new HttpError( HttpStatus.BAD_REQUEST, "Branch cannot be found.", "relationBranchNotFound", ); } + if (!body.registeredBranchId) { + body.registeredBranchId = undefined; + } const record = await prisma.$transaction( async (tx) => { From 0bcb69b09f8d40304ef8eed256107e689155e36d Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:24:07 +0700 Subject: [PATCH 054/102] feat: add quotation endpoint (complete) --- src/controllers/quotation-controller.ts | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index d988a27..68d5dbb 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -259,6 +259,13 @@ export class QuotationController extends Controller { "relationProductNotFound", ); } + if (existingEmployee.length !== employee.length) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Some worker(employee) cannot be found.", + "relationWorkerNotFound", + ); + } if (!customer) throw new HttpError( HttpStatus.BAD_REQUEST, @@ -280,7 +287,7 @@ export class QuotationController extends Controller { const { service: _service, worker: _worker, ...rest } = body; - await prisma.$transaction(async (tx) => { + return await prisma.$transaction(async (tx) => { const nonExistEmployee = body.worker.filter((v) => typeof v !== "string"); const lastEmployee = await tx.runningNo.upsert({ where: { @@ -351,7 +358,6 @@ export class QuotationController extends Controller { 100, ) / 100; - console.log(e.vat); return { ...e, vat: e.vat === undefined ? 0.07 : e.vat, @@ -404,9 +410,8 @@ export class QuotationController extends Controller { await Promise.all( restructureService.map(async (a) => { - await tx.quotationService.create({ + const { id: _currentServiceId } = await tx.quotationService.create({ data: { - id: a.id, code: a.code, name: a.name, detail: a.detail, @@ -419,11 +424,10 @@ export class QuotationController extends Controller { a.work.map(async (b) => { await tx.quotationServiceWork.create({ data: { - id: b.id, order: b.order, name: b.name, attributes: b.attributes, - serviceId: a.id, + serviceId: _currentServiceId, productOnWork: { createMany: { data: b.product.map((v, i) => ({ @@ -443,7 +447,7 @@ export class QuotationController extends Controller { }), ); - const result = await tx.quotation.findUnique({ + return await tx.quotation.findUnique({ include: { service: { include: { @@ -467,12 +471,6 @@ export class QuotationController extends Controller { }, where: { id: quotation.id }, }); - - console.log(JSON.stringify(result, null, 2)); - - // console.log("Re-Structure:", JSON.stringify(restructureService, null, 2)); - // console.log("Price:", JSON.stringify(price, null, 2)); - throw new Error(""); }); } From e397f467912aafed25cc0e405fd6de28898b52af Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:24:20 +0700 Subject: [PATCH 055/102] fix: error on disable log --- src/middlewares/auth.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/middlewares/auth.ts b/src/middlewares/auth.ts index 37038d7..694fd4f 100644 --- a/src/middlewares/auth.ts +++ b/src/middlewares/auth.ts @@ -11,6 +11,7 @@ export async function expressAuthentication( switch (securityName) { case "keycloak": const authData = await keycloakAuth(request, scopes); + if (!request.app.locals.logData) request.app.locals.logData = {}; request.app.locals.logData.sessionId = authData.session_state; request.app.locals.logData.user = authData.preferred_username; request.app.locals.logData.userName = authData.name; From e233869efe35c1f584892c91f1216d1f99aacf40 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:24:32 +0700 Subject: [PATCH 056/102] chore: remove log --- src/middlewares/log.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/middlewares/log.ts b/src/middlewares/log.ts index 992e1d4..1b13ea3 100644 --- a/src/middlewares/log.ts +++ b/src/middlewares/log.ts @@ -61,8 +61,6 @@ async function logMiddleware(req: Request, res: Response, next: NextFunction) { ...req.app.locals.logData, }; - console.log(obj); - elasticsearch.index({ index: ELASTICSEARCH_INDEX, document: obj, From bada98d12d055a7a1e0c6582818cedd34457ced1 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:53:43 +0700 Subject: [PATCH 057/102] feat: exclude some work from service --- src/controllers/quotation-controller.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index 68d5dbb..02e4347 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -67,6 +67,7 @@ type QuotationCreate = { work: { id: string; // Name field will come from original data + excluded?: boolean; product: { id: string; amount: number; @@ -335,6 +336,8 @@ export class QuotationController extends Controller { detail: currentService.detail, attributes: currentService.attributes as Prisma.JsonObject, work: a.work.flatMap((c) => { + if (c.excluded) return []; + const currentWork = currentService.work.find((d) => d.id === c.id); if (!currentWork) return []; // should not possible From fbf7f6c968665df1e72a05d6127cb14dd80bf547 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:43:06 +0700 Subject: [PATCH 058/102] feat: add original service reference --- .../20240723084219_add_service_ref/migration.sql | 11 +++++++++++ prisma/schema.prisma | 7 ++++++- src/controllers/quotation-controller.ts | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 prisma/migrations/20240723084219_add_service_ref/migration.sql diff --git a/prisma/migrations/20240723084219_add_service_ref/migration.sql b/prisma/migrations/20240723084219_add_service_ref/migration.sql new file mode 100644 index 0000000..7505e27 --- /dev/null +++ b/prisma/migrations/20240723084219_add_service_ref/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - Added the required column `refServiceId` to the `QuotationService` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "QuotationService" ADD COLUMN "refServiceId" TEXT NOT NULL; + +-- AddForeignKey +ALTER TABLE "QuotationService" ADD CONSTRAINT "QuotationService_refServiceId_fkey" FOREIGN KEY ("refServiceId") REFERENCES "Service"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 8faedfe..328b6b6 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -718,7 +718,8 @@ model Service { status Status @default(CREATED) statusOrder Int @default(0) - work Work[] + work Work[] + quotationService QuotationService[] productType ProductType? @relation(fields: [productTypeId], references: [id], onDelete: SetNull) productTypeId String? @@ -854,8 +855,12 @@ model QuotationService { work QuotationServiceWork[] + refServiceId String + refService Service @relation(fields: [refServiceId], references: [id]) + quotation Quotation @relation(fields: [quotationId], references: [id]) quotationId String + serviceId String } model QuotationServiceWork { diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index 02e4347..f586168 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -420,6 +420,7 @@ export class QuotationController extends Controller { detail: a.detail, attributes: a.attributes, quotationId: quotation.id, + refServiceId: a.id, }, }); From c13eeb9b1459e759c23039340f132f304ccf6905 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:14:39 +0700 Subject: [PATCH 059/102] feat: delete quotation endpoints --- src/controllers/quotation-controller.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index f586168..628d259 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -496,5 +496,25 @@ export class QuotationController extends Controller { @Delete("{quotationId}") @Security("keycloak") - async deleteQuotationById(@Request() req: RequestWithUser, @Path() quotationId: string) {} + async deleteQuotationById(@Request() req: RequestWithUser, @Path() quotationId: string) { + const record = await prisma.quotation.findUnique({ + where: { id: quotationId }, + }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Quotation not found.", "quotationNotFound"); + } + + if (record.status !== Status.CREATED) { + throw new HttpError(HttpStatus.FORBIDDEN, "Quotation is in used.", "quotationInUsed"); + } + + return await prisma.quotation.delete({ + include: { + createdBy: true, + updatedBy: true, + }, + where: { id: quotationId }, + }); + } } From 67c3ead7ec3003dc6088ec250730e1d76e584cc0 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 24 Jul 2024 09:26:17 +0700 Subject: [PATCH 060/102] fix: calculation --- src/controllers/quotation-controller.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index 628d259..90f8a11 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -340,7 +340,7 @@ export class QuotationController extends Controller { const currentWork = currentService.work.find((d) => d.id === c.id); - if (!currentWork) return []; // should not possible + if (!currentWork) return []; // additional will get stripped return { id: currentWork.id, @@ -352,11 +352,13 @@ export class QuotationController extends Controller { if (!currentProduct) return []; // should not possible - price.totalPrice += currentProduct.price; - price.totalDiscount += Math.round(currentProduct.price * e.discount * 100) / 100; + price.totalPrice += currentProduct.price * e.amount; + price.totalDiscount += + Math.round(currentProduct.price * e.amount * e.discount * 100) / 100; price.totalVat += Math.round( - (currentProduct.price - currentProduct.price * e.discount) * + (currentProduct.price * e.amount - + currentProduct.price * e.amount * e.discount) * (e.vat === undefined ? 0.07 : e.vat) * 100, ) / 100; @@ -373,9 +375,6 @@ export class QuotationController extends Controller { }); const quotation = await tx.quotation.create({ - include: { - service: true, - }, data: { ...rest, statusOrder: +(rest.status === "INACTIVE"), From 7d96d1afb6151bcd29af27c8067005ef33705661 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:33:46 +0700 Subject: [PATCH 061/102] feat: update quotation --- src/controllers/quotation-controller.ts | 292 +++++++++++++++++++++++- 1 file changed, 288 insertions(+), 4 deletions(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index 90f8a11..ec1893d 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -89,7 +89,7 @@ type QuotationCreate = { type QuotationUpdate = { status?: "ACTIVE" | "INACTIVE"; - payCondition: PayCondition; + payCondition?: PayCondition; paySplitCount?: number; paySplit?: Date[]; @@ -124,16 +124,17 @@ type QuotationUpdate = { } )[]; - customerBranchId: string; - customerId: string; + customerBranchId?: string; + customerId?: string; urgent?: boolean; - service: { + service?: { id: string; // Other fields will come from original data work: { id: string; + excluded?: boolean; // Name field will come from original data product: { id: string; @@ -485,12 +486,295 @@ export class QuotationController extends Controller { @Body() body: QuotationUpdate, ) { const record = await prisma.quotation.findUnique({ + include: { customer: true }, where: { id: quotationId }, }); if (!record) { throw new HttpError(HttpStatus.NOT_FOUND, "Quotation not found.", "quotationNotFound"); } + + const existingEmployee = body.worker?.filter((v) => typeof v === "string"); + const serviceIdList = body.service?.map((v) => v.id); + const productIdList = body.service?.flatMap((a) => + a.work.flatMap((b) => b.product.map((c) => c.id)), + ); + + const [customer, customerBranch, employee, service, product] = await prisma.$transaction( + async (tx) => + await Promise.all([ + tx.customer.findUnique({ + where: { id: body.customerId }, + }), + tx.customerBranch.findUnique({ + include: { customer: true }, + where: { id: body.customerBranchId }, + }), + body.worker + ? tx.employee.findMany({ + where: { id: { in: existingEmployee } }, + }) + : null, + body.service + ? tx.service.findMany({ + include: { work: true }, + where: { id: { in: serviceIdList } }, + }) + : null, + body.service + ? tx.product.findMany({ + where: { id: { in: productIdList } }, + }) + : null, + ]), + ); + + if (serviceIdList?.length !== service?.length) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Some service cannot be found.", + "relationServiceNotFound", + ); + } + if (productIdList?.length !== product?.length) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Some product cannot be found.", + "relationProductNotFound", + ); + } + if (existingEmployee?.length !== employee?.length) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Some worker(employee) cannot be found.", + "relationWorkerNotFound", + ); + } + if (!customer) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Customer cannot be found.", + "relationCustomerNotFound", + ); + if (!customerBranch) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Customer Branch cannot be found.", + "relationCustomerBranchNotFound", + ); + if (customerBranch.customerId !== customer.id) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Customer conflict with customer branch.", + "customerConflictCustomerBranch", + ); + + const { service: _service, worker: _worker, ...rest } = body; + + return await prisma.$transaction(async (tx) => { + const sortedEmployeeId: string[] = []; + + if (body.worker) { + const nonExistEmployee = body.worker.filter((v) => typeof v !== "string"); + const lastEmployee = await tx.runningNo.upsert({ + where: { + key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`, + }, + create: { + key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`, + value: 1, + }, + update: { value: { increment: nonExistEmployee.length } }, + }); + const newEmployee = await Promise.all( + nonExistEmployee.map(async (v, i) => + tx.employee.create({ + data: { + ...v, + code: `${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}${(lastEmployee.value + i).toString().padStart(4, "0")}`, + customerBranchId: customerBranch.id, + }, + }), + ), + ); + + while (body.worker.length > 0) { + const popExist = body.worker.shift(); + if (typeof popExist === "string") sortedEmployeeId.push(popExist); + else { + const popNew = newEmployee.shift(); + popNew && sortedEmployeeId.push(popNew.id); + } + } + } + + const price = { totalPrice: 0, totalDiscount: 0, totalVat: 0 }; + + const restructureService = body.service?.flatMap((a) => { + const currentService = service?.find((b) => b.id === a.id); + + if (!currentService) return []; // should not possible + + return { + id: currentService.id, + name: currentService.name, + code: currentService.code, + detail: currentService.detail, + attributes: currentService.attributes as Prisma.JsonObject, + work: a.work.flatMap((c) => { + if (c.excluded) return []; + + const currentWork = currentService.work.find((d) => d.id === c.id); + + if (!currentWork) return []; // additional will get stripped + + return { + id: currentWork.id, + order: currentWork.order, + name: currentWork.name, + attributes: currentWork.attributes as Prisma.JsonObject, + product: c.product.flatMap((e) => { + const currentProduct = product?.find((f) => f.id === e.id); + + if (!currentProduct) return []; // should not possible + + price.totalPrice += currentProduct.price; + price.totalDiscount += Math.round(currentProduct.price * e.discount * 100) / 100; + price.totalVat += + Math.round( + (currentProduct.price - currentProduct.price * e.discount) * + (e.vat === undefined ? 0.07 : e.vat) * + 100, + ) / 100; + + return { + ...e, + vat: e.vat === undefined ? 0.07 : e.vat, + pricePerUnit: currentProduct.price, + }; + }), + }; + }), + }; + }); + + const quotation = await tx.quotation.update({ + include: { + worker: { + include: { employee: true }, + }, + }, + where: { id: quotationId }, + data: { + ...rest, + statusOrder: +(rest.status === "INACTIVE"), + code: "", + worker: + sortedEmployeeId.length > 0 + ? { + deleteMany: { id: { notIn: sortedEmployeeId } }, + createMany: { + skipDuplicates: true, + data: sortedEmployeeId.map((v, i) => ({ + no: i, + code: "", + employeeId: v, + })), + }, + } + : undefined, + totalPrice: body.service ? price.totalPrice : undefined, + totalDiscount: body.service ? price.totalDiscount : undefined, + + vat: body.service ? price.totalVat : undefined, + vatExcluded: body.service ? 0 : undefined, + + finalPrice: body.service ? price.totalPrice - price.totalDiscount : undefined, + + paySplit: rest.paySplit + ? { + deleteMany: {}, + createMany: { + data: (rest.paySplit || []).map((v, i) => ({ + no: i + 1, + date: v, + })), + }, + } + : undefined, + + service: body.service ? { deleteMany: {} } : undefined, + + updatedByUserId: req.user.sub, + }, + }); + + if (restructureService) + await Promise.all( + restructureService.map(async (a) => { + const { id: _currentServiceId } = await tx.quotationService.create({ + data: { + code: a.code, + name: a.name, + detail: a.detail, + attributes: a.attributes, + quotationId: quotation.id, + refServiceId: a.id, + }, + }); + + await Promise.all( + a.work.map(async (b) => { + await tx.quotationServiceWork.create({ + data: { + order: b.order, + name: b.name, + attributes: b.attributes, + serviceId: _currentServiceId, + productOnWork: { + createMany: { + data: b.product.map((v, i) => ({ + productId: v.id, + order: i + 1, + vat: v.vat, + amount: v.amount, + discount: v.discount, + pricePerUnit: v.pricePerUnit, + })), + }, + }, + }, + }); + }), + ); + }), + ); + + return await tx.quotation.findUnique({ + include: { + service: { + include: { + work: { + include: { + productOnWork: { + include: { product: true }, + }, + }, + }, + }, + }, + paySplit: true, + worker: true, + customerBranch: { + include: { customer: true }, + }, + _count: { + select: { service: true }, + }, + }, + where: { id: quotation.id }, + }); + }); } @Delete("{quotationId}") From 99e2001b34d35344fee561add56aae4e97019472 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:36:47 +0700 Subject: [PATCH 062/102] fix: wrong type --- prisma/schema.prisma | 1 - 1 file changed, 1 deletion(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 328b6b6..66883c9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -860,7 +860,6 @@ model QuotationService { quotation Quotation @relation(fields: [quotationId], references: [id]) quotationId String - serviceId String } model QuotationServiceWork { From 8f5f4822f94f5d094a6dc5b28b9db51afc769756 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 24 Jul 2024 13:44:19 +0700 Subject: [PATCH 063/102] chore: remove unnecessary --- src/controllers/quotation-controller.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index ec1893d..bf3a88a 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -659,11 +659,6 @@ export class QuotationController extends Controller { }); const quotation = await tx.quotation.update({ - include: { - worker: { - include: { employee: true }, - }, - }, where: { id: quotationId }, data: { ...rest, From 126a44cb7f3d3a90a1fe922d4f075fe4d10acff6 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:19:45 +0700 Subject: [PATCH 064/102] refactor: update constraints --- .../migration.sql | 17 +++++++++++++++++ prisma/schema.prisma | 6 +++--- 2 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 prisma/migrations/20240724071058_update_constraints/migration.sql diff --git a/prisma/migrations/20240724071058_update_constraints/migration.sql b/prisma/migrations/20240724071058_update_constraints/migration.sql new file mode 100644 index 0000000..28794fa --- /dev/null +++ b/prisma/migrations/20240724071058_update_constraints/migration.sql @@ -0,0 +1,17 @@ +-- DropForeignKey +ALTER TABLE "QuotationService" DROP CONSTRAINT "QuotationService_quotationId_fkey"; + +-- DropForeignKey +ALTER TABLE "QuotationServiceWork" DROP CONSTRAINT "QuotationServiceWork_serviceId_fkey"; + +-- DropForeignKey +ALTER TABLE "QuotationServiceWorkProduct" DROP CONSTRAINT "QuotationServiceWorkProduct_workId_fkey"; + +-- AddForeignKey +ALTER TABLE "QuotationService" ADD CONSTRAINT "QuotationService_quotationId_fkey" FOREIGN KEY ("quotationId") REFERENCES "Quotation"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuotationServiceWork" ADD CONSTRAINT "QuotationServiceWork_serviceId_fkey" FOREIGN KEY ("serviceId") REFERENCES "QuotationService"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "QuotationServiceWorkProduct" ADD CONSTRAINT "QuotationServiceWorkProduct_workId_fkey" FOREIGN KEY ("workId") REFERENCES "QuotationServiceWork"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 66883c9..afab626 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -858,7 +858,7 @@ model QuotationService { refServiceId String refService Service @relation(fields: [refServiceId], references: [id]) - quotation Quotation @relation(fields: [quotationId], references: [id]) + quotation Quotation @relation(fields: [quotationId], references: [id], onDelete: Cascade) quotationId String } @@ -869,7 +869,7 @@ model QuotationServiceWork { name String attributes Json? - service QuotationService @relation(fields: [serviceId], references: [id]) + service QuotationService @relation(fields: [serviceId], references: [id], onDelete: Cascade) serviceId String productOnWork QuotationServiceWorkProduct[] @@ -877,7 +877,7 @@ model QuotationServiceWork { model QuotationServiceWorkProduct { order Int - work QuotationServiceWork @relation(fields: [workId], references: [id]) + work QuotationServiceWork @relation(fields: [workId], references: [id], onDelete: Cascade) workId String product Product @relation(fields: [productId], references: [id], onDelete: Cascade) productId String From 2c99b92aa5ca1308d2e483d5f1dbec4f5872c32f Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:26:06 +0700 Subject: [PATCH 065/102] fix: error and price calc --- src/controllers/quotation-controller.ts | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index bf3a88a..6e77fa9 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -96,7 +96,7 @@ type QuotationUpdate = { payBillDate?: Date; - workerCount: number; + workerCount?: number; // EmployeeId or Create new employee worker?: ( | string @@ -503,10 +503,10 @@ export class QuotationController extends Controller { const [customer, customerBranch, employee, service, product] = await prisma.$transaction( async (tx) => await Promise.all([ - tx.customer.findUnique({ + tx.customer.findFirst({ where: { id: body.customerId }, }), - tx.customerBranch.findUnique({ + tx.customerBranch.findFirst({ include: { customer: true }, where: { id: body.customerBranchId }, }), @@ -638,11 +638,13 @@ export class QuotationController extends Controller { if (!currentProduct) return []; // should not possible - price.totalPrice += currentProduct.price; - price.totalDiscount += Math.round(currentProduct.price * e.discount * 100) / 100; + price.totalPrice += currentProduct.price * e.amount; + price.totalDiscount += + Math.round(currentProduct.price * e.amount * e.discount * 100) / 100; price.totalVat += Math.round( - (currentProduct.price - currentProduct.price * e.discount) * + (currentProduct.price * e.amount - + currentProduct.price * e.amount * e.discount) * (e.vat === undefined ? 0.07 : e.vat) * 100, ) / 100; @@ -704,7 +706,7 @@ export class QuotationController extends Controller { }, }); - if (restructureService) + if (restructureService) { await Promise.all( restructureService.map(async (a) => { const { id: _currentServiceId } = await tx.quotationService.create({ @@ -744,6 +746,7 @@ export class QuotationController extends Controller { ); }), ); + } return await tx.quotation.findUnique({ include: { From c97a8e5f6630b516f7bc274121b920842f5d4bbe Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:26:23 +0700 Subject: [PATCH 066/102] feaet: protect by roles --- src/controllers/quotation-controller.ts | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index 6e77fa9..c572660 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -157,6 +157,22 @@ type QuotationUpdate = { }[]; }; +const MANAGE_ROLES = [ + "system", + "head_of_admin", + "admin", + "branch_admin", + "branch_manager", + "accountant", + "branch_accountant", +]; + +function globalAllow(roles?: string[]) { + return ["system", "head_of_admin", "admin", "branch_admin", "branch_manager", "accountant"].some( + (v) => roles?.includes(v), + ); +} + @Route("/api/v1/quotation") @Tags("Quotation") export class QuotationController extends Controller { @@ -219,7 +235,7 @@ export class QuotationController extends Controller { } @Post() - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) async createQuotation(@Request() req: RequestWithUser, @Body() body: QuotationCreate) { const existingEmployee = body.worker.filter((v) => typeof v === "string"); const serviceIdList = body.service.map((v) => v.id); @@ -479,7 +495,7 @@ export class QuotationController extends Controller { } @Put("{quotationId}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) async editQuotation( @Request() req: RequestWithUser, @Path() quotationId: string, @@ -776,7 +792,7 @@ export class QuotationController extends Controller { } @Delete("{quotationId}") - @Security("keycloak") + @Security("keycloak", MANAGE_ROLES) async deleteQuotationById(@Request() req: RequestWithUser, @Path() quotationId: string) { const record = await prisma.quotation.findUnique({ where: { id: quotationId }, From 557254eb1996fee9d858427d435d577e0ec8ed32 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:42:21 +0700 Subject: [PATCH 067/102] refactor: reorder endpoints (affect swagger) --- src/controllers/quotation-controller.ts | 56 ++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index c572660..6bbbdc3 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -176,6 +176,34 @@ function globalAllow(roles?: string[]) { @Route("/api/v1/quotation") @Tags("Quotation") export class QuotationController extends Controller { + @Get() + @Security("keycloak") + async getQuotationList(@Query() page: number = 1, @Query() pageSize: number = 30) { + const [result, total] = await prisma.$transaction([ + prisma.quotation.findMany({ + include: { + worker: true, + service: { + include: { + _count: { select: { work: true } }, + work: { + include: { + _count: { select: { productOnWork: true } }, + productOnWork: { + include: { product: true }, + }, + }, + }, + }, + }, + }, + }), + prisma.quotation.count(), + ]); + + return { result: result, page, pageSize, total }; + } + @Get("{quotationId}") @Security("keycloak") async getQuotationById(@Path() quotationId: string) { @@ -206,34 +234,6 @@ export class QuotationController extends Controller { return record; } - @Get() - @Security("keycloak") - async getQuotationList(@Query() page: number = 1, @Query() pageSize: number = 30) { - const [result, total] = await prisma.$transaction([ - prisma.quotation.findMany({ - include: { - worker: true, - service: { - include: { - _count: { select: { work: true } }, - work: { - include: { - _count: { select: { productOnWork: true } }, - productOnWork: { - include: { product: true }, - }, - }, - }, - }, - }, - }, - }), - prisma.quotation.count(), - ]); - - return { result: result, page, pageSize, total }; - } - @Post() @Security("keycloak", MANAGE_ROLES) async createQuotation(@Request() req: RequestWithUser, @Body() body: QuotationCreate) { From c29ec941ca637b625901b4e243e734e3f540536d Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 25 Jul 2024 11:44:01 +0700 Subject: [PATCH 068/102] chore: remove unused --- src/controllers/quotation-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index 6bbbdc3..3679852 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -793,7 +793,7 @@ export class QuotationController extends Controller { @Delete("{quotationId}") @Security("keycloak", MANAGE_ROLES) - async deleteQuotationById(@Request() req: RequestWithUser, @Path() quotationId: string) { + async deleteQuotationById(@Path() quotationId: string) { const record = await prisma.quotation.findUnique({ where: { id: quotationId }, }); From 89b5a22b75439efb013b8db6c91bd1257e4a4966 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 26 Jul 2024 17:35:09 +0700 Subject: [PATCH 069/102] refactor: swap logger --- src/app.ts | 12 +++++-- src/middlewares/log.ts | 73 --------------------------------------- src/middlewares/logger.ts | 40 +++++++++++++++++++++ src/middlewares/morgan.ts | 69 ++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+), 76 deletions(-) delete mode 100644 src/middlewares/log.ts create mode 100644 src/middlewares/logger.ts create mode 100644 src/middlewares/morgan.ts diff --git a/src/app.ts b/src/app.ts index b933540..f942a08 100644 --- a/src/app.ts +++ b/src/app.ts @@ -4,8 +4,8 @@ import express, { json, urlencoded } from "express"; import swaggerUi from "swagger-ui-express"; import swaggerDocument from "./swagger.json"; import error from "./middlewares/error"; +import morgan from "./middlewares/morgan"; import { RegisterRoutes } from "./routes"; -import logMiddleware from "./middlewares/log"; import { addUserRoles, createUser, getRoleByName, listUser } from "./services/keycloak"; import prisma from "./db"; @@ -62,11 +62,17 @@ const APP_PORT = +(process.env.APP_PORT || 3000); }); } + const originalSend = app.response.json; + + app.response.json = function (body: unknown) { + this.app.locals.response = body; + return originalSend.call(this, body); + }; + app.use(cors()); app.use(json()); app.use(urlencoded({ extended: true })); - - app.use(logMiddleware); + app.use(morgan); app.use("/", express.static("static")); app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocument)); diff --git a/src/middlewares/log.ts b/src/middlewares/log.ts deleted file mode 100644 index 1b13ea3..0000000 --- a/src/middlewares/log.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { NextFunction, Request, Response } from "express"; -import elasticsearch from "../services/elasticsearch"; -import { randomUUID } from "crypto"; - -if (!process.env.ELASTICSEARCH_INDEX) { - throw new Error("Require ELASTICSEARCH_INDEX to store log."); -} - -const ELASTICSEARCH_INDEX = process.env.ELASTICSEARCH_INDEX; - -const LOG_LEVEL_MAP: Record = { - debug: 4, - info: 3, - warning: 2, - error: 1, - none: 0, -}; - -async function logMiddleware(req: Request, res: Response, next: NextFunction) { - if (!req.url.startsWith("/api/")) return next(); - - let data: any; - - const originalJson = res.json; - - res.json = function (v: any) { - data = v; - return originalJson.call(this, v); - }; - - const timestamp = new Date().toISOString(); - const start = performance.now(); - - req.app.locals.logData = {}; - - res.on("finish", () => { - if (!req.url.startsWith("/api/")) return; - - const level = LOG_LEVEL_MAP[process.env.LOG_LEVEL ?? "info"] || 1; - - if (level === 1 && res.statusCode < 500) return; - if (level === 2 && res.statusCode < 400) return; - if (level === 3 && res.statusCode < 200) return; - - const obj = { - logType: res.statusCode >= 500 ? "error" : res.statusCode >= 400 ? "warning" : "info", - systemName: "JWS-SOS", - startTimeStamp: timestamp, - endTimeStamp: new Date().toISOString(), - processTime: performance.now() - start, - host: req.hostname, - sessionId: req.headers["x-session-id"], - rtId: req.headers["x-rtid"], - tId: randomUUID(), - method: req.method, - endpoint: req.url, - responseCode: res.statusCode, - responseDescription: data?.code, - input: (level === 4 && JSON.stringify(req.body, null, 2)) || undefined, - output: (level === 4 && JSON.stringify(data, null, 2)) || undefined, - ...req.app.locals.logData, - }; - - elasticsearch.index({ - index: ELASTICSEARCH_INDEX, - document: obj, - }); - }); - - return next(); -} - -export default logMiddleware; diff --git a/src/middlewares/logger.ts b/src/middlewares/logger.ts new file mode 100644 index 0000000..a72a9eb --- /dev/null +++ b/src/middlewares/logger.ts @@ -0,0 +1,40 @@ +import winston from "winston"; +import { ElasticsearchTransport } from "winston-elasticsearch"; +import elasticsearch from "../services/elasticsearch"; + +const logger = winston.createLogger({ + levels: winston.config.syslog.levels, + defaultMeta: { serviceName: "jws-sos" }, + transports: [ + new ElasticsearchTransport({ + level: "info", + index: "app-log-test-winston-index", + format: winston.format.combine(winston.format.timestamp(), winston.format.json()), + client: elasticsearch, + transformer: (payload) => { + const { logData: additional, ...rest } = payload.meta; + return { + level: payload.level, + ...rest, + ...additional, + requestBody: + process.env.LOG_LEVEL === "debug" ? JSON.stringify(rest.requestBody) : undefined, + responseBody: + process.env.LOG_LEVEL === "debug" ? JSON.stringify(rest.responseBody) : undefined, + }; + }, + }), + new winston.transports.Console({ + format: winston.format.combine( + winston.format.colorize(), + winston.format.timestamp(), + winston.format.printf( + ({ level, timestamp, logData, responseBody, requestBody, ...payload }) => + `${level} ${timestamp} ${JSON.stringify(Object.assign(payload, logData), null, 4)}`, + ), + ), + }), + ], +}); + +export default logger; diff --git a/src/middlewares/morgan.ts b/src/middlewares/morgan.ts new file mode 100644 index 0000000..cca1096 --- /dev/null +++ b/src/middlewares/morgan.ts @@ -0,0 +1,69 @@ +import express from "express"; +import morgan from "morgan"; +import logger from "./logger"; +import { randomUUID } from "crypto"; + +const LOG_LEVEL_MAP: Record = { + debug: 4, + info: 3, + warning: 2, + error: 1, + none: 0, +}; + +// log the HTTP method, request URL, response status, and response time. +const logFormat = `{ + "requestMethod": ":method", + "requestUrl": ":url", + "responseStatus": ":status", + "responseTime": ":response-time ms", + "transactionId": ":transaction-id", + "refTransactionId": ":ref-transaction-id", + "sessionId": ":session-id", + "requestBody": :request-body, + "responseBody": :response-body, + "logData": :log-data +}`; + +function logMessageHandler(message: string) { + const data = JSON.parse(message.trim()); + + const level = LOG_LEVEL_MAP[process.env.LOG_LEVEL ?? "info"] || 1; + const status = +data.responseStatus; + + if (level === 1 && status < 500) return; + if (level === 2 && status < 400) return; + if (level === 3 && status < 200) return; + + if (status >= 500) return logger.error("HTTP request received", JSON.parse(message.trim())); + if (status >= 400) return logger.warning("HTTP request received", JSON.parse(message.trim())); + return logger.info("HTTP request received", JSON.parse(message.trim())); +} + +morgan.token("log-data", (req: express.Request) => { + return JSON.stringify(req.app.locals.logData || {}); +}); +morgan.token("request-body", (req: express.Request) => { + return JSON.stringify(req.body); +}); +morgan.token("response-body", (req: express.Request) => { + return JSON.stringify(req.app.locals.response || {}); +}); +morgan.token("identity-field", (req: express.Request) => { + return req.app.locals.identityField; +}); +morgan.token("session-id", (req: express.Request) => { + return req.headers["x-session-id"] as string | undefined; +}); +morgan.token("ref-transaction-id", (req: express.Request) => { + return req.headers["x-rtid"] as string | undefined; +}); +morgan.token("transaction-id", () => { + return randomUUID(); +}); + +const loggingMiddleware = morgan(logFormat, { + stream: { write: logMessageHandler }, +}); + +export default loggingMiddleware; From 88559480b7f6a285ef6657836d9f976405bf4763 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 31 Jul 2024 09:45:11 +0700 Subject: [PATCH 070/102] feat: generate quotation code --- src/controllers/quotation-controller.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/controllers/quotation-controller.ts b/src/controllers/quotation-controller.ts index 3679852..d3e551d 100644 --- a/src/controllers/quotation-controller.ts +++ b/src/controllers/quotation-controller.ts @@ -391,11 +391,26 @@ export class QuotationController extends Controller { }; }); + const currentYear = new Date().getFullYear(); + const currentMonth = new Date().getMonth() + 1; + const currentDate = new Date().getDate(); + + const lastQuotation = await tx.runningNo.upsert({ + where: { + key: `QUOTATION_${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${currentDate.toString().padStart(2, "0")}`, + }, + create: { + key: `QUOTATION_${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${currentDate.toString().padStart(2, "0")}`, + value: 1, + }, + update: { value: { increment: 1 } }, + }); + const quotation = await tx.quotation.create({ data: { ...rest, statusOrder: +(rest.status === "INACTIVE"), - code: "", + code: `${currentYear.toString().padStart(2, "0")}${currentMonth.toString().padStart(2, "0")}${currentDate.toString().padStart(2, "0")}${lastQuotation.value.toString().padStart(4, "0")}`, worker: { createMany: { data: sortedEmployeeId.map((v, i) => ({ From b2c31774f217a62e440295aaba67f499b483c60c Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 31 Jul 2024 11:43:07 +0700 Subject: [PATCH 071/102] feat: image endpoint get or upload --- src/controllers/user-controller.ts | 43 ++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/controllers/user-controller.ts b/src/controllers/user-controller.ts index 5a1fa45..a1bf870 100644 --- a/src/controllers/user-controller.ts +++ b/src/controllers/user-controller.ts @@ -658,6 +658,49 @@ export class UserController extends Controller { where: { id: userId }, }); } + + @Get("{userId}/image") + async getUserImageByUserId(@Request() req: RequestWithUser, @Path() userId: string) { + const url = await presignedGetObjectIfExist(MINIO_BUCKET, imageLocation(userId), 60 * 60); + + if (!url) { + throw new HttpError(HttpStatus.NOT_FOUND, "Image cannot be found", "imageNotFound"); + } + + return req.res?.redirect(url); + } + + @Put("{userId}/image") + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_admin", "branch_manager"]) + async setUserImageByUserId(@Request() req: RequestWithUser, @Path() userId: string) { + const record = await prisma.user.findFirst({ + include: { + branch: { where: { userId: req.user.sub } }, + }, + where: { + id: userId, + }, + }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "User cannot be found.", "userNotFound"); + } + + if ( + !["system", "head_of_admin", "admin"].some((v) => req.user.roles?.includes(v)) && + !record.branch.some((v) => v.userId === req.user.sub) + ) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } + + return req.res?.redirect( + await minio.presignedPutObject(MINIO_BUCKET, imageLocation(userId), 12 * 60 * 60), + ); + } } function attachmentLocation(uid: string) { From 0e411ad5d0a64ad5804ea32f939c1dd1f839ff10 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:25:05 +0700 Subject: [PATCH 072/102] feat(customer): image endpoint get or upload --- src/controllers/customer-controller.ts | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index ca10da2..98b229c 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -573,4 +573,33 @@ export class CustomerController extends Controller { return v; }); } + + @Get("{customerId}/image") + async getCustomerImageById(@Request() req: RequestWithUser, @Path() customerId: string) { + const url = await presignedGetObjectIfExist(MINIO_BUCKET, imageLocation(customerId), 60 * 60); + + if (!url) { + throw new HttpError(HttpStatus.NOT_FOUND, "Image cannot be found", "imageNotFound"); + } + + return req.res?.redirect(url); + } + + @Put("{customerId}/image") + @Security("keycloak", MANAGE_ROLES) + async setCustomerImageById(@Request() req: RequestWithUser, @Path() customerId: string) { + const record = await prisma.customer.findFirst({ + where: { + id: customerId, + }, + }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Customer cannot be found.", "customerNotFound"); + } + + return req.res?.redirect( + await minio.presignedPutObject(MINIO_BUCKET, imageLocation(customerId), 12 * 60 * 60), + ); + } } From 1b2d06e7073795df3e7e811b5df51de44a23d085 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 31 Jul 2024 15:29:48 +0700 Subject: [PATCH 073/102] fix: error dep --- package.json | 6 +- pnpm-lock.yaml | 839 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 844 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 48b8114..d3feb47 100644 --- a/package.json +++ b/package.json @@ -33,15 +33,19 @@ "@elastic/elasticsearch": "^8.14.0", "@prisma/client": "^5.16.1", "@tsoa/runtime": "^6.3.0", + "@types/morgan": "^1.9.9", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.19.2", "fast-jwt": "^4.0.1", "kysely": "^0.27.3", "minio": "^8.0.1", + "morgan": "^1.10.0", "prisma-extension-kysely": "^2.1.0", "promise.any": "^2.0.6", "swagger-ui-express": "^5.0.1", - "tsoa": "^6.3.1" + "tsoa": "^6.3.1", + "winston": "^3.13.1", + "winston-elasticsearch": "^0.19.0" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb3008f..734304d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,9 @@ importers: '@tsoa/runtime': specifier: ^6.3.0 version: 6.3.0 + '@types/morgan': + specifier: ^1.9.9 + version: 1.9.9 cors: specifier: ^2.8.5 version: 2.8.5 @@ -35,6 +38,9 @@ importers: minio: specifier: ^8.0.1 version: 8.0.1 + morgan: + specifier: ^1.10.0 + version: 1.10.0 prisma-extension-kysely: specifier: ^2.1.0 version: 2.1.0(@prisma/client@5.16.1(prisma@5.16.1)) @@ -47,6 +53,12 @@ importers: tsoa: specifier: ^6.3.1 version: 6.3.1 + winston: + specifier: ^3.13.1 + version: 3.13.1 + winston-elasticsearch: + specifier: ^0.19.0 + version: 0.19.0 devDependencies: '@types/cors': specifier: ^2.8.17 @@ -109,10 +121,25 @@ packages: '@chevrotain/utils@10.5.0': resolution: {integrity: sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==} + '@colors/colors@1.6.0': + resolution: {integrity: sha512-Ir+AOibqzrIsL6ajt3Rz3LskB7OiMVHqltZmspbW/TJuTVuyOMirVqAkjfY6JISiLHgyNqicAC8AyHHGzNd/dA==} + engines: {node: '>=0.1.90'} + '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@dabh/diagnostics@2.0.3': + resolution: {integrity: sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==} + + '@elastic/ecs-helpers@2.1.1': + resolution: {integrity: sha512-ItoNazMnYdlUCmkBYTXc3SG6PF7UlVTbvMdHPvXkfTMPdwGv2G1Xtp5CjDHaGHGOZSwaDrW4RSCXvA/lMSU+rg==} + engines: {node: '>=10'} + + '@elastic/ecs-pino-format@1.5.0': + resolution: {integrity: sha512-7MMVmT50ucEl7no8mUgCIl+pffBVNRl36uZi0vmalWa2xPWISBxM9k9WSP/WTgOkmGj9G35e5g3UfCS1zxshBg==} + engines: {node: '>=10'} + '@elastic/elasticsearch@8.14.0': resolution: {integrity: sha512-MGrgCI4y+Ozssf5Q2IkVJlqt5bUMnKIICG2qxeOfrJNrVugMCBCAQypyesmSSocAtNm8IX3LxfJ3jQlFHmKe2w==} engines: {node: '>=18'} @@ -250,6 +277,28 @@ packages: resolution: {integrity: sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA==} engines: {node: '>=8.0.0'} + '@opentelemetry/core@1.25.1': + resolution: {integrity: sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/resources@1.25.1': + resolution: {integrity: sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/sdk-metrics@1.25.1': + resolution: {integrity: sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.3.0 <1.10.0' + + '@opentelemetry/semantic-conventions@1.25.1': + resolution: {integrity: sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==} + engines: {node: '>=14'} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -372,6 +421,9 @@ packages: resolution: {integrity: sha512-5eEkJZ/BLvTE3vXGKkWlyTSUVZuzj23Wj8PoyOq2lt5I3CYbiLBOPb3XmCW6QcuOibIUE6emHXHt9E/F/rCa6w==} deprecated: This is a stub types definition. mime provides its own type definitions, so you do not need this installed. + '@types/morgan@1.9.9': + resolution: {integrity: sha512-iRYSDKVaC6FkGSpEVVIvrRGw0DfJMiQzIn3qr2G5B3C//AWkulhXgaBd7tS9/J79GWSYMTHGs7PfI5b3Y8m+RQ==} + '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} @@ -402,6 +454,9 @@ packages: '@types/swagger-ui-express@4.1.6': resolution: {integrity: sha512-UVSiGYXa5IzdJJG3hrc86e8KdZWLYxyEsVoUI4iPXc7CO4VZ3AfNP8d/8+hrDRIqz+HAaSMtZSqAsF3Nq2X/Dg==} + '@types/triple-beam@1.3.5': + resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==} + '@zxing/text-encoding@0.9.0': resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} @@ -412,6 +467,11 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} + acorn-import-assertions@1.9.0: + resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + peerDependencies: + acorn: ^8 + acorn-walk@8.3.2: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} @@ -421,10 +481,17 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + after-all-results@2.0.0: + resolution: {integrity: sha512-2zHEyuhSJOuCrmas9YV0YL/MFCWLxe1dS6k/ENhgYrb/JqyMnadLN4iIAc9kkZrbElMDyyAGH/0J18OPErOWLg==} + agent-base@7.1.1: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} + agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -494,6 +561,9 @@ packages: resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} engines: {node: '>= 0.4'} + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + asn1.js@5.4.1: resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} @@ -501,9 +571,23 @@ packages: resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} engines: {node: '>=8'} + async-cache@1.1.0: + resolution: {integrity: sha512-YDQc4vBn5NFhY6g6HhVshyi3Fy9+SQ5ePnE7JLDJn1DoL+i7ER+vMwtTNOYk9leZkYMnOwpBCWqyLDPw8Aig8g==} + deprecated: No longer maintained. Use [lru-cache](http://npm.im/lru-cache) version 7.6 or higher, and provide an asynchronous `fetchMethod` option. + + async-value-promise@1.1.1: + resolution: {integrity: sha512-c2RFDKjJle1rHa0YxN9Ysu97/QBu3Wa+NOejJxsX+1qVDJrkD3JL/GN1B3gaILAEXJXbu/4Z1lcoCHFESe/APA==} + + async-value@1.2.2: + resolution: {integrity: sha512-8rwtYe32OAS1W9CTwvknoyts+mc3ta8N7Pi0h7AjkMaKvsFbr39K+gEfZ7Z81aPXQ1sK5M23lgLy1QfZpcpadQ==} + async@3.2.5: resolution: {integrity: sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==} + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + available-typed-arrays@1.0.7: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} @@ -514,10 +598,17 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + basic-auth@2.0.1: + resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} + engines: {node: '>= 0.8'} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + binary-search@1.3.6: + resolution: {integrity: sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA==} + bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} @@ -541,6 +632,9 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + breadth-filter@2.0.0: + resolution: {integrity: sha512-thQShDXnFWSk2oVBixRCyrWsFoV5tfOpWKHmxwafHQDNxCfDBk539utpvytNjmlFrTMqz41poLwJvA1MW3z0MQ==} + browser-or-node@2.1.1: resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==} @@ -580,6 +674,9 @@ packages: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} engines: {node: '>=8'} + cjs-module-lexer@1.3.1: + resolution: {integrity: sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -605,6 +702,15 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@3.2.1: + resolution: {integrity: sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==} + + colorspace@1.1.4: + resolution: {integrity: sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==} + commondir@1.0.1: resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} @@ -615,6 +721,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + console-log-level@1.4.1: + resolution: {integrity: sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==} + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -626,6 +735,10 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} @@ -669,6 +782,9 @@ packages: resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} engines: {node: '>= 0.4'} + dayjs@1.11.12: + resolution: {integrity: sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg==} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -686,6 +802,15 @@ packages: supports-color: optional: true + debug@4.3.5: + resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decode-uri-component@0.2.2: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} @@ -735,12 +860,19 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + elastic-apm-node@3.51.0: + resolution: {integrity: sha512-GvZyoV4uhHB9qW4QE4pGcYZLbDCay2VzbeE5zN5v9vrQQ7j72GbzE5wGmtryNHwqP4DGCuXUk/jerArfpIquOQ==} + engines: {node: '>=8.6.0'} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + enabled@2.0.0: + resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==} + encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -752,9 +884,16 @@ packages: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} + error-callsites@2.0.4: + resolution: {integrity: sha512-V877Ch4FC4FN178fDK1fsrHN4I1YQIBdtjKrHh3BUHMnh3SMvwUVrqkaOgDpUuevgSNna0RBq6Ox9SGlxYrigA==} + engines: {node: '>=6.x'} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + es-abstract@1.23.3: resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} engines: {node: '>= 0.4'} @@ -827,6 +966,16 @@ packages: resolution: {integrity: sha512-+mdSoH0QdOdFSbbGBctJu7L1yfXRtbmjbVJ4W/PEjyvivobDena0RKwihtBkOML1P+kUJ1QuewnH8u+mROsR1w==} engines: {node: '>=16'} + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-stream-to-buffer@1.0.0: + resolution: {integrity: sha512-bI/544WUQlD2iXBibQbOMSmG07Hay7YrpXlKaeGTPT7H7pC0eitt3usak5vUwEvCGK/O7rUAM3iyQValGU22TQ==} + fast-xml-parser@4.3.6: resolution: {integrity: sha512-M2SovcRxD4+vC493Uc2GZVcZaj66CCJhWurC4viynVSTvrpErCShNcDz1lAho6n9REQKvL/ll4A4/fw6Y9z8nw==} hasBin: true @@ -834,6 +983,9 @@ packages: fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fecha@4.2.3: + resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==} + fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} @@ -858,6 +1010,12 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} + flatstr@1.0.12: + resolution: {integrity: sha512-4zPxDyhCyiN2wIAtSLI6gc82/EjqZc1onI4Mz/l0pWrAlsSfYH/2ZIcU+e3oA2wDwbzIWNKwa23F8rh6+DRWkw==} + + fn.name@1.1.0: + resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==} + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -865,6 +1023,9 @@ packages: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} + forwarded-parse@2.1.2: + resolution: {integrity: sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw==} + forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -1010,6 +1171,9 @@ packages: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} + http-headers@3.0.2: + resolution: {integrity: sha512-87E1I+2Wg4dxxz4rcxElo3dxO/w1ZtgL1yA0Sb6vH3qU16vRKq1NjWQv9SCY3ly2OQROcoxHZOUpmelS+k6wOw==} + http-proxy-agent@7.0.0: resolution: {integrity: sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==} engines: {node: '>= 14'} @@ -1022,6 +1186,9 @@ packages: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} + humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -1040,6 +1207,9 @@ packages: resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} engines: {node: '>= 4'} + import-in-the-middle@1.4.2: + resolution: {integrity: sha512-9WOz1Yh/cvO/p69sxRmhyQwrIGGSp7EIdcb+fFNVi7CzQGQB8U1/1XrKVSbEd/GNOAeM0peJtmi7+qphe7NvAw==} + indent-string@4.0.0: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} @@ -1078,6 +1248,9 @@ packages: is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + is-bigint@1.0.4: resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} @@ -1114,6 +1287,10 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-finite@1.1.0: + resolution: {integrity: sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==} + engines: {node: '>=0.10.0'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} @@ -1126,14 +1303,23 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} + is-integer@1.0.7: + resolution: {integrity: sha512-RPQc/s9yBHSvpi+hs9dYiJ2cuFeU6x3TyyIp8O2H6SKEltIvJOzRj9ToyvcStDvPR/pS4rxgr1oBFajQjZ2Szg==} + is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} + is-native@1.0.1: + resolution: {integrity: sha512-I4z9hx+4u3/zyvpvGtAR+n7SodJugE+i2jiS8yfq1A9QAZY0KldLQz0SBptLC9ti7kBlpghWUwTKE2BA62eCcw==} + is-negative-zero@2.0.3: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} + is-nil@1.0.1: + resolution: {integrity: sha512-m2Rm8PhUFDNNhgvwZJjJG74a9h5CHU0fkA8WT+WGlCjyEbZ2jPwgb+ZxHu4np284EqNVyOsgppReK4qy/TwEwg==} + is-number-object@1.0.7: resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} engines: {node: '>= 0.4'} @@ -1229,6 +1415,9 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + kuler@2.0.0: + resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==} + kysely@0.27.3: resolution: {integrity: sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA==} engines: {node: '>=14.0.0'} @@ -1264,16 +1453,32 @@ packages: lodash.isplainobject@4.0.6: resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.omit@4.5.0: + resolution: {integrity: sha512-XeqSp49hNGmlkj2EJlfrQFIzQ6lXdNro9sddtQzcJY8QaoC2GO0DT7xaIokHeyM+mIT0mPMlPvkYzg2xCuHdZg==} + + lodash.sortby@4.7.0: + resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} + lodash.union@4.6.0: resolution: {integrity: sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==} lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + logform@2.6.1: + resolution: {integrity: sha512-CdaO738xRapbKIMVn2m4F6KTj4j7ooJ8POVnebSgKo3KBz5axNXRAL7ZdRjIV6NOr2Uf4vjtRkxrFETOioCqSA==} + engines: {node: '>= 12.0.0'} + lru-cache@10.2.0: resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} engines: {node: 14 || >=16.14} + lru-cache@4.1.5: + resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} + lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -1289,6 +1494,17 @@ packages: make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + mapcap@1.0.0: + resolution: {integrity: sha512-KcNlZSlFPx+r1jYZmxEbTVymG+dIctf10WmWkuhrhrblM+KMoF77HelwihL5cxYlORye79KoR4IlOOk99lUJ0g==} + + measured-core@1.51.1: + resolution: {integrity: sha512-DZQP9SEwdqqYRvT2slMK81D/7xwdxXosZZBtLVfPSo6y5P672FBTbzHVdN4IQyUkUpcVOR9pIvtUy5Ryl7NKyg==} + engines: {node: '>= 5.12'} + + measured-reporting@1.51.1: + resolution: {integrity: sha512-JCt+2u6XT1I5lG3SuYqywE0e62DJuAzBcfMzWGUhIYtPQV2Vm4HiYt/durqmzsAbZV181CEs+o/jMKWJKkYIWw==} + engines: {node: '>= 5.12'} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -1369,6 +1585,16 @@ packages: mnemonist@0.39.8: resolution: {integrity: sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==} + module-details-from-path@1.0.3: + resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} + + monitor-event-loop-delay@1.0.0: + resolution: {integrity: sha512-YRIr1exCIfBDLZle8WHOfSo7Xg3M+phcZfq9Fx1L6Abo+atGp7cge5pM7PjyBn4s1oZI/BRD4EMrzQBbPpVb5Q==} + + morgan@1.10.0: + resolution: {integrity: sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==} + engines: {node: '>= 0.8.0'} + ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -1389,6 +1615,9 @@ packages: resolution: {integrity: sha512-md4cGoxuT4T4d/HDOXbrUHkTKrp/vp+m3aOA7XXVYwNsUNMK49g3SQicTSeV5GIz/5QVGAeYRAOlyp9OvlgsYA==} engines: {node: '>=10'} + next-line@1.1.0: + resolution: {integrity: sha512-+I10J3wKNoKddNxn0CNpoZ3eTZuqxjNM3b1GImVx22+ePI+Y15P8g/j3WsbP0fhzzrFzrtjOAoq5NCCucswXOQ==} + node-fetch@2.6.12: resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} engines: {node: 4.x || >=6.0.0} @@ -1444,6 +1673,12 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + object-filter-sequence@1.0.0: + resolution: {integrity: sha512-CsubGNxhIEChNY4cXYuA6KXafztzHqzLLZ/y3Kasf3A+sa3lL9thq3z+7o0pZqzEinjXT6lXDPAfVWI59dUyzQ==} + + object-identity-map@1.0.2: + resolution: {integrity: sha512-a2XZDGyYTngvGS67kWnqVdpoaJWsY7C1GhPJvejWAFCsUioTAaiTu8oBad7c6cI4McZxr4CmvnZeycK05iav5A==} + object-inspect@1.13.1: resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} @@ -1455,16 +1690,31 @@ packages: resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} engines: {node: '>= 0.4'} + object.entries@1.1.8: + resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} + engines: {node: '>= 0.4'} + obliterator@2.0.4: resolution: {integrity: sha512-lgHwxlxV1qIg1Eap7LgIeoBWIMFibOjbrYPIPJZcI1mmGAI2m3lNYpK12Y+GBdPQ0U1hRwSord7GIaawz962qQ==} + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} + on-headers@1.0.2: + resolution: {integrity: sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==} + engines: {node: '>= 0.8'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + one-time@1.0.0: + resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==} + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} @@ -1473,6 +1723,12 @@ packages: resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} engines: {node: '>=8'} + optional-js@2.3.0: + resolution: {integrity: sha512-B0LLi+Vg+eko++0z/b8zIv57kp7HKEzaPJo7LowJXMUKYdf+3XJGu/cw03h/JhIOsLnP+cG5QnTHAuicjA5fMw==} + + original-url@1.2.3: + resolution: {integrity: sha512-BYm+pKYLtS4mVe/mgT3YKGtWV5HzN/XKiaIu1aK4rsxyjuHeTW9N+xVBEpJcY1onB3nccfH0RbzUEoimMqFUHQ==} + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -1550,6 +1806,13 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + pino-std-serializers@3.2.0: + resolution: {integrity: sha512-EqX4pwDPrt3MuOAAUBMU0Tk5kR/YcCM5fNPEzgCO2zJ5HfX0vbiH9HbJglnyeQsN96Kznae6MWD47pZB5avTrg==} + + pino@6.14.0: + resolution: {integrity: sha512-iuhEDel3Z3hF9Jfe44DPXR8l07bhjuFY3GMHIXbjnY9XcafbyDDwl2sN2vw2GjMPf5Nkoe+OFao7ffn9SXaKDg==} + hasBin: true + pkg-dir@4.2.0: resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} engines: {node: '>=8'} @@ -1580,6 +1843,9 @@ packages: process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + process-warning@1.0.0: + resolution: {integrity: sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==} + progress@2.0.3: resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} engines: {node: '>=0.4.0'} @@ -1588,6 +1854,9 @@ packages: resolution: {integrity: sha512-Ew/MrPtTjiHnnki0AA2hS2o65JaZ5n+5pp08JSyWWUdeOGF4F41P+Dn+rdqnaOV/FTxhR6eBDX412luwn3th9g==} engines: {node: '>= 0.4'} + promise@8.3.0: + resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -1596,9 +1865,16 @@ packages: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} + pseudomap@1.0.2: + resolution: {integrity: sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==} + pstree.remy@1.1.8: resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + qs@6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} engines: {node: '>=0.6'} @@ -1610,6 +1886,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -1650,6 +1929,9 @@ packages: resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} engines: {node: '>= 0.4'} + relative-microtime@2.0.0: + resolution: {integrity: sha512-l18ha6HEZc+No/uK4GyAnNxgKW7nvEe35IaeN54sShMojtqik2a6GbTyuiezkjpPaqP874Z3lW5ysBo5irz4NA==} + replace-string@3.1.0: resolution: {integrity: sha512-yPpxc4ZR2makceA9hy/jHNqc7QVkd4Je/N0WRHm6bs3PtivPuPynxE5ejU/mp5EhnCv8+uZL7vhz8rkluSlx+Q==} engines: {node: '>=8'} @@ -1658,10 +1940,18 @@ packages: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} + require-in-the-middle@7.4.0: + resolution: {integrity: sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ==} + engines: {node: '>=8.6.0'} + resolve@1.22.4: resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} hasBin: true + resolve@1.22.8: + resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + hasBin: true + retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} engines: {node: '>= 4'} @@ -1692,6 +1982,10 @@ packages: resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} engines: {node: '>= 0.4'} + safe-stable-stringify@2.4.3: + resolution: {integrity: sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==} + engines: {node: '>=10'} + safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} @@ -1733,6 +2027,9 @@ packages: setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shallow-clone-shim@2.0.0: + resolution: {integrity: sha512-YRNymdiL3KGOoS67d73TEmk4tdPTO9GSMCoiphQsTcC9EtC+AOmMPjkyBkRoCJfW9ASsaZw1craaiw1dPN2D3Q==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1752,6 +2049,9 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} @@ -1767,10 +2067,17 @@ packages: resolution: {integrity: sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==} engines: {node: '>=8'} + sonic-boom@1.4.1: + resolution: {integrity: sha512-LRHh/A8tpW7ru89lrlkU4AszXt1dbwSjVWguGrmlxE7tawVmDBlI1PILMkXAxJTwqhgsEeTHzj36D5CmHgQmNg==} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.8.0-beta.0: + resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} + engines: {node: '>= 8'} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -1787,6 +2094,15 @@ packages: resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} engines: {node: '>=6'} + sql-summary@1.0.1: + resolution: {integrity: sha512-IpCr2tpnNkP3Jera4ncexsZUp0enJBLr+pHCyTweMUBrbJsTgQeLWx1FXLhoBj/MvcnUQpkgOn2EY8FKOkUzww==} + + stack-trace@0.0.10: + resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} + + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + statuses@2.0.1: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} @@ -1798,6 +2114,9 @@ packages: stream-chain@2.2.5: resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} + stream-chopper@3.0.1: + resolution: {integrity: sha512-f7h+ly8baAE26iIjcp3VbnBkbIRGtrvV0X0xxFM/d7fwLTYnLzDPTXRKNxa2HZzohOrc96NTrR+FaV3mzOelNA==} + stream-json@1.8.0: resolution: {integrity: sha512-HZfXngYHUAr1exT4fxlbc1IOce1RYxp2ldeaf97LYCOPSoOqY/1Psp7iGvpb+6JIOgkra9zDYnPX01hGAHzEPw==} @@ -1890,6 +2209,9 @@ packages: resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} engines: {node: '>=8'} + text-hex@1.0.0: + resolution: {integrity: sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==} + through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} @@ -1901,6 +2223,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + to-source-code@1.0.2: + resolution: {integrity: sha512-YzWtjmNIf3E75eZYa7m1SCyl0vgOGoTzdpH3svfa8SUm5rqTgl9hnDolrAGOghCF9P2gsITXQoMrlujOoz+RPw==} + toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} @@ -1912,6 +2237,13 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@1.0.1: + resolution: {integrity: sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==} + + triple-beam@1.4.1: + resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==} + engines: {node: '>= 14.0.0'} + ts-deepmerge@7.0.0: resolution: {integrity: sha512-WZ/iAJrKDhdINv1WG6KZIGHrZDar6VfhftG1QJFpVbOYZMYJLJOvZOo1amictRXVdBXZIgBHKswMTXzElngprA==} engines: {node: '>=14.13.1'} @@ -2000,6 +2332,12 @@ packages: resolution: {integrity: sha512-JfjKqIauur3Q6biAtHJ564e3bWa8VvT+7cSiOJHFbX4Erv6CLGDpg8z+Fmg/1OI/47RA+GI2QZaF48SSaLvyBA==} engines: {node: '>=18.17'} + unicode-byte-truncate@1.0.0: + resolution: {integrity: sha512-GQgHk6DodEoKddKQdjnv7xKS9G09XCfHWX0R4RKht+EbUMSiVEmtWHGFO8HUm+6NvWik3E2/DG4MxTitOLL64A==} + + unicode-substring@0.1.0: + resolution: {integrity: sha512-36Xaw9wXi7MB/3/EQZZHkZyyiRNa9i3k9YtPAz2KfqMVH2xutdXyMHn4Igarmnvr+wOrfWa/6njhY+jPpXN2EQ==} + unique-string@2.0.0: resolution: {integrity: sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==} engines: {node: '>=8'} @@ -2046,9 +2384,15 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@4.0.2: + resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + whatwg-url@7.1.0: + resolution: {integrity: sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -2061,6 +2405,18 @@ packages: engines: {node: '>= 8'} hasBin: true + winston-elasticsearch@0.19.0: + resolution: {integrity: sha512-yD+Wi/NmMsKCSkWvzdmk2RZ2KSHJ+ox5PM/480nsahWFtiLESI90ESXnS8Yfvc0N4NFnCXNaIj2FERIgjImjoQ==} + engines: {node: '>= 8.0.0'} + + winston-transport@4.7.1: + resolution: {integrity: sha512-wQCXXVgfv/wUPOfb2x0ruxzwkcZfxcktz6JIMUaPLmcNhO4bZTwA/WtDWK74xV3F2dKu8YadrFv0qhwYjVEwhA==} + engines: {node: '>= 12.0.0'} + + winston@3.13.1: + resolution: {integrity: sha512-SvZit7VFNvXRzbqGHsv5KSmgbEYR5EiQfDAL9gxYkRqa934Hnk++zze0wANKtMHcy/gI4W/3xmSDwlhf865WGw==} + engines: {node: '>= 12.0.0'} + wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} @@ -2087,6 +2443,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@2.1.2: + resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -2151,10 +2510,26 @@ snapshots: '@chevrotain/utils@10.5.0': {} + '@colors/colors@1.6.0': {} + '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@dabh/diagnostics@2.0.3': + dependencies: + colorspace: 1.1.4 + enabled: 2.0.0 + kuler: 2.0.0 + + '@elastic/ecs-helpers@2.1.1': + optional: true + + '@elastic/ecs-pino-format@1.5.0': + dependencies: + '@elastic/ecs-helpers': 2.1.1 + optional: true + '@elastic/elasticsearch@8.14.0': dependencies: '@elastic/transport': 8.6.1 @@ -2375,6 +2750,30 @@ snapshots: '@opentelemetry/api@1.4.1': {} + '@opentelemetry/core@1.25.1(@opentelemetry/api@1.4.1)': + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/semantic-conventions': 1.25.1 + optional: true + + '@opentelemetry/resources@1.25.1(@opentelemetry/api@1.4.1)': + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.4.1) + '@opentelemetry/semantic-conventions': 1.25.1 + optional: true + + '@opentelemetry/sdk-metrics@1.25.1(@opentelemetry/api@1.4.1)': + dependencies: + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.4.1) + '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.4.1) + lodash.merge: 4.6.2 + optional: true + + '@opentelemetry/semantic-conventions@1.25.1': + optional: true + '@pkgjs/parseargs@0.11.0': optional: true @@ -2622,6 +3021,10 @@ snapshots: dependencies: mime: 4.0.1 + '@types/morgan@1.9.9': + dependencies: + '@types/node': 20.14.9 + '@types/ms@0.7.34': {} '@types/multer@1.4.11': @@ -2656,6 +3059,8 @@ snapshots: '@types/express': 4.17.21 '@types/serve-static': 1.15.5 + '@types/triple-beam@1.3.5': {} + '@zxing/text-encoding@0.9.0': optional: true @@ -2666,16 +3071,29 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 + acorn-import-assertions@1.9.0(acorn@8.11.3): + dependencies: + acorn: 8.11.3 + optional: true + acorn-walk@8.3.2: {} acorn@8.11.3: {} + after-all-results@2.0.0: + optional: true + agent-base@7.1.1: dependencies: debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color + agentkeepalive@4.5.0: + dependencies: + humanize-ms: 1.2.1 + optional: true + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 @@ -2773,6 +3191,8 @@ snapshots: is-array-buffer: 3.0.4 is-shared-array-buffer: 1.0.3 + asap@2.0.6: {} + asn1.js@5.4.1: dependencies: bn.js: 4.12.0 @@ -2782,8 +3202,24 @@ snapshots: astral-regex@2.0.0: {} + async-cache@1.1.0: + dependencies: + lru-cache: 4.1.5 + optional: true + + async-value-promise@1.1.1: + dependencies: + async-value: 1.2.2 + optional: true + + async-value@1.2.2: + optional: true + async@3.2.5: {} + atomic-sleep@1.0.0: + optional: true + available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 @@ -2792,8 +3228,15 @@ snapshots: base64-js@1.5.1: {} + basic-auth@2.0.1: + dependencies: + safe-buffer: 5.1.2 + binary-extensions@2.3.0: {} + binary-search@1.3.6: + optional: true + bl@4.1.0: dependencies: buffer: 5.7.1 @@ -2836,6 +3279,11 @@ snapshots: dependencies: fill-range: 7.1.1 + breadth-filter@2.0.0: + dependencies: + object.entries: 1.1.8 + optional: true + browser-or-node@2.1.1: {} buffer-crc32@0.2.13: {} @@ -2897,6 +3345,9 @@ snapshots: ci-info@3.8.0: {} + cjs-module-lexer@1.3.1: + optional: true + clean-stack@2.2.0: {} cli-truncate@2.1.0: @@ -2922,6 +3373,21 @@ snapshots: color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + + color@3.2.1: + dependencies: + color-convert: 1.9.3 + color-string: 1.9.1 + + colorspace@1.1.4: + dependencies: + color: 3.2.1 + text-hex: 1.0.0 + commondir@1.0.1: {} compress-commons@4.1.2: @@ -2933,6 +3399,9 @@ snapshots: concat-map@0.0.1: {} + console-log-level@1.4.1: + optional: true + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -2941,6 +3410,9 @@ snapshots: cookie-signature@1.0.6: {} + cookie@0.5.0: + optional: true + cookie@0.6.0: {} core-util-is@1.0.3: {} @@ -2985,6 +3457,8 @@ snapshots: es-errors: 1.3.0 is-data-view: 1.0.1 + dayjs@1.11.12: {} + debug@2.6.9: dependencies: ms: 2.0.0 @@ -2995,6 +3469,11 @@ snapshots: optionalDependencies: supports-color: 5.5.0 + debug@4.3.5: + dependencies: + ms: 2.1.2 + optional: true + decode-uri-component@0.2.2: {} define-data-property@1.1.4: @@ -3042,10 +3521,56 @@ snapshots: ee-first@1.1.1: {} + elastic-apm-node@3.51.0: + dependencies: + '@elastic/ecs-pino-format': 1.5.0 + '@opentelemetry/api': 1.4.1 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.4.1) + '@opentelemetry/sdk-metrics': 1.25.1(@opentelemetry/api@1.4.1) + after-all-results: 2.0.0 + agentkeepalive: 4.5.0 + async-cache: 1.1.0 + async-value-promise: 1.1.1 + basic-auth: 2.0.1 + breadth-filter: 2.0.0 + cookie: 0.5.0 + core-util-is: 1.0.3 + end-of-stream: 1.4.4 + error-callsites: 2.0.4 + error-stack-parser: 2.1.4 + escape-string-regexp: 4.0.0 + fast-safe-stringify: 2.1.1 + fast-stream-to-buffer: 1.0.0 + http-headers: 3.0.2 + import-in-the-middle: 1.4.2 + is-native: 1.0.1 + lru-cache: 6.0.0 + measured-reporting: 1.51.1 + module-details-from-path: 1.0.3 + monitor-event-loop-delay: 1.0.0 + object-filter-sequence: 1.0.0 + object-identity-map: 1.0.2 + original-url: 1.2.3 + pino: 6.14.0 + readable-stream: 3.6.2 + relative-microtime: 2.0.0 + require-in-the-middle: 7.4.0 + semver: 6.3.1 + shallow-clone-shim: 2.0.0 + source-map: 0.8.0-beta.0 + sql-summary: 1.0.1 + stream-chopper: 3.0.1 + unicode-byte-truncate: 1.0.0 + transitivePeerDependencies: + - supports-color + optional: true + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} + enabled@2.0.0: {} + encodeurl@1.0.2: {} end-of-stream@1.4.4: @@ -3054,10 +3579,18 @@ snapshots: env-paths@2.2.1: {} + error-callsites@2.0.4: + optional: true + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + optional: true + es-abstract@1.23.3: dependencies: array-buffer-byte-length: 1.0.1 @@ -3229,6 +3762,17 @@ snapshots: ecdsa-sig-formatter: 1.0.11 mnemonist: 0.39.8 + fast-redact@3.5.0: + optional: true + + fast-safe-stringify@2.1.1: + optional: true + + fast-stream-to-buffer@1.0.0: + dependencies: + end-of-stream: 1.4.4 + optional: true + fast-xml-parser@4.3.6: dependencies: strnum: 1.0.5 @@ -3237,6 +3781,8 @@ snapshots: dependencies: reusify: 1.0.4 + fecha@4.2.3: {} + fill-range@7.1.1: dependencies: to-regex-range: 5.0.1 @@ -3271,6 +3817,11 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 + flatstr@1.0.12: + optional: true + + fn.name@1.1.0: {} + for-each@0.3.3: dependencies: is-callable: 1.2.7 @@ -3280,6 +3831,9 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + forwarded-parse@2.1.2: + optional: true + forwarded@0.2.0: {} fp-ts@2.16.1: {} @@ -3438,6 +3992,11 @@ snapshots: statuses: 2.0.1 toidentifier: 1.0.1 + http-headers@3.0.2: + dependencies: + next-line: 1.1.0 + optional: true + http-proxy-agent@7.0.0: dependencies: agent-base: 7.1.1 @@ -3454,6 +4013,11 @@ snapshots: human-signals@2.1.0: {} + humanize-ms@1.2.1: + dependencies: + ms: 2.1.3 + optional: true + iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -3468,6 +4032,14 @@ snapshots: ignore@5.3.1: {} + import-in-the-middle@1.4.2: + dependencies: + acorn: 8.11.3 + acorn-import-assertions: 1.9.0(acorn@8.11.3) + cjs-module-lexer: 1.3.1 + module-details-from-path: 1.0.3 + optional: true + indent-string@4.0.0: {} inflight@1.0.6: @@ -3501,6 +4073,8 @@ snapshots: is-arrayish@0.2.1: {} + is-arrayish@0.3.2: {} + is-bigint@1.0.4: dependencies: has-bigints: 1.0.2 @@ -3532,6 +4106,9 @@ snapshots: is-extglob@2.1.1: {} + is-finite@1.1.0: + optional: true + is-fullwidth-code-point@3.0.0: {} is-generator-function@1.0.10: @@ -3542,10 +4119,24 @@ snapshots: dependencies: is-extglob: 2.1.1 + is-integer@1.0.7: + dependencies: + is-finite: 1.1.0 + optional: true + is-map@2.0.3: {} + is-native@1.0.1: + dependencies: + is-nil: 1.0.1 + to-source-code: 1.0.2 + optional: true + is-negative-zero@2.0.3: {} + is-nil@1.0.1: + optional: true + is-number-object@1.0.7: dependencies: has-tostringtag: 1.0.2 @@ -3626,6 +4217,8 @@ snapshots: kleur@4.1.5: {} + kuler@2.0.0: {} + kysely@0.27.3: {} lazystream@1.0.1: @@ -3652,12 +4245,35 @@ snapshots: lodash.isplainobject@4.0.6: {} + lodash.merge@4.6.2: + optional: true + + lodash.omit@4.5.0: {} + + lodash.sortby@4.7.0: + optional: true + lodash.union@4.6.0: {} lodash@4.17.21: {} + logform@2.6.1: + dependencies: + '@colors/colors': 1.6.0 + '@types/triple-beam': 1.3.5 + fecha: 4.2.3 + ms: 2.1.3 + safe-stable-stringify: 2.4.3 + triple-beam: 1.4.1 + lru-cache@10.2.0: {} + lru-cache@4.1.5: + dependencies: + pseudomap: 1.0.2 + yallist: 2.1.2 + optional: true + lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -3672,6 +4288,23 @@ snapshots: make-error@1.3.6: {} + mapcap@1.0.0: + optional: true + + measured-core@1.51.1: + dependencies: + binary-search: 1.3.6 + optional-js: 2.3.0 + optional: true + + measured-reporting@1.51.1: + dependencies: + console-log-level: 1.4.1 + mapcap: 1.0.0 + measured-core: 1.51.1 + optional-js: 2.3.0 + optional: true + media-typer@0.3.0: {} merge-anything@5.1.7: @@ -3744,6 +4377,22 @@ snapshots: dependencies: obliterator: 2.0.4 + module-details-from-path@1.0.3: + optional: true + + monitor-event-loop-delay@1.0.0: + optional: true + + morgan@1.10.0: + dependencies: + basic-auth: 2.0.1 + debug: 2.6.9 + depd: 2.0.0 + on-finished: 2.3.0 + on-headers: 1.0.2 + transitivePeerDependencies: + - supports-color + ms@2.0.0: {} ms@2.1.2: {} @@ -3756,6 +4405,9 @@ snapshots: new-github-issue-url@0.2.1: {} + next-line@1.1.0: + optional: true + node-fetch@2.6.12: dependencies: whatwg-url: 5.0.0 @@ -3809,6 +4461,14 @@ snapshots: object-assign@4.1.1: {} + object-filter-sequence@1.0.0: + optional: true + + object-identity-map@1.0.2: + dependencies: + object.entries: 1.1.8 + optional: true + object-inspect@1.13.1: {} object-keys@1.1.1: {} @@ -3820,16 +4480,33 @@ snapshots: has-symbols: 1.0.3 object-keys: 1.1.1 + object.entries@1.1.8: + dependencies: + call-bind: 1.0.7 + define-properties: 1.2.1 + es-object-atoms: 1.0.0 + optional: true + obliterator@2.0.4: {} + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + on-finished@2.4.1: dependencies: ee-first: 1.1.1 + on-headers@1.0.2: {} + once@1.4.0: dependencies: wrappy: 1.0.2 + one-time@1.0.0: + dependencies: + fn.name: 1.1.0 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 @@ -3839,6 +4516,14 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 + optional-js@2.3.0: + optional: true + + original-url@1.2.3: + dependencies: + forwarded-parse: 2.1.2 + optional: true + p-filter@2.1.0: dependencies: p-map: 2.1.0 @@ -3902,6 +4587,20 @@ snapshots: picomatch@2.3.1: {} + pino-std-serializers@3.2.0: + optional: true + + pino@6.14.0: + dependencies: + fast-redact: 3.5.0 + fast-safe-stringify: 2.1.1 + flatstr: 1.0.12 + pino-std-serializers: 3.2.0 + process-warning: 1.0.0 + quick-format-unescaped: 4.0.4 + sonic-boom: 1.4.1 + optional: true + pkg-dir@4.2.0: dependencies: find-up: 4.1.0 @@ -3931,6 +4630,9 @@ snapshots: process-nextick-args@2.0.1: {} + process-warning@1.0.0: + optional: true + progress@2.0.3: {} promise.any@2.0.6: @@ -3943,6 +4645,10 @@ snapshots: get-intrinsic: 1.2.4 iterate-value: 1.0.2 + promise@8.3.0: + dependencies: + asap: 2.0.6 + prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -3953,8 +4659,14 @@ snapshots: forwarded: 0.2.0 ipaddr.js: 1.9.1 + pseudomap@1.0.2: + optional: true + pstree.remy@1.1.8: {} + punycode@2.3.1: + optional: true + qs@6.11.0: dependencies: side-channel: 1.0.6 @@ -3968,6 +4680,9 @@ snapshots: queue-microtask@1.2.3: {} + quick-format-unescaped@4.0.4: + optional: true + range-parser@1.2.1: {} raw-body@2.5.2: @@ -4025,16 +4740,35 @@ snapshots: es-errors: 1.3.0 set-function-name: 2.0.2 + relative-microtime@2.0.0: + optional: true + replace-string@3.1.0: {} require-directory@2.1.1: {} + require-in-the-middle@7.4.0: + dependencies: + debug: 4.3.5 + module-details-from-path: 1.0.3 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + optional: true + resolve@1.22.4: dependencies: is-core-module: 2.14.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + resolve@1.22.8: + dependencies: + is-core-module: 2.14.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + optional: true + retry@0.13.1: {} reusify@1.0.4: {} @@ -4064,6 +4798,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.1.4 + safe-stable-stringify@2.4.3: {} + safer-buffer@2.1.2: {} sax@1.3.0: {} @@ -4123,6 +4859,9 @@ snapshots: setprototypeof@1.2.0: {} + shallow-clone-shim@2.0.0: + optional: true + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -4140,6 +4879,10 @@ snapshots: signal-exit@4.1.0: {} + simple-swizzle@0.2.2: + dependencies: + is-arrayish: 0.3.2 + simple-update-notifier@2.0.0: dependencies: semver: 7.6.0 @@ -4154,8 +4897,19 @@ snapshots: astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 + sonic-boom@1.4.1: + dependencies: + atomic-sleep: 1.0.0 + flatstr: 1.0.12 + optional: true + source-map@0.6.1: {} + source-map@0.8.0-beta.0: + dependencies: + whatwg-url: 7.1.0 + optional: true + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -4172,6 +4926,14 @@ snapshots: split-on-first@1.1.0: {} + sql-summary@1.0.1: + optional: true + + stack-trace@0.0.10: {} + + stackframe@1.3.4: + optional: true + statuses@2.0.1: {} stop-iteration-iterator@1.0.0: @@ -4180,6 +4942,11 @@ snapshots: stream-chain@2.2.5: {} + stream-chopper@3.0.1: + dependencies: + readable-stream: 3.6.2 + optional: true + stream-json@1.8.0: dependencies: stream-chain: 2.2.5 @@ -4286,6 +5053,8 @@ snapshots: ansi-escapes: 4.3.2 supports-hyperlinks: 2.3.0 + text-hex@1.0.0: {} + through2@4.0.2: dependencies: readable-stream: 3.6.2 @@ -4298,6 +5067,11 @@ snapshots: dependencies: is-number: 7.0.0 + to-source-code@1.0.2: + dependencies: + is-nil: 1.0.1 + optional: true + toidentifier@1.0.1: {} touch@3.1.0: @@ -4306,6 +5080,13 @@ snapshots: tr46@0.0.3: {} + tr46@1.0.1: + dependencies: + punycode: 2.3.1 + optional: true + + triple-beam@1.4.1: {} + ts-deepmerge@7.0.0: {} ts-node@10.9.2(@types/node@20.14.9)(typescript@5.5.2): @@ -4400,6 +5181,15 @@ snapshots: undici@6.19.2: {} + unicode-byte-truncate@1.0.0: + dependencies: + is-integer: 1.0.7 + unicode-substring: 0.1.0 + optional: true + + unicode-substring@0.1.0: + optional: true + unique-string@2.0.0: dependencies: crypto-random-string: 2.0.0 @@ -4441,11 +5231,21 @@ snapshots: webidl-conversions@3.0.1: {} + webidl-conversions@4.0.2: + optional: true + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 webidl-conversions: 3.0.1 + whatwg-url@7.1.0: + dependencies: + lodash.sortby: 4.7.0 + tr46: 1.0.1 + webidl-conversions: 4.0.2 + optional: true + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 @@ -4466,6 +5266,42 @@ snapshots: dependencies: isexe: 2.0.0 + winston-elasticsearch@0.19.0: + dependencies: + '@elastic/elasticsearch': 8.14.0 + dayjs: 1.11.12 + debug: 4.3.4(supports-color@5.5.0) + lodash.defaults: 4.2.0 + lodash.omit: 4.5.0 + promise: 8.3.0 + retry: 0.13.1 + winston: 3.13.1 + winston-transport: 4.7.1 + optionalDependencies: + elastic-apm-node: 3.51.0 + transitivePeerDependencies: + - supports-color + + winston-transport@4.7.1: + dependencies: + logform: 2.6.1 + readable-stream: 3.6.2 + triple-beam: 1.4.1 + + winston@3.13.1: + dependencies: + '@colors/colors': 1.6.0 + '@dabh/diagnostics': 2.0.3 + async: 3.2.5 + is-stream: 2.0.1 + logform: 2.6.1 + one-time: 1.0.0 + readable-stream: 3.6.2 + safe-stable-stringify: 2.4.3 + stack-trace: 0.0.10 + triple-beam: 1.4.1 + winston-transport: 4.7.1 + wordwrap@1.0.0: {} wrap-ansi@7.0.0: @@ -4491,6 +5327,9 @@ snapshots: y18n@5.0.8: {} + yallist@2.1.2: + optional: true + yallist@4.0.0: {} yaml@2.4.1: {} From 8cd393151ee55fbbe5190167e15c13c8e757fbb4 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 1 Aug 2024 09:53:16 +0700 Subject: [PATCH 074/102] feat: image endpoint (branch endpoint) --- src/controllers/branch-controller.ts | 84 +++++++++++++++++++++++++++- 1 file changed, 83 insertions(+), 1 deletion(-) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index a4b5f10..fd82fac 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -18,7 +18,7 @@ import prisma from "../db"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; import { RequestWithUser } from "../interfaces/user"; -import minio from "../services/minio"; +import minio, { presignedGetObjectIfExist } from "../services/minio"; if (!process.env.MINIO_BUCKET) { throw Error("Require MinIO bucket."); @@ -533,4 +533,86 @@ export class BranchController extends Controller { where: { id: branchId }, }); } + + @Get("{branchId}/line-image") + async getLineImageByBranchId(@Request() req: RequestWithUser, @Path() branchId: string) { + const url = await presignedGetObjectIfExist(MINIO_BUCKET, lineImageLoc(branchId), 60 * 60); + + if (!url) { + throw new HttpError(HttpStatus.NOT_FOUND, "Image cannot be found", "imageNotFound"); + } + + return req.res?.redirect(url); + } + + @Put("{branchId}/line-image") + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_admin", "branch_manager"]) + async setLineImageByBranchId(@Request() req: RequestWithUser, @Path() branchId: string) { + const record = await prisma.branch.findUnique({ + include: { + user: { where: { userId: req.user.sub } }, + }, + where: { id: branchId }, + }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound"); + } + + if ( + !["system", "head_of_admin", "admin"].some((v) => req.user.roles?.includes(v)) && + !record?.user.find((v) => v.userId === req.user.sub) + ) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } + + return req.res?.redirect( + await minio.presignedPutObject(MINIO_BUCKET, lineImageLoc(record.id), 12 * 60 * 60), + ); + } + + @Get("{branchId}/branch-image") + async getBranchImageByBranchId(@Request() req: RequestWithUser, @Path() branchId: string) { + const url = await presignedGetObjectIfExist(MINIO_BUCKET, branchImageLoc(branchId), 60 * 60); + + if (!url) { + throw new HttpError(HttpStatus.NOT_FOUND, "Image cannot be found", "imageNotFound"); + } + + return req.res?.redirect(url); + } + + @Put("{branchId}/branch-image") + @Security("keycloak", ["system", "head_of_admin", "admin", "branch_admin", "branch_manager"]) + async setBranchImageByBranchId(@Request() req: RequestWithUser, @Path() branchId: string) { + const record = await prisma.branch.findUnique({ + include: { + user: { where: { userId: req.user.sub } }, + }, + where: { id: branchId }, + }); + + if (!record) { + throw new HttpError(HttpStatus.NOT_FOUND, "Branch cannot be found.", "branchNotFound"); + } + + if ( + !["system", "head_of_admin", "admin"].some((v) => req.user.roles?.includes(v)) && + !record?.user.find((v) => v.userId === req.user.sub) + ) { + throw new HttpError( + HttpStatus.FORBIDDEN, + "You do not have permission to perform this action.", + "noPermission", + ); + } + + return req.res?.redirect( + await minio.presignedPutObject(MINIO_BUCKET, branchImageLoc(record.id), 12 * 60 * 60), + ); + } } From e6315a068721fa32d7c3ef04405e0623221c9354 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 1 Aug 2024 15:40:36 +0700 Subject: [PATCH 075/102] fix: error update on empty productGroupId --- src/controllers/product/type-controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/product/type-controller.ts b/src/controllers/product/type-controller.ts index 42172ad..b86ca39 100644 --- a/src/controllers/product/type-controller.ts +++ b/src/controllers/product/type-controller.ts @@ -202,8 +202,8 @@ export class ProductType extends Controller { where: { id: typeId }, }); - if (productGroup?.status === "CREATED") { - await prisma.productGroup.update({ + if (body.productGroupId && productGroup?.status === "CREATED") { + await prisma.productGroup.updateMany({ where: { id: body.productGroupId, status: Status.CREATED }, data: { status: Status.ACTIVE }, }); From a7702d8599f13155552d599cd5a243d59ddd6c10 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:50:07 +0700 Subject: [PATCH 076/102] refactor: do not update parent status to active --- src/controllers/customer-branch-controller.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/controllers/customer-branch-controller.ts b/src/controllers/customer-branch-controller.ts index 5a8de8a..a08baf4 100644 --- a/src/controllers/customer-branch-controller.ts +++ b/src/controllers/customer-branch-controller.ts @@ -329,11 +329,6 @@ export class CustomerBranchController extends Controller { { isolationLevel: Prisma.TransactionIsolationLevel.Serializable }, ); - await prisma.customer.updateMany({ - where: { id: customerId, status: Status.CREATED }, - data: { status: Status.ACTIVE }, - }); - this.setStatus(HttpStatus.CREATED); return record; From ef1f404779565dcf5c5983d750b3883c14ad0dbb Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:50:23 +0700 Subject: [PATCH 077/102] chore: format --- src/controllers/customer-branch-controller.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/controllers/customer-branch-controller.ts b/src/controllers/customer-branch-controller.ts index a08baf4..a9cf867 100644 --- a/src/controllers/customer-branch-controller.ts +++ b/src/controllers/customer-branch-controller.ts @@ -456,9 +456,7 @@ export class CustomerBranchController extends Controller { stream.on("error", () => reject(new Error("MinIO error."))); }).then((list) => { list.map(async (v) => { - await minio.removeObject(MINIO_BUCKET, v, { - forceDelete: true, - }); + await minio.removeObject(MINIO_BUCKET, v, { forceDelete: true }); }); }); return v; From 3e4709d8ffad7f0fe73156e917fc08f34aa6bded Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 2 Aug 2024 14:53:54 +0700 Subject: [PATCH 078/102] feat: add bank to branch payload (optional) --- .../migration.sql | 13 +++++++++++++ prisma/schema.prisma | 12 ++++++++++++ src/controllers/branch-controller.ts | 18 ++++++++++++++++-- 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 prisma/migrations/20240802075330_add_bank_relation_with_branch/migration.sql diff --git a/prisma/migrations/20240802075330_add_bank_relation_with_branch/migration.sql b/prisma/migrations/20240802075330_add_bank_relation_with_branch/migration.sql new file mode 100644 index 0000000..cfb1ba1 --- /dev/null +++ b/prisma/migrations/20240802075330_add_bank_relation_with_branch/migration.sql @@ -0,0 +1,13 @@ +-- CreateTable +CREATE TABLE "BranchBank" ( + "id" TEXT NOT NULL, + "bankName" TEXT NOT NULL, + "accountName" TEXT NOT NULL, + "accountNumber" TEXT NOT NULL, + "branchId" TEXT, + + CONSTRAINT "BranchBank_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "BranchBank" ADD CONSTRAINT "BranchBank_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index afab626..7c9bbb0 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -217,6 +217,8 @@ model Branch { headOffice Branch? @relation(name: "HeadOfficeRelation", fields: [headOfficeId], references: [id]) headOfficeId String? + bank BranchBank[] + status Status @default(CREATED) statusOrder Int @default(0) @@ -236,6 +238,16 @@ model Branch { customerRegistration Customer[] } +model BranchBank { + id String @id @default(uuid()) + bankName String + accountName String + accountNumber String + + branch Branch? @relation(fields: [branchId], references: [id]) + branchId String? +} + model BranchContact { id String @id @default(uuid()) telephoneNo String diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index fd82fac..06a91f3 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -42,6 +42,12 @@ type BranchCreate = { longitude: string; latitude: string; + bank?: { + bankName: string; + accountName: string; + accountNumber: string; + }[]; + subDistrictId?: string | null; districtId?: string | null; provinceId?: string | null; @@ -68,6 +74,12 @@ type BranchUpdate = { districtId?: string | null; provinceId?: string | null; headOfficeId?: string | null; + + bank?: { + bankName: string; + accountName: string; + accountNumber: string; + }[]; }; function lineImageLoc(id: string) { @@ -273,7 +285,7 @@ export class BranchController extends Controller { "relationHQNotFound", ); - const { provinceId, districtId, subDistrictId, headOfficeId, contact, ...rest } = body; + const { provinceId, districtId, subDistrictId, headOfficeId, bank, contact, ...rest } = body; const year = new Date().getFullYear(); @@ -306,6 +318,7 @@ export class BranchController extends Controller { ...rest, statusOrder: +(rest.status === "INACTIVE"), code, + bank: bank ? { createMany: { data: bank } } : undefined, isHeadOffice: !headOfficeId, province: { connect: provinceId ? { id: provinceId } : undefined }, district: { connect: districtId ? { id: districtId } : undefined }, @@ -401,7 +414,7 @@ export class BranchController extends Controller { ); } - const { provinceId, districtId, subDistrictId, headOfficeId, contact, ...rest } = body; + const { provinceId, districtId, subDistrictId, headOfficeId, bank, contact, ...rest } = body; const branch = await prisma.branch.findUnique({ include: { @@ -431,6 +444,7 @@ export class BranchController extends Controller { ...rest, statusOrder: +(rest.status === "INACTIVE"), isHeadOffice: headOfficeId !== undefined ? headOfficeId === null : undefined, + bank: bank ? { deleteMany: {}, createMany: { data: bank } } : undefined, province: { connect: provinceId ? { id: provinceId } : undefined, disconnect: provinceId === null || undefined, From 0bd3ce55a743e3bc7ec18fc5230b1912aee6b15a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 2 Aug 2024 17:30:29 +0700 Subject: [PATCH 079/102] feat: add bank field --- .../migration.sql | 20 +++++++++++++++++++ prisma/schema.prisma | 9 ++++++--- src/controllers/branch-controller.ts | 8 ++++++++ 3 files changed, 34 insertions(+), 3 deletions(-) create mode 100644 prisma/migrations/20240802103017_add_field_bank/migration.sql diff --git a/prisma/migrations/20240802103017_add_field_bank/migration.sql b/prisma/migrations/20240802103017_add_field_bank/migration.sql new file mode 100644 index 0000000..1305b42 --- /dev/null +++ b/prisma/migrations/20240802103017_add_field_bank/migration.sql @@ -0,0 +1,20 @@ +/* + Warnings: + + - Added the required column `accountType` to the `BranchBank` table without a default value. This is not possible if the table is not empty. + - Added the required column `bankBranch` to the `BranchBank` table without a default value. This is not possible if the table is not empty. + - Added the required column `currentlyUse` to the `BranchBank` table without a default value. This is not possible if the table is not empty. + - Made the column `branchId` on table `BranchBank` required. This step will fail if there are existing NULL values in that column. + +*/ +-- DropForeignKey +ALTER TABLE "BranchBank" DROP CONSTRAINT "BranchBank_branchId_fkey"; + +-- AlterTable +ALTER TABLE "BranchBank" ADD COLUMN "accountType" TEXT NOT NULL, +ADD COLUMN "bankBranch" TEXT NOT NULL, +ADD COLUMN "currentlyUse" BOOLEAN NOT NULL, +ALTER COLUMN "branchId" SET NOT NULL; + +-- AddForeignKey +ALTER TABLE "BranchBank" ADD CONSTRAINT "BranchBank_branchId_fkey" FOREIGN KEY ("branchId") REFERENCES "Branch"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7c9bbb0..d477750 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -239,13 +239,16 @@ model Branch { } model BranchBank { - id String @id @default(uuid()) + id String @id @default(uuid()) bankName String + bankBranch String accountName String accountNumber String + accountType String + currentlyUse Boolean - branch Branch? @relation(fields: [branchId], references: [id]) - branchId String? + branch Branch @relation(fields: [branchId], references: [id], onDelete: Cascade) + branchId String } model BranchContact { diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index 06a91f3..4003f4b 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -44,8 +44,11 @@ type BranchCreate = { bank?: { bankName: string; + bankBranch: string; accountName: string; accountNumber: string; + accountType: string; + currentlyUse: boolean; }[]; subDistrictId?: string | null; @@ -77,8 +80,11 @@ type BranchUpdate = { bank?: { bankName: string; + bankBranch: string; accountName: string; accountNumber: string; + accountType: string; + currentlyUse: boolean; }[]; }; @@ -199,6 +205,7 @@ export class BranchController extends Controller { subDistrict: true, }, }, + bank: true, _count: { select: { branch: true }, }, @@ -236,6 +243,7 @@ export class BranchController extends Controller { subDistrict: true, }, }, + bank: true, contact: includeContact, }, where: { id: branchId }, From 2ed718ccd06b6ad338847cc7a26f480156fb7fe8 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Mon, 5 Aug 2024 10:59:44 +0700 Subject: [PATCH 080/102] refactor: change relationship --- .../20240805035626_change_relation_1_to_1/migration.sql | 8 ++++++++ prisma/schema.prisma | 4 ++-- src/controllers/employee-controller.ts | 6 ++++-- 3 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 prisma/migrations/20240805035626_change_relation_1_to_1/migration.sql diff --git a/prisma/migrations/20240805035626_change_relation_1_to_1/migration.sql b/prisma/migrations/20240805035626_change_relation_1_to_1/migration.sql new file mode 100644 index 0000000..758ce4d --- /dev/null +++ b/prisma/migrations/20240805035626_change_relation_1_to_1/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - A unique constraint covering the columns `[employeeId]` on the table `EmployeeOtherInfo` will be added. If there are existing duplicate values, this will fail. + +*/ +-- CreateIndex +CREATE UNIQUE INDEX "EmployeeOtherInfo_employeeId_key" ON "EmployeeOtherInfo"("employeeId"); diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d477750..61be7da 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -545,7 +545,7 @@ model Employee { employeeCheckup EmployeeCheckup[] employeeWork EmployeeWork[] - employeeOtherInfo EmployeeOtherInfo[] + employeeOtherInfo EmployeeOtherInfo? editHistory EmployeeHistory[] quotationWorker QuotationWorker[] @@ -620,7 +620,7 @@ model EmployeeOtherInfo { id String @id @default(uuid()) employee Employee @relation(fields: [employeeId], references: [id], onDelete: Cascade) - employeeId String + employeeId String @unique citizenId String? fatherBirthPlace String? diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts index 3863413..4fcf5cb 100644 --- a/src/controllers/employee-controller.ts +++ b/src/controllers/employee-controller.ts @@ -323,6 +323,9 @@ export class EmployeeController extends Controller { async getById(@Path() employeeId: string) { const record = await prisma.employee.findFirst({ include: { + employeeWork: true, + employeeCheckup: true, + employeeOtherInfo: true, province: true, district: true, subDistrict: true, @@ -669,8 +672,7 @@ export class EmployeeController extends Controller { : undefined, employeeOtherInfo: employeeOtherInfo ? { - deleteMany: {}, - create: employeeOtherInfo, + update: employeeOtherInfo, } : undefined, province: { From c92ae50f82da2e6cdde3d44451f0613c83960f18 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 7 Aug 2024 14:49:02 +0700 Subject: [PATCH 081/102] feat: new gen branch code --- src/controllers/branch-controller.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index 4003f4b..5e65729 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -28,6 +28,7 @@ const MINIO_BUCKET = process.env.MINIO_BUCKET; type BranchCreate = { status?: Status; + code: string; taxNo: string; nameEN: string; name: string; @@ -293,27 +294,22 @@ export class BranchController extends Controller { "relationHQNotFound", ); - const { provinceId, districtId, subDistrictId, headOfficeId, bank, contact, ...rest } = body; - - const year = new Date().getFullYear(); + const { provinceId, districtId, subDistrictId, headOfficeId, bank, contact, code, ...rest } = + body; const record = await prisma.$transaction( async (tx) => { const last = await tx.runningNo.upsert({ where: { - key: !headOfficeId ? `HQ${year.toString().slice(2)}` : `BR${head?.code.slice(2, 5)}`, + key: `MAIN_BRANCH_${code}`, }, create: { - key: !headOfficeId ? `HQ${year.toString().slice(2)}` : `BR${head?.code.slice(2, 5)}`, + key: `MAIN_BRANCH_${code}`, value: 1, }, update: { value: { increment: 1 } }, }); - const code = !headOfficeId - ? `HQ${year.toString().slice(2)}${last.value}` - : `BR${head?.code.slice(2, 5)}${last.value.toString().padStart(2, "0")}`; - return await tx.branch.create({ include: { province: true, @@ -325,7 +321,7 @@ export class BranchController extends Controller { data: { ...rest, statusOrder: +(rest.status === "INACTIVE"), - code, + code: `${code?.toLocaleUpperCase()}${`${last.value - 1}`.padStart(6, "0")}`, bank: bank ? { createMany: { data: bank } } : undefined, isHeadOffice: !headOfficeId, province: { connect: provinceId ? { id: provinceId } : undefined }, From f7e4d415496ceb8c201de152612dadb7457c4d4a Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 8 Aug 2024 09:14:26 +0700 Subject: [PATCH 082/102] feat: change code gen of user --- src/controllers/user-controller.ts | 30 ++++++++++-------------------- 1 file changed, 10 insertions(+), 20 deletions(-) diff --git a/src/controllers/user-controller.ts b/src/controllers/user-controller.ts index a1bf870..e2b8e14 100644 --- a/src/controllers/user-controller.ts +++ b/src/controllers/user-controller.ts @@ -124,12 +124,19 @@ async function userBranchCodeGen(user: User, branch: Branch) { async (tx) => { const typ = user.userType; + const mapTypeNo = { + USER: 1, + MESSENGER: 2, + DELEGATE: 3, + AGENCY: 4, + }[typ]; + const last = await tx.runningNo.upsert({ where: { - key: `BR_USR_${branch.code.slice(4).padEnd(3, "0")}${typ !== "USER" ? typ.charAt(0).toLocaleUpperCase() : ""}`, + key: `BR_USR_${branch.code}_${mapTypeNo}`, }, create: { - key: `BR_USR_${branch.code.slice(4).padEnd(3, "0")}${typ !== "USER" ? typ.charAt(0).toLocaleUpperCase() : ""}`, + key: `BR_USR_${branch.code}_${mapTypeNo}`, value: 1, }, update: { value: { increment: 1 } }, @@ -138,7 +145,7 @@ async function userBranchCodeGen(user: User, branch: Branch) { return await tx.user.update({ where: { id: user.id }, data: { - code: `${last.key.slice(7)}${last.value.toString().padStart(4, "0")}`, + code: mapTypeNo + `${last.value}`.padStart(6, "9"), }, }); }, @@ -301,7 +308,6 @@ export class UserController extends Controller { !["system", "head_of_admin", "admin"].some((v) => req.user.roles?.includes(v)) && branch?.some((v) => !v.user.find((v) => v.userId === req.user.sub)) ) { - console.log(req.user.roles); throw new HttpError( HttpStatus.FORBIDDEN, "You do not have permission to perform this action.", @@ -506,18 +512,6 @@ export class UserController extends Controller { const { provinceId, districtId, subDistrictId, branchId, ...rest } = body; - const lastUserOfType = - body.userType && - body.userType !== user.userType && - user.code && - (await prisma.user.findFirst({ - orderBy: { createdAt: "desc" }, - where: { - userType: body.userType, - code: { startsWith: `${user.code?.slice(0, 3)}` }, - }, - })); - const record = await prisma.user.update({ include: { province: true, @@ -530,10 +524,6 @@ export class UserController extends Controller { ...rest, statusOrder: +(rest.status === "INACTIVE"), userRole, - code: - (lastUserOfType && - `${user.code?.slice(0, 3)}${body.userType !== "USER" ? body.userType?.charAt(0) : ""}${(+(lastUserOfType?.code?.slice(-4) || 0) + 1).toString().padStart(4, "0")}`) || - undefined, province: { connect: provinceId ? { id: provinceId } : undefined, disconnect: provinceId === null || undefined, From 0ffa7e410ac19f70b63ce9bc41fa44c762f5b822 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 8 Aug 2024 10:18:04 +0700 Subject: [PATCH 083/102] feat: check code constraints --- src/controllers/branch-controller.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index 5e65729..c5384e3 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -297,6 +297,14 @@ export class BranchController extends Controller { const { provinceId, districtId, subDistrictId, headOfficeId, bank, contact, code, ...rest } = body; + if (head && head.code.slice(0, -6) !== code) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Headoffice code not match with branch code", + "codeMismatch", + ); + } + const record = await prisma.$transaction( async (tx) => { const last = await tx.runningNo.upsert({ @@ -310,6 +318,18 @@ export class BranchController extends Controller { update: { value: { increment: 1 } }, }); + if (last.value === 1) { + const exist = await tx.branch.findFirst({ + where: { code: `${code?.toLocaleUpperCase()}${`${last.value - 1}`.padStart(6, "0")}` }, + }); + if (exist) + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Branch with same code already exists.", + "sameBranchCodeExists", + ); + } + return await tx.branch.create({ include: { province: true, From 4a563d7885e00edbcbdd87dc8a70e6b4e578f15b Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 8 Aug 2024 10:22:51 +0700 Subject: [PATCH 084/102] fix: condition always true --- src/controllers/branch-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index c5384e3..aa808dc 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -297,7 +297,7 @@ export class BranchController extends Controller { const { provinceId, districtId, subDistrictId, headOfficeId, bank, contact, code, ...rest } = body; - if (head && head.code.slice(0, -6) !== code) { + if (headOfficeId && head && head.code.slice(0, -6) !== code) { throw new HttpError( HttpStatus.BAD_REQUEST, "Headoffice code not match with branch code", From 78a0974f3662bc6d2debb537a13e2cff36c27207 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:27:48 +0700 Subject: [PATCH 085/102] fix: relation ordering --- src/controllers/customer-controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index 98b229c..fd67377 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -232,6 +232,7 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, + orderBy: { branchNo: "desc" }, }, createdBy: true, updatedBy: true, From a065df3b1fb8baf8142337957c9d46e471eb38ff Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 8 Aug 2024 13:29:35 +0700 Subject: [PATCH 086/102] refactor: change order --- src/controllers/customer-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index fd67377..5947bb8 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -232,7 +232,7 @@ export class CustomerController extends Controller { district: true, subDistrict: true, }, - orderBy: { branchNo: "desc" }, + orderBy: { branchNo: "asc" }, }, createdBy: true, updatedBy: true, From bcd765c71a12a63023c22cfb80bcaa6dfa9da727 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:48:41 +0700 Subject: [PATCH 087/102] fix: condition not cover --- src/controllers/branch-controller.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index aa808dc..e8fb801 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -330,6 +330,14 @@ export class BranchController extends Controller { ); } + if (last.value !== 1 && !headOfficeId) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Branch with same code already exists.", + "sameBranchCodeExists", + ); + } + return await tx.branch.create({ include: { province: true, From e0b3ed6383b9368d8ac955d5b2fb094eea240165 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 9 Aug 2024 09:44:51 +0700 Subject: [PATCH 088/102] feat!: code gen customer --- src/controllers/customer-branch-controller.ts | 13 ++++++++- src/controllers/customer-controller.ts | 28 +++++++++++++++---- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/controllers/customer-branch-controller.ts b/src/controllers/customer-branch-controller.ts index a9cf867..c6ed578 100644 --- a/src/controllers/customer-branch-controller.ts +++ b/src/controllers/customer-branch-controller.ts @@ -305,6 +305,17 @@ export class CustomerBranchController extends Controller { ); } + const last = await tx.runningNo.upsert({ + where: { + key: `CUSTOMER_${customer.code.slice(0, -6)}`, + }, + create: { + key: `CUSTOMER_${customer.code.slice(0, -6)}`, + value: 1, + }, + update: { value: { increment: 1 } }, + }); + return await tx.customerBranch.create({ include: { province: true, @@ -316,7 +327,7 @@ export class CustomerBranchController extends Controller { data: { ...rest, statusOrder: +(rest.status === "INACTIVE"), - code: `${customer.code}-${rest.branchNo.toString().padStart(2, "0")}`, + code: `${customer.code.slice(0, -6)}${`${last.value - 1}`.padStart(6, "0")}`, customer: { connect: { id: customerId } }, province: { connect: provinceId ? { id: provinceId } : undefined }, district: { connect: districtId ? { id: districtId } : undefined }, diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index 5947bb8..a091cfe 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -37,6 +37,8 @@ const MANAGE_ROLES = [ export type CustomerCreate = { registeredBranchId?: string; + code: string; + status?: Status; personName: string; personNameEN?: string; @@ -312,15 +314,29 @@ export class CustomerController extends Controller { async (tx) => { const last = await tx.runningNo.upsert({ where: { - key: `CUSTOMER_${body.customerType}`, + key: `CUSTOMER_${body.code.toLocaleUpperCase()}`, }, create: { - key: `CUSTOMER_${body.customerType}`, + key: `CUSTOMER_${body.code.toLocaleUpperCase()}`, value: 1, }, - update: { value: { increment: 1 } }, + update: { value: { increment: (body.customerBranch?.length || 0) + 1 } }, }); + body.code = body.code.toLocaleUpperCase(); + + const exist = await tx.customer.findFirst({ + where: { code: `${body.code}000000` }, + }); + + if (exist) { + throw new HttpError( + HttpStatus.BAD_REQUEST, + "Customer with same code already exists.", + "sameCustomerCodeExists", + ); + } + return await prisma.customer.create({ include: { branch: { @@ -336,13 +352,13 @@ export class CustomerController extends Controller { data: { ...payload, statusOrder: +(payload.status === "INACTIVE"), - code: `${last.key.slice(9)}${last.value.toString().padStart(6, "0")}`, + code: `${body.code}000000`, branch: { createMany: { data: - customerBranch?.map((v) => ({ + customerBranch?.map((v, i) => ({ ...v, - code: `${last.key.slice(9)}${last.value.toString().padStart(6, "0")}-${v.branchNo.toString().padStart(2, "0")}`, + code: `${body.code}${`${last.value - customerBranch?.length + i + 2}`.padStart(6, "0")}`, createdByUserId: req.user.sub, updatedByUserId: req.user.sub, })) || [], From c434b085bc80f5befcb7366e5f974e9752aa33d6 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 9 Aug 2024 09:51:36 +0700 Subject: [PATCH 089/102] refactor!: user code gen --- src/controllers/branch-user-controller.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/controllers/branch-user-controller.ts b/src/controllers/branch-user-controller.ts index 7fa62f4..231b768 100644 --- a/src/controllers/branch-user-controller.ts +++ b/src/controllers/branch-user-controller.ts @@ -28,12 +28,19 @@ async function userBranchCodeGen(branch: Branch, user: User[]) { const typ = usr.userType; + const mapTypeNo = { + USER: 1, + MESSENGER: 2, + DELEGATE: 3, + AGENCY: 4, + }[typ]; + const last = await tx.runningNo.upsert({ where: { - key: `BR_USR_${branch.code.slice(4).padEnd(3, "0")}${typ !== "USER" ? typ.charAt(0).toLocaleUpperCase() : ""}`, + key: `BR_USR_${branch.code}_${mapTypeNo}`, }, create: { - key: `BR_USR_${branch.code.slice(4).padEnd(3, "0")}${typ !== "USER" ? typ.charAt(0).toLocaleUpperCase() : ""}`, + key: `BR_USR_${branch.code}_${mapTypeNo}`, value: 1, }, update: { value: { increment: 1 } }, @@ -42,7 +49,7 @@ async function userBranchCodeGen(branch: Branch, user: User[]) { await tx.user.update({ where: { id: usr.id }, data: { - code: `${last.key.slice(7)}${last.value.toString().padStart(4, "0")}`, + code: mapTypeNo + `${last.value}`.padStart(6, "9"), }, }); } From 96298daab20250ca3ae03a0d412f0dc8609e7112 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 9 Aug 2024 09:57:08 +0700 Subject: [PATCH 090/102] fix: wrong create value --- src/controllers/customer-controller.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/customer-controller.ts b/src/controllers/customer-controller.ts index a091cfe..0efb941 100644 --- a/src/controllers/customer-controller.ts +++ b/src/controllers/customer-controller.ts @@ -318,9 +318,9 @@ export class CustomerController extends Controller { }, create: { key: `CUSTOMER_${body.code.toLocaleUpperCase()}`, - value: 1, + value: body.customerBranch?.length || 0, }, - update: { value: { increment: (body.customerBranch?.length || 0) + 1 } }, + update: { value: { increment: body.customerBranch?.length || 0 } }, }); body.code = body.code.toLocaleUpperCase(); @@ -358,7 +358,7 @@ export class CustomerController extends Controller { data: customerBranch?.map((v, i) => ({ ...v, - code: `${body.code}${`${last.value - customerBranch?.length + i + 2}`.padStart(6, "0")}`, + code: `${body.code}${`${last.value - customerBranch?.length + i + 1}`.padStart(6, "0")}`, createdByUserId: req.user.sub, updatedByUserId: req.user.sub, })) || [], From a677c6879dc581e48c386e19832dbf37bb6a0879 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Fri, 9 Aug 2024 11:40:56 +0700 Subject: [PATCH 091/102] refactor!: employee code gen --- src/controllers/employee-controller.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/employee-controller.ts b/src/controllers/employee-controller.ts index 4fcf5cb..ff26bf7 100644 --- a/src/controllers/employee-controller.ts +++ b/src/controllers/employee-controller.ts @@ -419,10 +419,10 @@ export class EmployeeController extends Controller { async (tx) => { const last = await tx.runningNo.upsert({ where: { - key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`, + key: `EMPLOYEE_${customerBranch.customer.code.slice(0, -6)}${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}`, }, create: { - key: `EMPLOYEE_${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}`, + key: `EMPLOYEE_${customerBranch.customer.code.slice(0, -6)}${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}`, value: 1, }, update: { value: { increment: 1 } }, @@ -446,7 +446,7 @@ export class EmployeeController extends Controller { data: { ...rest, statusOrder: +(rest.status === "INACTIVE"), - code: `${customerBranch.customer.code}-${customerBranch.branchNo.toString().padStart(2, "0")}-${new Date().getFullYear().toString().slice(-2).padStart(2, "0")}${last.value.toString().padStart(4, "0")}`, + code: `${customerBranch.customer.code.slice(0, -6)}${`${new Date().getFullYear()}`.slice(-2).padStart(2, "0")}${`${last.value}`.padStart(7, "0")}`, employeeWork: { createMany: { data: employeeWork || [], From ab08492304b2c66ecf1c2d211e05e071216202e9 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 13 Aug 2024 17:07:05 +0700 Subject: [PATCH 092/102] feat: now system do not show system role --- src/controllers/user-controller.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/controllers/user-controller.ts b/src/controllers/user-controller.ts index e2b8e14..f4b0cca 100644 --- a/src/controllers/user-controller.ts +++ b/src/controllers/user-controller.ts @@ -201,6 +201,9 @@ export class UserController extends Controller { { email: { contains: query }, zipCode, userType }, { telephoneNo: { contains: query }, zipCode, userType }, ], + AND: { + userRole: { not: "system" }, + }, } satisfies Prisma.UserWhereInput; const [result, total] = await prisma.$transaction([ From 4be62e6bd2e3473664897a615a9c78b356c46dcb Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 13 Aug 2024 20:25:32 +0700 Subject: [PATCH 093/102] refactor: filter out system role --- src/controllers/keycloak-controller.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/controllers/keycloak-controller.ts b/src/controllers/keycloak-controller.ts index 6b827c8..52eee67 100644 --- a/src/controllers/keycloak-controller.ts +++ b/src/controllers/keycloak-controller.ts @@ -42,7 +42,9 @@ export class KeycloakController extends Controller { if (Array.isArray(role)) return role.filter( (a) => - !["uma_authorization", "offline_access", "default-roles"].some((b) => a.name.includes(b)), + !["uma_authorization", "offline_access", "default-roles", "system"].some((b) => + a.name.includes(b), + ), ); throw new Error("Failed. Cannot get role."); } From 20cf11f988ed08de86b7a10da73b90d03c50cea2 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Tue, 13 Aug 2024 20:26:32 +0700 Subject: [PATCH 094/102] fix: delete unused branch cause error new branch same code --- src/controllers/branch-controller.ts | 42 ++++++++++++++++++---------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index e8fb801..84f29d1 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -561,22 +561,34 @@ export class BranchController extends Controller { throw new HttpError(HttpStatus.FORBIDDEN, "Branch is in used.", "branchInUsed"); } - await minio.removeObject(MINIO_BUCKET, lineImageLoc(branchId), { - forceDelete: true, - }); - await minio.removeObject(MINIO_BUCKET, branchImageLoc(branchId), { - forceDelete: true, - }); + return await prisma.$transaction(async (tx) => { + const data = await tx.branch.delete({ + include: { + province: true, + district: true, + subDistrict: true, + createdBy: true, + updatedBy: true, + }, + where: { id: branchId }, + }); - return await prisma.branch.delete({ - include: { - province: true, - district: true, - subDistrict: true, - createdBy: true, - updatedBy: true, - }, - where: { id: branchId }, + if (record.isHeadOffice) { + await tx.runningNo.delete({ + where: { + key: `MAIN_BRANCH_${record.code.slice(0, -6)}`, + }, + }); + } + + await minio.removeObject(MINIO_BUCKET, lineImageLoc(branchId), { + forceDelete: true, + }); + await minio.removeObject(MINIO_BUCKET, branchImageLoc(branchId), { + forceDelete: true, + }); + + return data; }); } From b89b9dce346d7b8ed9de5b00271069834f3ecfbb Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:45:41 +0700 Subject: [PATCH 095/102] feat: add endpoint for get branch admin --- src/controllers/branch-user-controller.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/controllers/branch-user-controller.ts b/src/controllers/branch-user-controller.ts index 231b768..a6e9e80 100644 --- a/src/controllers/branch-user-controller.ts +++ b/src/controllers/branch-user-controller.ts @@ -58,6 +58,23 @@ async function userBranchCodeGen(branch: Branch, user: User[]) { ); } +@Route("api/v1/branch/{branchId}/admin") +@Tags("Branch User") +export class BranchAdminUserController extends Controller { + @Get() + @Security("keycloak") + async getBranchAdmin(@Path() branchId: string) { + return await prisma.user.findFirst({ + where: { + branch: { + some: { branchId }, + }, + userRole: "branch_admin", + }, + }); + } +} + @Route("api/v1/branch/{branchId}/user") @Tags("Branch User") export class BranchUserController extends Controller { From ebba5cff0368ee05f0f911a5d41ff048a313682f Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:46:05 +0700 Subject: [PATCH 096/102] refactor: only filter log api --- src/middlewares/morgan.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/middlewares/morgan.ts b/src/middlewares/morgan.ts index cca1096..a3e71c4 100644 --- a/src/middlewares/morgan.ts +++ b/src/middlewares/morgan.ts @@ -28,6 +28,8 @@ const logFormat = `{ function logMessageHandler(message: string) { const data = JSON.parse(message.trim()); + if (!(data.requestUrl as string).startsWith("/api/")) return; + const level = LOG_LEVEL_MAP[process.env.LOG_LEVEL ?? "info"] || 1; const status = +data.responseStatus; @@ -35,9 +37,9 @@ function logMessageHandler(message: string) { if (level === 2 && status < 400) return; if (level === 3 && status < 200) return; - if (status >= 500) return logger.error("HTTP request received", JSON.parse(message.trim())); - if (status >= 400) return logger.warning("HTTP request received", JSON.parse(message.trim())); - return logger.info("HTTP request received", JSON.parse(message.trim())); + if (status >= 500) return logger.error("HTTP request received", data); + if (status >= 400) return logger.warning("HTTP request received", data); + return logger.info("HTTP request received", data); } morgan.token("log-data", (req: express.Request) => { From 3aa405db7b94377a6bdf902850c8a1c6063af928 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 14 Aug 2024 14:03:25 +0700 Subject: [PATCH 097/102] fix: update branch status when assign user to branch --- src/controllers/user-controller.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/controllers/user-controller.ts b/src/controllers/user-controller.ts index f4b0cca..0ac3604 100644 --- a/src/controllers/user-controller.ts +++ b/src/controllers/user-controller.ts @@ -560,6 +560,13 @@ export class UserController extends Controller { branchId: v, })), }), + prisma.branch.updateMany({ + where: { + id: { in: Array.isArray(branchId) ? branchId : [branchId] }, + status: "CREATED", + }, + data: { status: "ACTIVE" }, + }), ]); if (branch[0]?.id !== user.branch[0]?.id) { From 5b75fe46b0d9bae2afe8134473d58a0d3c28bc58 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 14 Aug 2024 20:16:40 +0700 Subject: [PATCH 098/102] fix: typo 9 (should be 0) --- src/controllers/branch-user-controller.ts | 2 +- src/controllers/user-controller.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/branch-user-controller.ts b/src/controllers/branch-user-controller.ts index a6e9e80..bb63ea6 100644 --- a/src/controllers/branch-user-controller.ts +++ b/src/controllers/branch-user-controller.ts @@ -49,7 +49,7 @@ async function userBranchCodeGen(branch: Branch, user: User[]) { await tx.user.update({ where: { id: usr.id }, data: { - code: mapTypeNo + `${last.value}`.padStart(6, "9"), + code: mapTypeNo + `${last.value}`.padStart(6, "0"), }, }); } diff --git a/src/controllers/user-controller.ts b/src/controllers/user-controller.ts index 0ac3604..391df2e 100644 --- a/src/controllers/user-controller.ts +++ b/src/controllers/user-controller.ts @@ -145,7 +145,7 @@ async function userBranchCodeGen(user: User, branch: Branch) { return await tx.user.update({ where: { id: user.id }, data: { - code: mapTypeNo + `${last.value}`.padStart(6, "9"), + code: mapTypeNo + `${last.value}`.padStart(6, "0"), }, }); }, From cb0ac4f0be6cc28b2c7030d0a2ca2edb6254310e Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 14 Aug 2024 20:32:24 +0700 Subject: [PATCH 099/102] fix: wrong condition --- src/controllers/user-controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/controllers/user-controller.ts b/src/controllers/user-controller.ts index 391df2e..c3878ab 100644 --- a/src/controllers/user-controller.ts +++ b/src/controllers/user-controller.ts @@ -569,7 +569,7 @@ export class UserController extends Controller { }), ]); - if (branch[0]?.id !== user.branch[0]?.id) { + if (branch[0]?.id !== user.branch[0]?.branchId) { const updated = await userBranchCodeGen(user, branch[0]); record.code = updated.code; } From d15273de1b9b6c3df82e3823a0aeffc26324a7cd Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Wed, 14 Aug 2024 20:40:31 +0700 Subject: [PATCH 100/102] fix: wrong field used --- src/controllers/branch-controller.ts | 2 +- src/controllers/product/product-controller.ts | 2 +- src/controllers/service/service-controller.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/branch-controller.ts b/src/controllers/branch-controller.ts index 84f29d1..055fbfa 100644 --- a/src/controllers/branch-controller.ts +++ b/src/controllers/branch-controller.ts @@ -535,7 +535,7 @@ export class BranchController extends Controller { subDistrict: true, createdBy: true, updatedBy: true, - user: { where: { id: req.user.sub } }, + user: { where: { userId: req.user.sub } }, }, where: { id: branchId }, }); diff --git a/src/controllers/product/product-controller.ts b/src/controllers/product/product-controller.ts index c2e6e88..cd309ca 100644 --- a/src/controllers/product/product-controller.ts +++ b/src/controllers/product/product-controller.ts @@ -200,7 +200,7 @@ export class ProductController extends Controller { where: { id: body.productTypeId }, }), prisma.branch.findFirst({ - include: { user: { where: { id: req.user.sub } } }, + include: { user: { where: { userId: req.user.sub } } }, where: { id: body.registeredBranchId }, }), ]); diff --git a/src/controllers/service/service-controller.ts b/src/controllers/service/service-controller.ts index 2946add..feb7627 100644 --- a/src/controllers/service/service-controller.ts +++ b/src/controllers/service/service-controller.ts @@ -244,7 +244,7 @@ export class ServiceController extends Controller { where: { id: body.productTypeId }, }), prisma.branch.findFirst({ - include: { user: { where: { id: req.user.sub } } }, + include: { user: { where: { userId: req.user.sub } } }, where: { id: body.registeredBranchId }, }), ]); From 9441c713a27dcc1cd96e19ec3385659b59d16b55 Mon Sep 17 00:00:00 2001 From: Methapon2001 <61303214+Methapon2001@users.noreply.github.com> Date: Thu, 15 Aug 2024 09:26:21 +0700 Subject: [PATCH 101/102] feat: add field middleName and name prefix (optional) --- .../20240815021817_add_field/migration.sql | 4 ++++ prisma/schema.prisma | 17 ++++++++++------- src/controllers/user-controller.ts | 6 ++++++ 3 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 prisma/migrations/20240815021817_add_field/migration.sql diff --git a/prisma/migrations/20240815021817_add_field/migration.sql b/prisma/migrations/20240815021817_add_field/migration.sql new file mode 100644 index 0000000..8e7348b --- /dev/null +++ b/prisma/migrations/20240815021817_add_field/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "middleName" TEXT, +ADD COLUMN "middleNameEN" TEXT, +ADD COLUMN "namePrefix" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 61be7da..5e083d2 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -293,13 +293,16 @@ enum UserType { model User { id String @id @default(uuid()) - code String? - firstName String - firstNameEN String - lastName String - lastNameEN String - username String - gender String + code String? + namePrefix String? + firstName String + firstNameEN String + middleName String? + middleNameEN String? + lastName String + lastNameEN String + username String + gender String address String addressEN String diff --git a/src/controllers/user-controller.ts b/src/controllers/user-controller.ts index c3878ab..30b3130 100644 --- a/src/controllers/user-controller.ts +++ b/src/controllers/user-controller.ts @@ -43,8 +43,11 @@ type UserCreate = { username: string; + namePrefix?: string | null; firstName: string; firstNameEN: string; + middleName?: string | null; + middleNameEN?: string | null; lastName: string; lastNameEN: string; gender: string; @@ -85,8 +88,11 @@ type UserUpdate = { userType?: UserType; userRole?: string; + namePrefix?: string | null; firstName?: string; firstNameEN?: string; + middleName?: string | null; + middleNameEN?: string | null; lastName?: string; lastNameEN?: string; gender?: string; From 379b7e528640b5e5a6e5225e50eb19ae4da97158 Mon Sep 17 00:00:00 2001 From: Methapon Metanipat Date: Fri, 16 Aug 2024 10:48:56 +0700 Subject: [PATCH 102/102] feat: add status filter --- src/controllers/user-controller.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/controllers/user-controller.ts b/src/controllers/user-controller.ts index 30b3130..c6cc1ae 100644 --- a/src/controllers/user-controller.ts +++ b/src/controllers/user-controller.ts @@ -197,15 +197,24 @@ export class UserController extends Controller { @Query() query: string = "", @Query() page: number = 1, @Query() pageSize: number = 30, + @Query() status?: Status, ) { + const filterStatus = (val?: Status) => { + if (!val) return {}; + + return val !== Status.CREATED && val !== Status.ACTIVE + ? { status: val } + : { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] }; + }; + const where = { OR: [ - { firstName: { contains: query }, zipCode, userType }, - { firstNameEN: { contains: query }, zipCode, userType }, - { lastName: { contains: query }, zipCode, userType }, - { lastNameEN: { contains: query }, zipCode, userType }, - { email: { contains: query }, zipCode, userType }, - { telephoneNo: { contains: query }, zipCode, userType }, + { firstName: { contains: query }, zipCode, userType, ...filterStatus(status) }, + { firstNameEN: { contains: query }, zipCode, userType, ...filterStatus(status) }, + { lastName: { contains: query }, zipCode, userType, ...filterStatus(status) }, + { lastNameEN: { contains: query }, zipCode, userType, ...filterStatus(status) }, + { email: { contains: query }, zipCode, userType, ...filterStatus(status) }, + { telephoneNo: { contains: query }, zipCode, userType, ...filterStatus(status) }, ], AND: { userRole: { not: "system" },