diff --git a/package-lock.json b/package-lock.json index a96df4a..5f83233 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,32 +1,37 @@ { - "name": "proj", + "name": "saha", "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "proj", + "name": "saha", "version": "0.0.1", "license": "UNLICENSED", "dependencies": { "@nestjs/bullmq": "^11.0.3", - "@nestjs/common": "^11.1.3", + "@nestjs/common": "^11.1.5", "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.3", + "@nestjs/core": "^11.1.5", "@nestjs/mapped-types": "^2.1.0", "@nestjs/mongoose": "^11.0.3", - "@nestjs/platform-express": "^11.1.3", + "@nestjs/passport": "^11.0.5", + "@nestjs/platform-express": "^11.1.5", "@nestjs/typeorm": "^11.0.0", + "axios": "^1.11.0", "bullmq": "^5.56.7", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "glob": "^11.0.3", "jsonwebtoken": "^9.0.2", + "jwks-rsa": "^3.2.0", "libphonenumber-js": "^1.12.9", "mongodb": "^6.17.0", "mongoose": "^8.16.3", "mongoose-paginate-v2": "^1.9.1", "nestjs-paginate": "^12.5.1", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", "pg": "^8.11.3", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", @@ -2691,9 +2696,9 @@ } }, "node_modules/@nestjs/common": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.3.tgz", - "integrity": "sha512-ogEK+GriWodIwCw6buQ1rpcH4Kx+G7YQ9EwuPySI3rS05pSdtQ++UhucjusSI9apNidv+QURBztJkRecwwJQXg==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.5.tgz", + "integrity": "sha512-DQpWdr3ShO0BHWkHl3I4W/jR6R3pDtxyBlmrpTuZF+PXxQyBXNvsUne0Wyo6QHPEDi+pAz9XchBFoKbqOhcdTg==", "dependencies": { "file-type": "21.0.0", "iterare": "1.2.1", @@ -2746,9 +2751,9 @@ } }, "node_modules/@nestjs/core": { - "version": "11.1.3", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.3.tgz", - "integrity": "sha512-5lTni0TCh8x7bXETRD57pQFnKnEg1T6M+VLE7wAmyQRIecKQU+2inRGZD+A4v2DC1I04eA0WffP0GKLxjOKlzw==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.5.tgz", + "integrity": "sha512-Qr25MEY9t8VsMETy7eXQ0cNXqu0lzuFrrTr+f+1G57ABCtV5Pogm7n9bF71OU2bnkDD32Bi4hQLeFR90cku3Tw==", "hasInstallScript": true, "dependencies": { "@nuxt/opencollective": "0.4.1", @@ -2815,6 +2820,15 @@ "rxjs": "^7.0.0" } }, + "node_modules/@nestjs/passport": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-11.0.5.tgz", + "integrity": "sha512-ulQX6mbjlws92PIM15Naes4F4p2JoxGnIJuUsdXQPT+Oo2sqQmENEZXM7eYuimocfHnKlcfZOuyzbA33LwUlOQ==", + "peerDependencies": { + "@nestjs/common": "^10.0.0 || ^11.0.0", + "passport": "^0.5.0 || ^0.6.0 || ^0.7.0" + } + }, "node_modules/@nestjs/platform-express": { "version": "11.1.5", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.5.tgz", @@ -3539,7 +3553,6 @@ "version": "1.19.6", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "dev": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -3549,7 +3562,6 @@ "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -3618,8 +3630,7 @@ "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "dev": true + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", @@ -3665,7 +3676,6 @@ "version": "9.0.10", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", - "dev": true, "dependencies": { "@types/ms": "*", "@types/node": "*" @@ -3680,20 +3690,17 @@ "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "dev": true + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==" }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "dev": true + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" }, "node_modules/@types/node": { "version": "24.0.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.13.tgz", "integrity": "sha512-Qm9OYVOFHFYg3wJoTSrz80hoec5Lia/dPp84do3X7dZvLikQvM1YpmvTBEdIr/e+U8HTkFjLHLnl78K/qjf+jQ==", - "devOptional": true, "dependencies": { "undici-types": "~7.8.0" } @@ -3701,20 +3708,17 @@ "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "dev": true + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==" }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", - "dev": true + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, "node_modules/@types/send": { "version": "0.17.5", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", - "dev": true, "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -3724,7 +3728,6 @@ "version": "1.15.8", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", - "dev": true, "dependencies": { "@types/http-errors": "*", "@types/node": "*", @@ -5249,8 +5252,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/atomic-sleep": { "version": "1.0.0", @@ -5286,6 +5288,16 @@ "fastq": "^1.17.1" } }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/b4a": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", @@ -6000,7 +6012,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -6311,7 +6322,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -6539,7 +6549,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -7349,6 +7358,25 @@ "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -7410,7 +7438,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -7435,7 +7462,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -7444,7 +7470,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -8960,6 +8985,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9093,6 +9126,44 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/jwks-rsa": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.2.0.tgz", + "integrity": "sha512-PwchfHcQK/5PSydeKCs1ylNym0w/SSv8a62DgHJ//7x2ZclCoinlsjAfDxAAbpoTPybOum/Jgy+vkvMmKz89Ww==", + "dependencies": { + "@types/express": "^4.17.20", + "@types/jsonwebtoken": "^9.0.4", + "debug": "^4.3.4", + "jose": "^4.15.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.2.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", + "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express-serve-static-core": { + "version": "4.19.6", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz", + "integrity": "sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -9201,6 +9272,11 @@ ], "peer": true }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -9254,6 +9330,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, "node_modules/lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -9348,6 +9429,31 @@ "yallist": "^3.0.2" } }, + "node_modules/lru-memoizer": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.3.0.tgz", + "integrity": "sha512-GXn7gyHAMhO13WSKrIiNfztwxodVsP8IoZ3XfrJV4yH2x0/OeTO/FIaAHTY5YekdGgW94njfuKmyyt1E0mR6Ug==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "6.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/luxon": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.1.tgz", @@ -10151,6 +10257,40 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz", + "integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-jwt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.1.tgz", + "integrity": "sha512-UCKMDYhNuGOBE9/9Ycuoyh7vP6jpeTp/+sfMJl7nLff/t6dps+iaeE0hhNkKN8/HZHcJ7lCdOyDxHdDoxoSvdQ==", + "dependencies": { + "jsonwebtoken": "^9.0.0", + "passport-strategy": "^1.0.0" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -10217,6 +10357,11 @@ "node": ">=8" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/peek-readable": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", @@ -10597,6 +10742,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -12589,8 +12739,7 @@ "node_modules/undici-types": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", - "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", - "devOptional": true + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==" }, "node_modules/universalify": { "version": "2.0.1", @@ -12687,6 +12836,14 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", diff --git a/package.json b/package.json index 647a454..4fa97a8 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "proj", + "name": "saha", "version": "0.0.1", "description": "", "author": "", @@ -21,23 +21,28 @@ }, "dependencies": { "@nestjs/bullmq": "^11.0.3", - "@nestjs/common": "^11.1.3", + "@nestjs/common": "^11.1.5", "@nestjs/config": "^4.0.2", - "@nestjs/core": "^11.1.3", + "@nestjs/core": "^11.1.5", "@nestjs/mapped-types": "^2.1.0", "@nestjs/mongoose": "^11.0.3", - "@nestjs/platform-express": "^11.1.3", + "@nestjs/passport": "^11.0.5", + "@nestjs/platform-express": "^11.1.5", "@nestjs/typeorm": "^11.0.0", + "axios": "^1.11.0", "bullmq": "^5.56.7", "class-transformer": "^0.5.1", "class-validator": "^0.14.2", "glob": "^11.0.3", "jsonwebtoken": "^9.0.2", + "jwks-rsa": "^3.2.0", "libphonenumber-js": "^1.12.9", "mongodb": "^6.17.0", "mongoose": "^8.16.3", "mongoose-paginate-v2": "^1.9.1", "nestjs-paginate": "^12.5.1", + "passport": "^0.7.0", + "passport-jwt": "^4.0.1", "pg": "^8.11.3", "reflect-metadata": "^0.2.2", "rxjs": "^7.8.2", diff --git a/src/answer/answer.controller.ts b/src/answer/answer.controller.ts index 53504f8..e1676a5 100644 --- a/src/answer/answer.controller.ts +++ b/src/answer/answer.controller.ts @@ -5,11 +5,11 @@ import { UpdateAnswerDto } from './dto/update-answer.dto'; import { Answer } from './entity/answer.entity'; import { AuthRequest } from '../middleware/jwtMiddleware'; -@Controller('answers') +@Controller('answer') export class AnswerController { constructor(private readonly answerService: AnswerService) {} - @Post() + @Post('create') @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) async create(@Body() body: CreateAnswerDto, @Request() req: AuthRequest): Promise { return this.answerService.create(body, req.user); diff --git a/src/answer/answer.module.ts b/src/answer/answer.module.ts index a137f0f..1836ca5 100644 --- a/src/answer/answer.module.ts +++ b/src/answer/answer.module.ts @@ -3,10 +3,12 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { AnswerService } from './answer.service'; import { AnswerController } from './answer.controller'; import { Answer } from './entity/answer.entity'; +import { Question } from '../question/entity/question.entity'; +import { Participant } from '../participant/entity/participant.entity'; @Module({ imports: [ - TypeOrmModule.forFeature([Answer]), + TypeOrmModule.forFeature([Answer, Question, Participant]), ], controllers: [AnswerController], providers: [AnswerService], diff --git a/src/answer/answer.service.ts b/src/answer/answer.service.ts index e9a57d7..4cbcd28 100644 --- a/src/answer/answer.service.ts +++ b/src/answer/answer.service.ts @@ -5,22 +5,37 @@ import { Answer } from './entity/answer.entity'; import { CreateAnswerDto } from './dto/create-answer.dto'; import { UpdateAnswerDto } from './dto/update-answer.dto'; import { AuthRequest } from '../middleware/jwtMiddleware'; +import { Question } from '../question/entity/question.entity'; +import { Participant } from '../participant/entity/participant.entity'; @Injectable() export class AnswerService { constructor( @InjectRepository(Answer) private readonly answerRepository: Repository, + @InjectRepository(Question) + private readonly questionRepository: Repository, + @InjectRepository(Participant) + private readonly participantRepository: Repository, ) {} async create(data: CreateAnswerDto, user: AuthRequest['user']): Promise { + const question = await this.questionRepository.findOne({ where: { id: data.questionId } }); + if (!question) { + throw new NotFoundException('Question with this ID not found'); + } + + const participant = await this.participantRepository.findOne({ where: { userId: user.sub } }); + if (!participant) { + throw new NotFoundException('Participant with this ID not found'); + } + const answer = this.answerRepository.create({ ...data, - participantId: user.sub, + participantId: participant.id, }); return await this.answerRepository.save(answer); } - async findAll(page = 1, limit = 10): Promise<{ docs: Answer[]; totalDocs: number; @@ -67,17 +82,13 @@ export class AnswerService { } } - async update(id: string, data: UpdateAnswerDto): Promise { - try { - await this.answerRepository.update({ id }, { ...data, updatedAt: new Date() }); - const updatedAnswer = await this.answerRepository.findOne({ where: { id } }); - if (!updatedAnswer) { - throw new NotFoundException(`Answer with ID "${id}" not found`); - } - return updatedAnswer; - } catch (error) { - throw new Error(`Failed to update answer: ${error.message}`); + async update(id: string, data: UpdateAnswerDto): Promise { + const result = await this.answerRepository.update({ id }, { ...data, updatedAt: new Date() }); + if (result.affected === 0) { + throw new NotFoundException(`Answer with ID "${id}" not found`); } + const updatedAnswer = await this.answerRepository.findOne({ where: { id } }); + return updatedAnswer!; } async remove(id: string): Promise { diff --git a/src/answer/dto/create-answer.dto.ts b/src/answer/dto/create-answer.dto.ts index b8315d6..1942847 100644 --- a/src/answer/dto/create-answer.dto.ts +++ b/src/answer/dto/create-answer.dto.ts @@ -2,8 +2,8 @@ import { IsString, IsUUID } from 'class-validator'; import { BaseDto } from '../../_core/dto/base.dto'; export class CreateAnswerDto extends BaseDto { - @IsUUID('4') - participantId: string; + // @IsUUID('4') + // participantId: string; @IsUUID('4') questionId: string; diff --git a/src/app.module.ts b/src/app.module.ts index 5706b2c..bc9576c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,7 +1,11 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; + import { AppController } from './app.controller'; import { AppService } from './app.service'; +import { typeOrmConfig } from './config/database.config'; + import { ParticipantModule } from './participant/participant.module'; import { QuestionModule } from './question/question.module'; import { AnswerModule } from './answer/answer.module'; @@ -13,16 +17,15 @@ import { FormSectionModule } from './formSection/formSection.module'; import { OptionModule } from './option/option.module'; import { ParticipanGrouptModule } from './participantGroup/participantGroup.module'; import { RealmModule } from './realm/realm.module'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { typeOrmConfig } from './config/database.config'; import { FormResultWorkerModule } from './formResult/formResultWorker.module'; +import { AuthModule } from './auth/auth.module'; @Module({ imports: [ - TypeOrmModule.forRoot(typeOrmConfig), ConfigModule.forRoot({ - isGlobal: true, // Makes ConfigModule available globally + isGlobal: true, }), + TypeOrmModule.forRoot(typeOrmConfig), ParticipantModule, QuestionModule, AnswerModule, @@ -35,8 +38,9 @@ import { FormResultWorkerModule } from './formResult/formResultWorker.module'; ParticipanGrouptModule, RealmModule, FormResultWorkerModule, + AuthModule, ], controllers: [AppController], providers: [AppService], }) -export class AppModule {} \ No newline at end of file +export class AppModule {} diff --git a/src/attachment/attachment.controller.ts b/src/attachment/attachment.controller.ts index 2e08fcb..83d4cac 100644 --- a/src/attachment/attachment.controller.ts +++ b/src/attachment/attachment.controller.ts @@ -5,11 +5,11 @@ import { UpdateAttachmentDto } from './dto/update_attachment.dto'; import { Attachment } from './entity/attachment.entity'; import { AuthRequest } from '../middleware/jwtMiddleware'; -@Controller('attachments') +@Controller('attachment') export class AttachmentController { constructor(private readonly attachmentService: AttachmentService) {} - @Post() + @Post('create') @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) async create(@Body() body: CreateAttachmentDto, @Request() req: AuthRequest): Promise { return this.attachmentService.create(body, req.user); diff --git a/src/attachment/attachment.service.ts b/src/attachment/attachment.service.ts index 2393faa..6e5ef52 100644 --- a/src/attachment/attachment.service.ts +++ b/src/attachment/attachment.service.ts @@ -70,19 +70,16 @@ export class AttachmentService { } } - async update(id: string, data: UpdateAttachmentDto): Promise { - try { - await this.attachmentRepository.update({ id }, { ...data, updatedAt: new Date() }); - const updatedAttachment = await this.attachmentRepository.findOne({ where: { id } }); - if (!updatedAttachment) { - throw new NotFoundException(`Attachment with ID "${id}" not found`); - } - return updatedAttachment; - } catch (error) { - throw new Error(`Failed to update attachment: ${error.message}`); + async update(id: string, data: UpdateAttachmentDto): Promise { + const result = await this.attachmentRepository.update({ id }, { ...data, updatedAt: new Date() }); + if (result.affected === 0) { + throw new NotFoundException(`Attachment with ID "${id}" not found`); } + const updatedAttachment = await this.attachmentRepository.findOne({ where: { id } }); + return updatedAttachment!; } + async remove(id: string): Promise { try { const result = await this.attachmentRepository.delete({ id }); diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts new file mode 100644 index 0000000..1697b22 --- /dev/null +++ b/src/auth/auth.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { PassportModule } from '@nestjs/passport'; +import { JwtStrategy } from './jwt.strategy'; + +@Module({ + imports: [PassportModule], + providers: [JwtStrategy], +}) +export class AuthModule {} diff --git a/src/auth/jwt-auth.guard.ts b/src/auth/jwt-auth.guard.ts new file mode 100644 index 0000000..2155290 --- /dev/null +++ b/src/auth/jwt-auth.guard.ts @@ -0,0 +1,5 @@ +import { Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class JwtAuthGuard extends AuthGuard('jwt') {} diff --git a/src/auth/jwt.strategy.ts b/src/auth/jwt.strategy.ts new file mode 100644 index 0000000..4abfeff --- /dev/null +++ b/src/auth/jwt.strategy.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@nestjs/common'; +import { PassportStrategy } from '@nestjs/passport'; +import { ExtractJwt, Strategy } from 'passport-jwt'; +import * as jwksRsa from 'jwks-rsa'; + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor() { + super({ + // Extract the Bearer token from Authorization header + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + secretOrKeyProvider: jwksRsa.passportJwtSecret({ + cache: true, + rateLimit: true, + jwksRequestsPerMinute: 10, + jwksUri: 'http://localhost:8080/realms/testRealm/protocol/openid-connect/certs', + }), + audience: 'saha', // Keycloak client ID + issuer: 'http://localhost:8080/realms/testRealm', + algorithms: ['RS256'], + }); + } + + async validate(payload: any) { + return { + userId: payload.sub, + username: payload.preferred_username, + email: payload.email, + roles: payload.realm_access?.roles || [], + }; + } +} diff --git a/src/form/dto/create-form.dto.ts b/src/form/dto/create-form.dto.ts index 3dabaef..6c98a80 100644 --- a/src/form/dto/create-form.dto.ts +++ b/src/form/dto/create-form.dto.ts @@ -9,7 +9,8 @@ export class CreateFormDto extends BaseDto { description: string; @IsBoolean() - isEnded: boolean; + @IsOptional() + isEnded?: boolean; @IsOptional() @IsArray() diff --git a/src/form/form.controller.ts b/src/form/form.controller.ts index abe6af9..d732924 100644 --- a/src/form/form.controller.ts +++ b/src/form/form.controller.ts @@ -4,11 +4,11 @@ import { CreateFormDto } from './dto/create-form.dto'; import { UpdateFormDto } from './dto/update-form.dto'; import { Form } from './entity/form.entity'; -@Controller('forms') +@Controller('form') export class FormController { constructor(private readonly formService: FormService) {} - @Post() + @Post('create') @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) async create(@Body() body: CreateFormDto): Promise
{ return this.formService.create(body); diff --git a/src/form/form.service.ts b/src/form/form.service.ts index 42fe42d..4654904 100644 --- a/src/form/form.service.ts +++ b/src/form/form.service.ts @@ -18,14 +18,16 @@ export class FormService { const form = this.formRepo.create({ ...data, }); + const savedForm = await this.formRepo.save(form); await this.formResultService.create({ + formId: savedForm.id, numParticipants: 0, numQuestions: 0, numAnswers: 0, numComplete: 0, opinions: [], }); // Create corresponding FormResult instance and schedule statistics - return await this.formRepo.save(form); + return savedForm; } async findAll( @@ -101,6 +103,11 @@ export class FormService { if (result.affected === 0) { throw new NotFoundException(`Form with ID "${id}" not found`); } + const updatedForm = await this.findById(id); + if (updatedForm && data.isEnded !== undefined) { + if (updatedForm.isEnded) + await this.formResultService.scheduleFormStatistics(id); // Trigger scheduler update + } return await this.findById(id); } diff --git a/src/formPage/formPage.controller.ts b/src/formPage/formPage.controller.ts index 740f7ff..e1bc190 100644 --- a/src/formPage/formPage.controller.ts +++ b/src/formPage/formPage.controller.ts @@ -4,11 +4,11 @@ import { CreateFormPageDto } from './dto/create-formPage.dto'; import { UpdateFormPageDto } from './dto/update-formPage.dto'; import { FormPage } from './entity/formPage.entity'; -@Controller('formPages') +@Controller('formPage') export class FormPageController { constructor(private readonly formPageService: FormPageService) {} - @Post() + @Post('create') @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) async create(@Body() body: CreateFormPageDto): Promise { return this.formPageService.create(body); diff --git a/src/formPage/formPage.service.ts b/src/formPage/formPage.service.ts index 852c852..a769ac8 100644 --- a/src/formPage/formPage.service.ts +++ b/src/formPage/formPage.service.ts @@ -72,7 +72,6 @@ export class FormPageService { { id }, { ...data, updatedAt: new Date() }, ); - if (result.affected === 0) { throw new NotFoundException(`FormPage with ID "${id}" not found`); } diff --git a/src/formResult/dto/create-formResult.dto.ts b/src/formResult/dto/create-formResult.dto.ts index 41fb22f..95e4e9b 100644 --- a/src/formResult/dto/create-formResult.dto.ts +++ b/src/formResult/dto/create-formResult.dto.ts @@ -1,4 +1,4 @@ -import { IsNumber, IsString, IsUUID, ValidateNested, IsArray } from 'class-validator'; +import { IsNumber, IsString, IsUUID, ValidateNested, IsArray, IsOptional } from 'class-validator'; import { Type } from 'class-transformer'; import { BaseDto } from '../../_core/dto/base.dto'; @@ -24,6 +24,9 @@ class CreateOpinionDto { // Main DTO for creating a FormResult export class CreateFormResultDto extends BaseDto { + @IsUUID('4') + formId: string; + @IsNumber() numParticipants: number; @@ -36,8 +39,9 @@ export class CreateFormResultDto extends BaseDto { @IsNumber() numComplete: number; + @IsOptional() @IsArray() @ValidateNested({ each: true }) @Type(() => CreateOpinionDto) - opinions: CreateOpinionDto[]; + opinions?: CreateOpinionDto[]; } \ No newline at end of file diff --git a/src/formResult/entity/formResult.entity.ts b/src/formResult/entity/formResult.entity.ts index 33310d8..71bf4a6 100644 --- a/src/formResult/entity/formResult.entity.ts +++ b/src/formResult/entity/formResult.entity.ts @@ -1,4 +1,4 @@ -import { Entity, Column } from 'typeorm'; +import { Entity, Column, Unique } from 'typeorm'; import { BaseEntity } from '../../_core/entity/_base.entity'; // Options class (embedded) @@ -22,6 +22,7 @@ export class Opinion { } @Entity() +@Unique(['formId']) export class FormResult extends BaseEntity { @Column({ type: 'uuid' }) formId: string; @@ -38,6 +39,6 @@ export class FormResult extends BaseEntity { @Column({ type: 'int', default: 0 }) numComplete: number; - @Column(() => Opinion) - opinions: Opinion[]; + @Column({ type: 'jsonb', nullable: true }) + opinions?: Opinion[]; } \ No newline at end of file diff --git a/src/formResult/formResult.controller.ts b/src/formResult/formResult.controller.ts index ec0d1f7..e4ccec7 100644 --- a/src/formResult/formResult.controller.ts +++ b/src/formResult/formResult.controller.ts @@ -8,7 +8,7 @@ import { FormResult } from './entity/formResult.entity'; export class FormResultController { constructor(private readonly formResultService: FormResultService) {} - @Post() + @Post('create') @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) async create(@Body() body: CreateFormResultDto): Promise { return this.formResultService.create(body); diff --git a/src/formResult/formResult.service.ts b/src/formResult/formResult.service.ts index c34c499..5b2db13 100644 --- a/src/formResult/formResult.service.ts +++ b/src/formResult/formResult.service.ts @@ -5,6 +5,7 @@ import { CreateFormResultDto } from './dto/create-formResult.dto'; import { UpdateFormResultDto } from './dto/update-formResult.dto'; import { Repository } from 'typeorm'; import { Queue } from 'bullmq'; +import { Form } from '../form/entity/form.entity'; @Injectable() export class FormResultService { @@ -13,6 +14,8 @@ export class FormResultService { constructor( @InjectRepository(FormResult) private readonly formResultRepo: Repository, + @InjectRepository(Form) + private readonly formRepo: Repository, ) { this.statisticsQueue = new Queue('form-statistics', { connection: { @@ -83,7 +86,6 @@ export class FormResultService { { id }, { ...data, updatedAt: new Date() }, ); - if (result.affected === 0) { throw new NotFoundException(`FormResult with ID "${id}" not found`); } @@ -106,6 +108,14 @@ export class FormResultService { } async scheduleFormStatistics(formId: string): Promise { + const form = await this.formRepo.findOne({ where: { id: formId } }); + if (!form) { + throw new NotFoundException('Form with this ID not found'); + } + if (form.isEnded) { + await this.statisticsQueue.remove(`form-statistics-${formId}`); + return; // Stop scheduler if form is ended + } await this.statisticsQueue.upsertJobScheduler( `form-statistics-${formId}`, { diff --git a/src/formResult/formResult.worker.ts b/src/formResult/formResult.worker.ts index 5039f16..2c33b62 100644 --- a/src/formResult/formResult.worker.ts +++ b/src/formResult/formResult.worker.ts @@ -66,26 +66,40 @@ export class FormResultWorker { const questionIds = formSections.flatMap(section => section.questionIds || []); const numQuestions = questionIds.length; + // Skip remaining calculations if no question IDs are found + if (!questionIds.length) { + await this.formResultRepo.update({ formId }, { + numQuestions: 0, + numParticipants: 0, + numAnswers: 0, + numComplete: 0, + opinions: [], + updatedAt: new Date(), + }); + return; + } + // 3. Compute numParticipants const numParticipantsResult = await this.answerRepository .createQueryBuilder('answer') .select('COUNT(DISTINCT answer.participantId)', 'count') - .where('answer.questionId IN (:...questionIds)', { questionIds: questionIds.length ? questionIds : ['none'] }) + .where('answer.questionId IN (:...questionIds)', { questionIds }) .getRawOne(); const numParticipants = numParticipantsResult ? Number(numParticipantsResult.count) : 0; // 4. Compute numAnswers const numAnswers = await this.answerRepository.count({ - where: { questionId: In(questionIds.length ? questionIds : ['none']) }, + where: { questionId: In(questionIds) }, }); // 5. Compute numComplete const subQuery = this.answerRepository .createQueryBuilder('answer') .select('answer.participantId') - .where('answer.questionId IN (:...questionIds)', { questionIds: questionIds.length ? questionIds : ['none'] }) + .where('answer.questionId IN (:...questionIds)', { questionIds }) .groupBy('answer.participantId') - .having('COUNT(DISTINCT answer.questionId) = :numQuestions', { numQuestions: numQuestions || 1 }); + .having('COUNT(DISTINCT answer.questionId) = :numQuestions', { numQuestions }); + const numCompleteParticipantsResult = await this.answerRepository.manager .createQueryBuilder() .select('COUNT(*)', 'count') @@ -96,7 +110,7 @@ export class FormResultWorker { // 6. Compute opinions with question options and their vote counts const questions = await this.questionRepository.find({ - where: { id: In(questionIds.length ? questionIds : ['none']) }, + where: { id: In(questionIds) }, relations: ['options'], }); @@ -108,7 +122,7 @@ export class FormResultWorker { .select('answer.value', 'value') .addSelect('COUNT(*)', 'count') .where('answer.questionId = :questionId', { questionId: question.id }) - .andWhere('answer.value IN (:...optionIds)', { optionIds: optionIds.length ? optionIds : ['none'] }) + .andWhere('answer.value IN (:...optionIds)', { optionIds }) .groupBy('answer.value') .getRawMany(); @@ -127,26 +141,15 @@ export class FormResultWorker { }), ); - // 7. Find or create FormResult - let formResult = await this.formResultRepo.findOne({ where: { formId } }); - if (!formResult) { - formResult = this.formResultRepo.create({ - formId, - numQuestions, - numParticipants, - numAnswers, - numComplete, - opinions, - }); - } else { - formResult.numQuestions = numQuestions; - formResult.numParticipants = numParticipants; - formResult.numAnswers = numAnswers; - formResult.numComplete = numComplete; - formResult.opinions = opinions; - } - // 8. Save - await this.formResultRepo.save(formResult); + // 7. Update existing FormResult tuple + await this.formResultRepo.update({ formId }, { + numQuestions, + numParticipants, + numAnswers, + numComplete, + opinions, + updatedAt: new Date(), + }); } -} \ No newline at end of file +} diff --git a/src/formSection/entity/formSection.entity.ts b/src/formSection/entity/formSection.entity.ts index 563075f..d82298c 100644 --- a/src/formSection/entity/formSection.entity.ts +++ b/src/formSection/entity/formSection.entity.ts @@ -30,6 +30,6 @@ export class FormSection extends BaseEntity { @Column('simple-array', { nullable: true }) questionIds: string[]; - @Column(() => DisplayCondition) // needs to be checked - displayCondition: DisplayCondition[]; + // @Column(() => DisplayCondition) // needs to be checked + // displayCondition: DisplayCondition[]; } \ No newline at end of file diff --git a/src/formSection/formSection.controller.ts b/src/formSection/formSection.controller.ts index 971b8c2..d2f13db 100644 --- a/src/formSection/formSection.controller.ts +++ b/src/formSection/formSection.controller.ts @@ -4,11 +4,11 @@ import { CreateFormSectionDto } from './dto/create-formSection.dto'; import { UpdateFormSectionDto } from './dto/update-formSection.dto'; import { FormSection } from './entity/formSection.entity'; -@Controller('formSections') +@Controller('formSection') export class FormSectionController { constructor(private readonly formSectionService: FormSectionService) {} - @Post() + @Post('create') @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) async create(@Body() body: CreateFormSectionDto): Promise { return this.formSectionService.create(body); diff --git a/src/formSection/formSection.service.ts b/src/formSection/formSection.service.ts index 3091fce..5e26354 100644 --- a/src/formSection/formSection.service.ts +++ b/src/formSection/formSection.service.ts @@ -68,17 +68,13 @@ export class FormSectionService { id: string, data: UpdateFormSectionDto, ): Promise { - // TypeORM's update method returns UpdateResult, not the entity itself. - // We update and then fetch the updated entity. const result = await this.formSectionRepo.update( { id }, { ...data, updatedAt: new Date() }, ); - if (result.affected === 0) { throw new NotFoundException(`FormSection with ID "${id}" not found`); } - // Fetch and return the updated entity to ensure all fields are fresh. return await this.findById(id); } diff --git a/src/option/option.controller.ts b/src/option/option.controller.ts index 7dfea06..83c1971 100644 --- a/src/option/option.controller.ts +++ b/src/option/option.controller.ts @@ -4,11 +4,11 @@ import { CreateOptionDto } from './dto/create-option.dto'; import { UpdateOptionDto } from './dto/update-option.dto'; import { Option } from './entity/option.entity'; -@Controller('options') +@Controller('option') export class OptionController { constructor(private readonly optionService: OptionService) {} - @Post() + @Post('create') @UsePipes(new ValidationPipe({ transform: true, whitelist: true })) async create(@Body() body: CreateOptionDto): Promise