1 Commits

48 changed files with 1048 additions and 6265 deletions

View File

@@ -0,0 +1,40 @@
# Split Serverless services (deploy order)
This repo is split into multiple Serverless configs so you can deploy smaller CloudFormation stacks instead of one huge stack.
## Config files
- `serverless.layers.yml`: Prisma layer stack (deploy once per stage)
- `serverless.host.yml`: Host + PQQ functions (owns the shared HTTP API)
- `serverless.admin.yml`: Minglar Admin functions (attaches routes to Host HTTP API)
- `serverless.user.yml`: User functions (attaches routes to Host HTTP API)
- `serverless.prepopulate.yml`: Prepopulate functions (attaches routes to Host HTTP API)
## Deploy order (per stage)
1) Deploy the layer:
```bash
npx serverless deploy --config serverless.layers.yml --stage dev
```
2) Deploy Host (creates the HTTP API + routes for host functions):
```bash
npx serverless deploy --config serverless.host.yml --stage dev
```
3) Deploy remaining services (they reuse Host's HTTP API id):
```bash
npx serverless deploy --config serverless.admin.yml --stage dev
npx serverless deploy --config serverless.user.yml --stage dev
npx serverless deploy --config serverless.prepopulate.yml --stage dev
```
## Deploy a single function
```bash
npx serverless deploy function --config serverless.host.yml --stage dev -f getHosts
```

533
package-lock.json generated
View File

@@ -13,7 +13,7 @@
"@aws-crypto/sha256-browser": "^5.2.0",
"@aws-crypto/sha256-js": "^5.2.0",
"@aws-sdk/client-s3": "^3.928.0",
"@aws-sdk/s3-request-presigner": "^3.928.0",
"@aws-sdk/s3-request-presigner": "^3.310.0",
"@aws/lambda-invoke-store": "^0.2.1",
"@nestjs/common": "^10.3.0",
"@nestjs/config": "^3.1.1",
@@ -31,27 +31,18 @@
"@types/http-status": "^1.1.2",
"ajv": "8.12.0",
"aws-lambda": "^1.0.7",
"aws-sdk": "^2.1692.0",
"bcrypt": "^6.0.0",
"bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"date-fns": "^4.1.0",
"dayjs": "^1.11.19",
"docx": "^9.6.0",
"docxtemplater": "^3.68.3",
"fast-xml-parser": "^5.3.1",
"fs": "^0.0.1-security",
"helmet": "^7.1.0",
"http-status": "^2.1.0",
"moment": "^2.30.1",
"number-to-words": "^1.2.4",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"path": "^0.12.7",
"pdf-lib": "^1.17.1",
"pizzip": "^3.2.0",
"prisma": "^7.0.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
@@ -82,7 +73,6 @@
"prettier": "^3.2.5",
"serverless-esbuild": "^1.55.1",
"serverless-offline": "^14.4.0",
"serverless-plugin-split-stacks": "^1.14.0",
"source-map-support": "^0.5.21",
"supertest": "^6.3.4",
"ts-jest": "^29.1.2",
@@ -4660,24 +4650,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/@pdf-lib/standard-fonts": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@pdf-lib/standard-fonts/-/standard-fonts-1.0.0.tgz",
"integrity": "sha512-hU30BK9IUN/su0Mn9VdlVKsWBS6GyhVfqjwl1FjZN4TxP6cCw0jP2w7V3Hf5uX7M0AZJ16vey9yE0ny7Sa59ZA==",
"license": "MIT",
"dependencies": {
"pako": "^1.0.6"
}
},
"node_modules/@pdf-lib/upng": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@pdf-lib/upng/-/upng-1.0.1.tgz",
"integrity": "sha512-dQK2FUMQtowVP00mtIksrlZhdFXQZPC+taih1q4CvPZ5vqdxR/LKBaFg0oAfzd1GlHZXXSPdQfzQnt+ViGvEIQ==",
"license": "MIT",
"dependencies": {
"pako": "^1.0.10"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -5779,13 +5751,6 @@
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"license": "MIT"
},
"node_modules/@tootallnate/quickjs-emscripten": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
"integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
"dev": true,
"license": "MIT"
},
"node_modules/@tsconfig/node10": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz",
@@ -6581,15 +6546,6 @@
"@xtuc/long": "4.2.2"
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.9.8",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.8.tgz",
"integrity": "sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==",
"license": "MIT",
"engines": {
"node": ">=14.6"
}
},
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
@@ -6668,16 +6624,6 @@
"node": ">=0.4.0"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
@@ -7018,19 +6964,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/ast-types": {
"version": "0.13.4",
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
"integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
"dev": true,
"license": "MIT",
"dependencies": {
"tslib": "^2.0.1"
},
"engines": {
"node": ">=4"
}
},
"node_modules/async": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz",
@@ -7059,13 +6992,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/aws-info": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/aws-info/-/aws-info-1.3.0.tgz",
"integrity": "sha512-dYE3J2GQOMXjirx54IonDisZ6Ok4vBSYjNklNAGGDj2FzGHkWpGOlGAn5/BC8TRh8ttmYRy+Fsmxc8EJMnzSCg==",
"dev": true,
"license": "MIT"
},
"node_modules/aws-lambda": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/aws-lambda/-/aws-lambda-1.0.7.tgz",
@@ -7331,16 +7257,6 @@
"baseline-browser-mapping": "dist/cli.js"
}
},
"node_modules/basic-ftp": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.2.0.tgz",
"integrity": "sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/bcrypt": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz",
@@ -8470,6 +8386,7 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"dev": true,
"license": "MIT"
},
"node_modules/cors": {
@@ -8622,12 +8539,6 @@
"url": "https://github.com/sponsors/kossnocorp"
}
},
"node_modules/dayjs": {
"version": "1.11.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -8722,21 +8633,6 @@
"integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
"license": "MIT"
},
"node_modules/degenerator": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
"integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ast-types": "^0.13.4",
"escodegen": "^2.1.0",
"esprima": "^4.0.1"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -8867,50 +8763,6 @@
"node": ">=6.0.0"
}
},
"node_modules/docx": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/docx/-/docx-9.6.0.tgz",
"integrity": "sha512-y6EaJJMDvt4P7wgGQB9KsZf4wsRkQMJfkc9LlNufRshggI5BT35hGNkXBCAeEoI3MLMwApKguxzjdqqVcBCqNA==",
"license": "MIT",
"dependencies": {
"@types/node": "^25.2.3",
"hash.js": "^1.1.7",
"jszip": "^3.10.1",
"nanoid": "^5.1.3",
"xml": "^1.0.1",
"xml-js": "^1.6.8"
},
"engines": {
"node": ">=10"
}
},
"node_modules/docx/node_modules/@types/node": {
"version": "25.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz",
"integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/docx/node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT"
},
"node_modules/docxtemplater": {
"version": "3.68.3",
"resolved": "https://registry.npmjs.org/docxtemplater/-/docxtemplater-3.68.3.tgz",
"integrity": "sha512-hTZfGcHgN60A09w68Qj0EQRCnF5kf2/ohFlZlUVqAOozCFwx9QMm8naCTvmTsXafuO3nG9qpS4pQWSjFdaCWfQ==",
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "^0.9.8"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/dotenv": {
"version": "16.4.5",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
@@ -9179,39 +9031,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/escodegen": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
"integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"esprima": "^4.0.1",
"estraverse": "^5.2.0",
"esutils": "^2.0.2"
},
"bin": {
"escodegen": "bin/escodegen.js",
"esgenerate": "bin/esgenerate.js"
},
"engines": {
"node": ">=6.0"
},
"optionalDependencies": {
"source-map": "~0.6.1"
}
},
"node_modules/escodegen/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/eslint": {
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
@@ -10203,12 +10022,6 @@
"node": ">= 0.8"
}
},
"node_modules/fs": {
"version": "0.0.1-security",
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
"integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==",
"license": "ISC"
},
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
@@ -10386,31 +10199,6 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/get-uri": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
"integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
"dev": true,
"license": "MIT",
"dependencies": {
"basic-ftp": "^5.0.2",
"data-uri-to-buffer": "^6.0.2",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/get-uri/node_modules/data-uri-to-buffer": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
"integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/giget": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
@@ -10649,16 +10437,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hash.js": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
"integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"minimalistic-assert": "^1.0.1"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -10721,20 +10499,6 @@
"node": ">= 0.8"
}
},
"node_modules/http-proxy-agent": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
"integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.0",
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/http-status": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/http-status/-/http-status-2.1.0.tgz",
@@ -10750,20 +10514,6 @@
"integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
"license": "MIT"
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -10806,6 +10556,7 @@
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"dev": true,
"license": "MIT"
},
"node_modules/import-fresh": {
@@ -10910,16 +10661,6 @@
"node": ">=12.0.0"
}
},
"node_modules/ip-address": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz",
"integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -12245,6 +11986,7 @@
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"dev": true,
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
@@ -12257,6 +11999,7 @@
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true,
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
@@ -12272,12 +12015,14 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"dev": true,
"license": "MIT"
},
"node_modules/jszip/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
@@ -12404,6 +12149,7 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
@@ -12802,12 +12548,6 @@
"node": ">=6"
}
},
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
"license": "ISC"
},
"node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
@@ -12958,24 +12698,6 @@
"node": ">=12"
}
},
"node_modules/nanoid": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz",
"integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.js"
},
"engines": {
"node": "^18 || >=20"
}
},
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -13000,16 +12722,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/netmask": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
"integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/nock": {
"version": "13.5.6",
"resolved": "https://registry.npmjs.org/nock/-/nock-13.5.6.tgz",
@@ -13161,12 +12873,6 @@
"node": ">=8"
}
},
"node_modules/number-to-words": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/number-to-words/-/number-to-words-1.2.4.tgz",
"integrity": "sha512-/fYevVkXRcyBiZDg6yzZbm0RuaD6i0qRfn8yr+6D0KgBMOndFPxuW10qCHpzs50nN8qKuv78k8MuotZhcVX6Pw==",
"license": "MIT"
},
"node_modules/nypm": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.2.tgz",
@@ -13412,40 +13118,6 @@
"node": ">=6"
}
},
"node_modules/pac-proxy-agent": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
"integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@tootallnate/quickjs-emscripten": "^0.23.0",
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"get-uri": "^6.0.1",
"http-proxy-agent": "^7.0.0",
"https-proxy-agent": "^7.0.6",
"pac-resolver": "^7.0.1",
"socks-proxy-agent": "^8.0.5"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/pac-resolver": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
"integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
"dev": true,
"license": "MIT",
"dependencies": {
"degenerator": "^5.0.0",
"netmask": "^2.0.2"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -13456,6 +13128,7 @@
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"dev": true,
"license": "(MIT AND Zlib)"
},
"node_modules/parent-module": {
@@ -13546,16 +13219,6 @@
"node": ">= 0.4.0"
}
},
"node_modules/path": {
"version": "0.12.7",
"resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
"integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==",
"license": "MIT",
"dependencies": {
"process": "^0.11.1",
"util": "^0.10.3"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -13630,21 +13293,6 @@
"node": ">=8"
}
},
"node_modules/path/node_modules/inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
"license": "ISC"
},
"node_modules/path/node_modules/util": {
"version": "0.10.4",
"resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
"integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
"license": "MIT",
"dependencies": {
"inherits": "2.0.3"
}
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -13656,24 +13304,6 @@
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
},
"node_modules/pdf-lib": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/pdf-lib/-/pdf-lib-1.17.1.tgz",
"integrity": "sha512-V/mpyJAoTsN4cnP31vc0wfNA1+p20evqqnap0KLoRUN0Yk/p3wN52DOEsL4oBFcLdb76hlpKPtzJIgo67j/XLw==",
"license": "MIT",
"dependencies": {
"@pdf-lib/standard-fonts": "^1.0.0",
"@pdf-lib/upng": "^1.0.1",
"pako": "^1.0.11",
"tslib": "^1.11.1"
}
},
"node_modules/pdf-lib/node_modules/tslib": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"license": "0BSD"
},
"node_modules/perfect-debounce": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
@@ -13808,21 +13438,6 @@
"node": ">= 6"
}
},
"node_modules/pizzip": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/pizzip/-/pizzip-3.2.0.tgz",
"integrity": "sha512-X4NPNICxCfIK8VYhF6wbksn81vTiziyLbvKuORVAmolvnUzl1A1xmz9DAWKxPRq9lZg84pJOOAMq3OE61bD8IQ==",
"license": "(MIT OR GPL-3.0)",
"dependencies": {
"pako": "^2.1.0"
}
},
"node_modules/pizzip/node_modules/pako": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==",
"license": "(MIT AND Zlib)"
},
"node_modules/pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
@@ -14074,19 +13689,11 @@
}
}
},
"node_modules/process": {
"version": "0.11.10",
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"dev": true,
"license": "MIT"
},
"node_modules/prompts": {
@@ -14149,36 +13756,6 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-agent": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
"integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"http-proxy-agent": "^7.0.1",
"https-proxy-agent": "^7.0.6",
"lru-cache": "^7.14.1",
"pac-proxy-agent": "^7.1.0",
"proxy-from-env": "^1.1.0",
"socks-proxy-agent": "^8.0.5"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/proxy-agent/node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@@ -15179,23 +14756,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/serverless-plugin-split-stacks": {
"version": "1.14.0",
"resolved": "https://registry.npmjs.org/serverless-plugin-split-stacks/-/serverless-plugin-split-stacks-1.14.0.tgz",
"integrity": "sha512-VksNqvJUPnGHqef0jHNiN0BzTVr0Hy0cWaLxCG75HiQ3vnIog8qeyiu7uWH6LKNhJnGP1jiTNh0YcheCN8kaKA==",
"dev": true,
"license": "MIT",
"dependencies": {
"aws-info": "^1.2.0",
"lodash": "^4.17.21",
"proxy-agent": "^6.3.1",
"semver": "^7.3.5",
"throat": "^6.0.1"
},
"peerDependencies": {
"serverless": "1 || 2 || 3 || 4"
}
},
"node_modules/serverless/node_modules/rimraf": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz",
@@ -15232,6 +14792,7 @@
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"dev": true,
"license": "MIT"
},
"node_modules/setprototypeof": {
@@ -15362,47 +14923,6 @@
"node": ">=8"
}
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks": {
"version": "2.8.7",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz",
"integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ip-address": "^10.0.1",
"smart-buffer": "^4.2.0"
},
"engines": {
"node": ">= 10.0.0",
"npm": ">= 3.0.0"
}
},
"node_modules/socks-proxy-agent": {
"version": "8.0.5",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
"integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
"dev": true,
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "^4.3.4",
"socks": "^2.8.3"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/sorted-array-functions": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz",
@@ -15984,13 +15504,6 @@
"dev": true,
"license": "MIT"
},
"node_modules/throat": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/throat/-/throat-6.0.2.tgz",
"integrity": "sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==",
"dev": true,
"license": "MIT"
},
"node_modules/through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
@@ -17563,30 +17076,6 @@
}
}
},
"node_modules/xml": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
"license": "MIT"
},
"node_modules/xml-js": {
"version": "1.6.11",
"resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz",
"integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==",
"license": "MIT",
"dependencies": {
"sax": "^1.2.4"
},
"bin": {
"xml-js": "bin/cli.js"
}
},
"node_modules/xml-js/node_modules/sax": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
"integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==",
"license": "BlueOak-1.0.0"
},
"node_modules/xml2js": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",

View File

@@ -97,9 +97,7 @@
"eslint-plugin-prettier": "^5.1.3",
"jest": "^29.7.0",
"prettier": "^3.2.5",
"serverless-esbuild": "^1.55.1",
"serverless-offline": "^14.4.0",
"serverless-plugin-split-stacks": "^1.14.0",
"source-map-support": "^0.5.21",
"supertest": "^6.3.4",
"ts-jest": "^29.1.2",

View File

@@ -1,8 +1,7 @@
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "rhel-openssl-3.0.x"] // Lambda Node 18/20 (Amazon Linux) target
binaryTargets = ["native", "rhel-openssl-3.0.x"] // Add Linux target
previewFeatures = ["multiSchema"]
engineType = "library"
}
datasource db {
@@ -555,6 +554,20 @@ model Frequencies {
@@schema("mst")
}
model NavigationModes {
id Int @id @default(autoincrement())
navigationModeName String @unique @map("navigation_mode_name") @db.VarChar(30)
navigationModeIcon String @map("navigation_mode_icon") @db.VarChar(500)
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
ActivityNavigationModes ActivityNavigationModes[]
@@map("navigation_modes")
@@schema("mst")
}
model TransportModes {
id Int @id @default(autoincrement())
transportModeName String @unique @map("transport_mode_name") @db.VarChar(60)
@@ -1039,13 +1052,13 @@ model ActivityOtherDetails {
id Int @id @default(autoincrement())
activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
exclusiveNotes String? @map("exclusive_notes") @db.Text
SafetyInstruction String? @map("safety_instruction") @db.Text
Cancellations String? @map("cancellations") @db.Text
dosNotes String? @map("dos_notes") @db.Text
dontsNotes String? @map("donts_notes") @db.Text
tipsNotes String? @map("tips_notes") @db.Text
termsAndCondition String? @map("terms_and_condition") @db.Text
exclusiveNotes String? @map("exclusive_notes") @db.VarChar(500)
SafetyInstruction String? @map("safety_instruction") @db.VarChar(400)
Cancellations String? @map("cancellations") @db.VarChar(400)
dosNotes String? @map("dos_notes") @db.VarChar(400)
dontsNotes String? @map("donts_notes") @db.VarChar(400)
tipsNotes String? @map("tips_notes") @db.VarChar(400)
termsAndCondition String? @map("terms_and_condition") @db.VarChar(500)
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@ -1443,7 +1456,8 @@ model ActivityNavigationModes {
id Int @id @default(autoincrement())
activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
navigationModeName String @map("navigation_mode_name") @db.VarChar(30)
navigationModeXid Int @map("navigation_mode_xid")
navigationMode NavigationModes @relation(fields: [navigationModeXid], references: [id], onDelete: Restrict)
isInActivityChargeable Boolean @default(false) @map("is_in_activity_chargeable")
navigationModesBasePrice Int @map("navigation_modes_base_price")
navigationModesTotalPrice Int @map("navigation_modes_total_price")
@@ -1728,8 +1742,8 @@ model ItineraryActivities {
travelMode String? @map("travel_mode") @db.VarChar(30)
kmForNextPoint Float? @map("km_for_next_point")
timeForNextPointMins Int? @map("time_for_next_point_mins")
paxCount Int? @map("pax_count")
totalAmount Int? @map("total_amount")
paxCount Int @map("pax_count")
totalAmount Int @map("total_amount")
bookingStatus String @default("pending") @map("booking_status") @db.VarChar(30)
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")

View File

@@ -268,9 +268,9 @@ async function main() {
create: { interestName: 'Nightlife & Events', displayOrder: 10, interestColor: 'Blue', interestImage: 'https://minglar-dev-bucket.s3.ap-south-1.amazonaws.com/StaticImages/InterestTypes/NightlifeandEvents.png', interestCode: 'NE' },
});
const furfam = await prisma.interests.upsert({
where: { interestName: 'Pet space' },
where: { interestName: 'Fur Fam' },
update: {},
create: { interestName: 'Pet space', displayOrder: 11, interestColor: 'Blue', interestImage: 'https://minglar-dev-bucket.s3.ap-south-1.amazonaws.com/StaticImages/InterestTypes/petspace.jpg', interestCode: 'PS' },
create: { interestName: 'Fur Fam', displayOrder: 11, interestColor: 'Blue', interestImage: 'https://minglar-dev-bucket.s3.ap-south-1.amazonaws.com/StaticImages/InterestTypes/petspace.jpg', interestCode: 'PS' },
});
const dogoodfeelgood = await prisma.interests.upsert({
where: { interestName: 'Do Good, Feel Good' },
@@ -693,6 +693,16 @@ async function main() {
skipDuplicates: true,
});
// ✅ Navigation Modes
await prisma.navigationModes.createMany({
data: [
{ navigationModeName: 'Elephant Ride', navigationModeIcon: '🚗' },
{ navigationModeName: 'Horse Ride', navigationModeIcon: '🏍️' },
{ navigationModeName: 'Camel Ride', navigationModeIcon: '🚶' },
],
skipDuplicates: true,
});
// ✅ Transport Modes
await prisma.transportModes.createMany({
data: [

View File

@@ -1,3 +1,5 @@
service: minglar-admin
useDotenv: true
params:
@@ -13,17 +15,12 @@ provider:
runtime: nodejs22.x
region: ap-south-1
stage: ${opt:stage, 'dev'}
deploymentBucket:
# use a fixed bucket name to prevent Serverless from creating/quashing a resource
name: serverless-framework-deployments-ap-south-1-50264b8e-d2b9
# optionally uncomment below to enable serverless to create if missing
# serverSideEncryption: AES256
versionFunctions: false
memorySize: 512
# Apply Prisma layer to all functions
# Reference the layer defined in the dedicated layer stack
layers:
- ${cf:minglar-prisma-layer-${sls:stage}.PrismaLambdaLayerQualifiedArn}
- ${cf:minglar-layers-${sls:stage}.PrismaLambdaLayerQualifiedArn}
httpApi:
id: ${cf:minglar-host-${sls:stage}.HttpApiId}
apiGateway:
binaryMediaTypes:
- '*/*'
@@ -73,14 +70,17 @@ provider:
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}'
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*'
custom:
serverless-offline:
reloadHandler: true
build:
esbuild:
bundle: true
minify: true
sourcemap: false
target: node20
target: node22
platform: node
# Mark as external so they're not bundled into the JS
external:
- '@prisma/client'
- '.prisma/client'
@@ -91,7 +91,6 @@ build:
- '@aws-sdk/*'
- '@smithy/*'
- '@aws-crypto/*'
# Exclude prevents npm install of these packages in the zip
exclude:
- 'aws-sdk'
- '@aws-sdk/*'
@@ -111,6 +110,7 @@ build:
package:
individually: true
excludeDevDependencies: true
patterns:
- '!node_modules/**'
- '!node_modules/@prisma/**'
@@ -126,9 +126,8 @@ package:
- '!.git/**'
- '!.github/**'
functions:
- ${file(./serverless/functions/minglaradmin.yml)}
plugins:
- serverless-offline
custom:
serverless-offline:
reloadHandler: true

View File

@@ -1,13 +1,132 @@
service: minglar-host
useDotenv: ${file(./serverless/common.yml):useDotenv}
params: ${file(./serverless/common.yml):params}
provider: ${file(./serverless/common.yml):provider}
build: ${file(./serverless/common.yml):build}
package: ${file(./serverless/common.yml):package}
plugins: ${file(./serverless/common.yml):plugins}
custom: ${file(./serverless/common.yml):custom}
useDotenv: true
params:
dev:
stage: dev
test:
stage: test
uat:
stage: uat
provider:
name: aws
runtime: nodejs22.x
region: ap-south-1
stage: ${opt:stage, 'dev'}
versionFunctions: false
memorySize: 512
layers:
- ${cf:minglar-layers-${sls:stage}.PrismaLambdaLayerQualifiedArn}
apiGateway:
binaryMediaTypes:
- '*/*'
minimumCompressionSize: 1024
environment:
DATABASE_URL: ${env:DATABASE_URL}
DB_USERNAME: ${env:DB_USERNAME}
DB_PASSWORD: ${env:DB_PASSWORD}
DB_DATABASE_NAME: ${env:DB_DATABASE_NAME}
DB_HOSTNAME: ${env:DB_HOSTNAME}
DB_PORT: ${env:DB_PORT}
BY_PASS_EMAIL: ${env:BY_PASS_EMAIL}
BYPASS_OTP: ${env:BYPASS_OTP}
BREVO_EMAIL_API_KEY: ${env:BREVO_EMAIL_API_KEY}
BREVO_API_BASEURL: ${env:BREVO_API_BASEURL}
BREVO_FROM_EMAIL: ${env:BREVO_FROM_EMAIL}
BREVO_SMTP_HOST: ${env:BREVO_SMTP_HOST}
BREVO_SMTP_PORT: ${env:BREVO_SMTP_PORT}
BREVO_SMTP_USER: ${env:BREVO_SMTP_USER}
BREVO_SMTP_PASS: ${env:BREVO_SMTP_PASS}
REFRESH_TOKEN_SECRET: ${env:REFRESH_TOKEN_SECRET}
JWT_SECRET: ${env:JWT_SECRET}
JWT_ACCESS_EXPIRATION_MINUTES: ${env:JWT_ACCESS_EXPIRATION_MINUTES}
JWT_REFRESH_EXPIRATION_DAYS: ${env:JWT_REFRESH_EXPIRATION_DAYS}
JWT_RESET_PASSWORD_EXPIRATION_MINUTES: ${env:JWT_RESET_PASSWORD_EXPIRATION_MINUTES}
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: ${env:JWT_VERIFY_EMAIL_EXPIRATION_MINUTES}
SALT_ROUNDS: ${env:SALT_ROUNDS}
NODE_ENV: ${env:NODE_ENV}
S3_BUCKET_NAME: ${env:S3_BUCKET_NAME}
MINGLAR_ADMIN_NAME: ${env:MINGLAR_ADMIN_NAME}
MINGLAR_ADMIN_EMAIL: ${env:MINGLAR_ADMIN_EMAIL}
AM_INVITATION_LINK: ${env:AM_INVITATION_LINK}
HOST_LINK: ${env:HOST_LINK}
HOST_LINK_PQ: ${env:HOST_LINK_PQ}
iam:
role:
statements:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:DeleteObject
- s3:ListBucket
Resource:
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}'
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*'
custom:
serverless-offline:
reloadHandler: true
build:
esbuild:
bundle: true
minify: true
sourcemap: false
target: node22
platform: node
external:
- '@prisma/client'
- '.prisma/client'
- '.prisma'
- '@prisma/adapter-pg'
- 'pg'
- 'zod'
- '@aws-sdk/*'
- '@smithy/*'
- '@aws-crypto/*'
exclude:
- 'aws-sdk'
- '@aws-sdk/*'
- '@smithy/*'
- '@aws-crypto/*'
- '@prisma/adapter-pg'
- '@prisma/client'
- '.prisma'
- '.prisma/client'
- 'pg'
- 'zod'
- 'pg-*'
- 'postgres-*'
- 'pgpass'
- 'split2'
- 'xtend'
package:
individually: true
excludeDevDependencies: true
patterns:
- '!node_modules/**'
- '!node_modules/@prisma/**'
- '!node_modules/.prisma/**'
- '!**/*.test.js'
- '!**/*.spec.js'
- '!**/test/**'
- '!**/__tests__/**'
- '!package-lock.json'
- '!yarn.lock'
- '!README.md'
- '!*.config.js'
- '!.git/**'
- '!.github/**'
functions:
- ${file(./serverless/functions/host.yml)}
- ${file(./serverless/functions/pqq.yml)}
plugins:
- serverless-offline

View File

@@ -1,4 +1,4 @@
service: minglar-prisma-layer
service: minglar-layers
useDotenv: true
@@ -15,13 +15,9 @@ provider:
runtime: nodejs22.x
region: ap-south-1
stage: ${opt:stage, 'dev'}
deploymentBucket:
# use a fixed bucket name to prevent Serverless from creating/quashing a resource
name: serverless-framework-deployments-ap-south-1-50264b8e-d2b9
# optionally uncomment below to enable serverless to create if missing
# serverSideEncryption: AES256
versionFunctions: false
# Define layers (deployed once; other stacks reference via cf output)
layers:
prisma:
path: layers/prisma
@@ -30,3 +26,5 @@ layers:
compatibleRuntimes:
- nodejs22.x
retain: false
plugins: []

View File

@@ -1,13 +0,0 @@
service: minglar-admin
useDotenv: ${file(./serverless/common.yml):useDotenv}
params: ${file(./serverless/common.yml):params}
provider: ${file(./serverless/common.yml):provider}
build: ${file(./serverless/common.yml):build}
package: ${file(./serverless/common.yml):package}
plugins: ${file(./serverless/common.yml):plugins}
custom: ${file(./serverless/common.yml):custom}
functions:
- ${file(./serverless/functions/minglaradmin.yml)}

View File

@@ -1,13 +1,133 @@
service: minglar-prepopulate
useDotenv: ${file(./serverless/common.yml):useDotenv}
params: ${file(./serverless/common.yml):params}
provider: ${file(./serverless/common.yml):provider}
build: ${file(./serverless/common.yml):build}
package: ${file(./serverless/common.yml):package}
plugins: ${file(./serverless/common.yml):plugins}
custom: ${file(./serverless/common.yml):custom}
useDotenv: true
params:
dev:
stage: dev
test:
stage: test
uat:
stage: uat
provider:
name: aws
runtime: nodejs22.x
region: ap-south-1
stage: ${opt:stage, 'dev'}
versionFunctions: false
memorySize: 512
layers:
- ${cf:minglar-layers-${sls:stage}.PrismaLambdaLayerQualifiedArn}
httpApi:
id: ${cf:minglar-host-${sls:stage}.HttpApiId}
apiGateway:
binaryMediaTypes:
- '*/*'
minimumCompressionSize: 1024
environment:
DATABASE_URL: ${env:DATABASE_URL}
DB_USERNAME: ${env:DB_USERNAME}
DB_PASSWORD: ${env:DB_PASSWORD}
DB_DATABASE_NAME: ${env:DB_DATABASE_NAME}
DB_HOSTNAME: ${env:DB_HOSTNAME}
DB_PORT: ${env:DB_PORT}
BY_PASS_EMAIL: ${env:BY_PASS_EMAIL}
BYPASS_OTP: ${env:BYPASS_OTP}
BREVO_EMAIL_API_KEY: ${env:BREVO_EMAIL_API_KEY}
BREVO_API_BASEURL: ${env:BREVO_API_BASEURL}
BREVO_FROM_EMAIL: ${env:BREVO_FROM_EMAIL}
BREVO_SMTP_HOST: ${env:BREVO_SMTP_HOST}
BREVO_SMTP_PORT: ${env:BREVO_SMTP_PORT}
BREVO_SMTP_USER: ${env:BREVO_SMTP_USER}
BREVO_SMTP_PASS: ${env:BREVO_SMTP_PASS}
REFRESH_TOKEN_SECRET: ${env:REFRESH_TOKEN_SECRET}
JWT_SECRET: ${env:JWT_SECRET}
JWT_ACCESS_EXPIRATION_MINUTES: ${env:JWT_ACCESS_EXPIRATION_MINUTES}
JWT_REFRESH_EXPIRATION_DAYS: ${env:JWT_REFRESH_EXPIRATION_DAYS}
JWT_RESET_PASSWORD_EXPIRATION_MINUTES: ${env:JWT_RESET_PASSWORD_EXPIRATION_MINUTES}
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: ${env:JWT_VERIFY_EMAIL_EXPIRATION_MINUTES}
SALT_ROUNDS: ${env:SALT_ROUNDS}
NODE_ENV: ${env:NODE_ENV}
S3_BUCKET_NAME: ${env:S3_BUCKET_NAME}
MINGLAR_ADMIN_NAME: ${env:MINGLAR_ADMIN_NAME}
MINGLAR_ADMIN_EMAIL: ${env:MINGLAR_ADMIN_EMAIL}
AM_INVITATION_LINK: ${env:AM_INVITATION_LINK}
HOST_LINK: ${env:HOST_LINK}
HOST_LINK_PQ: ${env:HOST_LINK_PQ}
iam:
role:
statements:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:DeleteObject
- s3:ListBucket
Resource:
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}'
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*'
custom:
serverless-offline:
reloadHandler: true
build:
esbuild:
bundle: true
minify: true
sourcemap: false
target: node22
platform: node
external:
- '@prisma/client'
- '.prisma/client'
- '.prisma'
- '@prisma/adapter-pg'
- 'pg'
- 'zod'
- '@aws-sdk/*'
- '@smithy/*'
- '@aws-crypto/*'
exclude:
- 'aws-sdk'
- '@aws-sdk/*'
- '@smithy/*'
- '@aws-crypto/*'
- '@prisma/adapter-pg'
- '@prisma/client'
- '.prisma'
- '.prisma/client'
- 'pg'
- 'zod'
- 'pg-*'
- 'postgres-*'
- 'pgpass'
- 'split2'
- 'xtend'
package:
individually: true
excludeDevDependencies: true
patterns:
- '!node_modules/**'
- '!node_modules/@prisma/**'
- '!node_modules/.prisma/**'
- '!**/*.test.js'
- '!**/*.spec.js'
- '!**/test/**'
- '!**/__tests__/**'
- '!package-lock.json'
- '!yarn.lock'
- '!README.md'
- '!*.config.js'
- '!.git/**'
- '!.github/**'
functions:
- ${file(./serverless/functions/prepopulate.yml)}
plugins:
- serverless-offline

View File

@@ -1,13 +1,133 @@
service: minglar-user
useDotenv: ${file(./serverless/common.yml):useDotenv}
params: ${file(./serverless/common.yml):params}
provider: ${file(./serverless/common.yml):provider}
build: ${file(./serverless/common.yml):build}
package: ${file(./serverless/common.yml):package}
plugins: ${file(./serverless/common.yml):plugins}
custom: ${file(./serverless/common.yml):custom}
useDotenv: true
params:
dev:
stage: dev
test:
stage: test
uat:
stage: uat
provider:
name: aws
runtime: nodejs22.x
region: ap-south-1
stage: ${opt:stage, 'dev'}
versionFunctions: false
memorySize: 512
layers:
- ${cf:minglar-layers-${sls:stage}.PrismaLambdaLayerQualifiedArn}
httpApi:
id: ${cf:minglar-host-${sls:stage}.HttpApiId}
apiGateway:
binaryMediaTypes:
- '*/*'
minimumCompressionSize: 1024
environment:
DATABASE_URL: ${env:DATABASE_URL}
DB_USERNAME: ${env:DB_USERNAME}
DB_PASSWORD: ${env:DB_PASSWORD}
DB_DATABASE_NAME: ${env:DB_DATABASE_NAME}
DB_HOSTNAME: ${env:DB_HOSTNAME}
DB_PORT: ${env:DB_PORT}
BY_PASS_EMAIL: ${env:BY_PASS_EMAIL}
BYPASS_OTP: ${env:BYPASS_OTP}
BREVO_EMAIL_API_KEY: ${env:BREVO_EMAIL_API_KEY}
BREVO_API_BASEURL: ${env:BREVO_API_BASEURL}
BREVO_FROM_EMAIL: ${env:BREVO_FROM_EMAIL}
BREVO_SMTP_HOST: ${env:BREVO_SMTP_HOST}
BREVO_SMTP_PORT: ${env:BREVO_SMTP_PORT}
BREVO_SMTP_USER: ${env:BREVO_SMTP_USER}
BREVO_SMTP_PASS: ${env:BREVO_SMTP_PASS}
REFRESH_TOKEN_SECRET: ${env:REFRESH_TOKEN_SECRET}
JWT_SECRET: ${env:JWT_SECRET}
JWT_ACCESS_EXPIRATION_MINUTES: ${env:JWT_ACCESS_EXPIRATION_MINUTES}
JWT_REFRESH_EXPIRATION_DAYS: ${env:JWT_REFRESH_EXPIRATION_DAYS}
JWT_RESET_PASSWORD_EXPIRATION_MINUTES: ${env:JWT_RESET_PASSWORD_EXPIRATION_MINUTES}
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: ${env:JWT_VERIFY_EMAIL_EXPIRATION_MINUTES}
SALT_ROUNDS: ${env:SALT_ROUNDS}
NODE_ENV: ${env:NODE_ENV}
S3_BUCKET_NAME: ${env:S3_BUCKET_NAME}
MINGLAR_ADMIN_NAME: ${env:MINGLAR_ADMIN_NAME}
MINGLAR_ADMIN_EMAIL: ${env:MINGLAR_ADMIN_EMAIL}
AM_INVITATION_LINK: ${env:AM_INVITATION_LINK}
HOST_LINK: ${env:HOST_LINK}
HOST_LINK_PQ: ${env:HOST_LINK_PQ}
iam:
role:
statements:
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:DeleteObject
- s3:ListBucket
Resource:
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}'
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*'
custom:
serverless-offline:
reloadHandler: true
build:
esbuild:
bundle: true
minify: true
sourcemap: false
target: node22
platform: node
external:
- '@prisma/client'
- '.prisma/client'
- '.prisma'
- '@prisma/adapter-pg'
- 'pg'
- 'zod'
- '@aws-sdk/*'
- '@smithy/*'
- '@aws-crypto/*'
exclude:
- 'aws-sdk'
- '@aws-sdk/*'
- '@smithy/*'
- '@aws-crypto/*'
- '@prisma/adapter-pg'
- '@prisma/client'
- '.prisma'
- '.prisma/client'
- 'pg'
- 'zod'
- 'pg-*'
- 'postgres-*'
- 'pgpass'
- 'split2'
- 'xtend'
package:
individually: true
excludeDevDependencies: true
patterns:
- '!node_modules/**'
- '!node_modules/@prisma/**'
- '!node_modules/.prisma/**'
- '!**/*.test.js'
- '!**/*.spec.js'
- '!**/test/**'
- '!**/__tests__/**'
- '!package-lock.json'
- '!yarn.lock'
- '!README.md'
- '!*.config.js'
- '!.git/**'
- '!.github/**'
functions:
- ${file(./serverless/functions/user.yml)}
plugins:
- serverless-offline

View File

@@ -1,5 +1,4 @@
# Legacy monolith config. For new deployments use serverless.*.yml files.
service: minglarDev
service: minglar
useDotenv: true
@@ -17,11 +16,6 @@ provider:
runtime: nodejs22.x
region: ap-south-1
stage: ${opt:stage, 'dev'}
deploymentBucket:
# use a fixed bucket name to prevent Serverless from creating/quashing a resource
name: serverless-framework-deployments-ap-south-1-50264b8e-d2b9
# optionally uncomment below to enable serverless to create if missing
# serverSideEncryption: AES256
versionFunctions: false
memorySize: 512
# Apply Prisma layer to all functions
@@ -29,7 +23,7 @@ provider:
layers:
# Use the exported stack output so deploy function works (expects a string ARN)
# For offline/local, fall back to an empty string so the CF lookup is optional.
- ${cf:${self:service}-${sls:stage}.PrismaLambdaLayerQualifiedArn, ''}
- ${cf:${self:service}-${sls:stage}.PrismaLambdaLayerQualifiedArn}
apiGateway:
binaryMediaTypes:
- '*/*'
@@ -79,12 +73,16 @@ provider:
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}'
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*'
custom:
serverless-offline:
reloadHandler: true
build:
esbuild:
bundle: true
minify: true
sourcemap: false
target: node20
target: node22
platform: node
# Mark as external so they're not bundled into the JS
external:
@@ -127,6 +125,7 @@ layers:
package:
individually: true
excludeDevDependencies: true
patterns:
- '!node_modules/**'
- '!node_modules/@prisma/**'
@@ -147,18 +146,8 @@ functions:
- ${file(./serverless/functions/host.yml)}
- ${file(./serverless/functions/minglaradmin.yml)}
- ${file(./serverless/functions/prepopulate.yml)}
- ${file(./serverless/functions/pqq.yml)}
- ${file(./serverless/functions/user.yml)}
plugins:
- serverless-offline
- serverless-plugin-split-stacks
custom:
serverless-offline:
reloadHandler: true
# split-stacks configuration to avoid CloudFormation resource limit
splitStacks:
perFunction: true
perType: true
perGroupFunction: false

View File

@@ -14,7 +14,7 @@ getHosts:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /
path: /host
method: get
verifyOTP:
@@ -30,7 +30,7 @@ verifyOTP:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Host_Admin/onboarding/verify-otp
path: /host/Host_Admin/onboarding/verify-otp
method: post
login:
@@ -46,7 +46,7 @@ login:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Host_Admin/onboarding/login
path: /host/Host_Admin/onboarding/login
method: post
signUp:
@@ -62,7 +62,7 @@ signUp:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Host_Admin/onboarding/registration
path: /host/Host_Admin/onboarding/registration
method: post
createPassword:
@@ -78,7 +78,7 @@ createPassword:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Host_Admin/onboarding/create-password
path: /host/Host_Admin/onboarding/create-password
method: post
updateBankDetails:
@@ -94,7 +94,7 @@ updateBankDetails:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Host_Admin/onboarding/add-payment-details
path: /host/Host_Admin/onboarding/add-payment-details
method: post
saveActivity_ForPQQ:
@@ -110,7 +110,7 @@ saveActivity_ForPQQ:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/add-activity
path: /host/Activity_Hub/OnBoarding/add-activity
method: post
getHostById:
@@ -126,7 +126,7 @@ getHostById:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /getById
path: /host/getById
method: get
getPQQ_ByQuestionId:
@@ -142,7 +142,7 @@ getPQQ_ByQuestionId:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/get-pqq-question-details
path: /host/Activity_Hub/OnBoarding/get-pqq-question-details
method: get
getPQQ_LastUpdatedQuestion:
@@ -158,7 +158,7 @@ getPQQ_LastUpdatedQuestion:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/get-latest-pqq-question-details
path: /host/Activity_Hub/OnBoarding/get-latest-pqq-question-details
method: get
prePopulateNewActivity:
@@ -174,7 +174,7 @@ prePopulateNewActivity:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/prepopulate-new-activity
path: /host/Activity_Hub/OnBoarding/prepopulate-new-activity
method: get
createNewActivity:
@@ -191,7 +191,7 @@ createNewActivity:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/create-new-activity
path: /host/Activity_Hub/OnBoarding/create-new-activity
method: patch
showSuggestion:
@@ -207,7 +207,7 @@ showSuggestion:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /get-suggestion
path: /host/get-suggestion
method: get
getAllActivitySuggestion:
@@ -223,7 +223,7 @@ getAllActivitySuggestion:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /get-Activity-suggestion
path: /host/get-Activity-suggestion
method: get
getAllHostActivity:
@@ -239,7 +239,7 @@ getAllHostActivity:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/get-all-host-activity
path: /host/Activity_Hub/OnBoarding/get-all-host-activity
method: get
acceptAggrement:
@@ -255,25 +255,9 @@ acceptAggrement:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Host_Admin/onboarding/accept-agreement
path: /host/Host_Admin/onboarding/accept-agreement
method: patch
getLatestAgreement:
handler: src/modules/host/handlers/Host_Admin/onboarding/getLatestAgreement.handler
memorySize: 384
package:
patterns:
- 'src/modules/host/handlers/Host_Admin/onboarding/getLatestAgreement.*'
- 'src/modules/host/services/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Host_Admin/onboarding/get-latest-agreement
method: get
getStepperInfo:
handler: src/modules/host/handlers/getStepper.handler
memorySize: 384
@@ -292,22 +276,6 @@ getStepperInfo:
path: /stepper
method: get
updateHostProfile:
handler: src/modules/host/handlers/updateHostProfile.handler
memorySize: 384
package:
patterns:
- 'src/modules/host/handlers/updateHostProfile.*'
- 'src/modules/host/services/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /profile
method: patch
# Functions with S3/AWS SDK dependencies
submitCompanyDetails:
handler: src/modules/host/handlers/Host_Admin/onboarding/submitCompanyDetails.handler
@@ -320,7 +288,7 @@ submitCompanyDetails:
- 'src/common/**'
events:
- httpApi:
path: /Host_Admin/onboarding/add-company-details
path: /host/Host_Admin/onboarding/add-company-details
method: patch
submitPQQ_Answer:
@@ -336,7 +304,7 @@ submitPQQ_Answer:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/submit-pqq-answer
path: /host/Activity_Hub/OnBoarding/submit-pqq-answer
method: patch
updatePQQ_LastAnswer:
@@ -352,7 +320,7 @@ updatePQQ_LastAnswer:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/submit-final-pqq-answer
path: /host/Activity_Hub/OnBoarding/submit-final-pqq-answer
method: post
submitPQQForReview:
@@ -368,7 +336,7 @@ submitPQQForReview:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/submit-pqq-for-review
path: /host/Activity_Hub/OnBoarding/submit-pqq-for-review
method: patch
getAllPQQwithSubmittedAns:
@@ -383,7 +351,7 @@ getAllPQQwithSubmittedAns:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/get-all-pqq-ques-submited-ans
path: /host/Activity_Hub/OnBoarding/get-all-pqq-ques-submited-ans
method: get
getAllDetailsOfActivityAndVenue:
@@ -398,7 +366,7 @@ getAllDetailsOfActivityAndVenue:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/get-all-details-activity-venue/{activityXid}
path: /host/Activity_Hub/OnBoarding/get-all-details-activity-venue/{activityXid}
method: get
updateSuggestionAsReviewed:
@@ -413,7 +381,7 @@ updateSuggestionAsReviewed:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/update-suggestion-reviewed
path: /host/Activity_Hub/OnBoarding/update-suggestion-reviewed
method: patch
resendOTPmail:
@@ -555,33 +523,3 @@ openCanceledSlotForActivity:
- httpApi:
path: /scheduling/open-canceled-slot
method: patch
createActivityAndAllQuestionsEntry:
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/createActivityAndAllQuestionsEntry.handler
memorySize: 384
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/createActivityAndAllQuestionsEntry**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/create-activity
method: post
submitPQAnswer:
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQAnswer.handler
memorySize: 384
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQAnswer**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /Activity_Hub/OnBoarding/submit-pq-answer
method: patch

View File

@@ -13,7 +13,7 @@ minglarRegistration:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /registration
path: /minglaradmin/registration
method: post
minglarLoginForAdmin:
@@ -28,7 +28,7 @@ minglarLoginForAdmin:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /login
path: /minglaradmin/login
method: post
minglarCreatePassword:
@@ -43,7 +43,7 @@ minglarCreatePassword:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /create-password
path: /minglaradmin/create-password
method: post
updateMinglarProfile:
@@ -60,7 +60,7 @@ updateMinglarProfile:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /update-profile
path: /minglaradmin/update-profile
method: patch
prepopulateRole:
@@ -75,7 +75,7 @@ prepopulateRole:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /prepopulate-Roles
path: /minglaradmin/prepopulate-Roles
method: get
getHostDetailsById:
@@ -90,7 +90,7 @@ getHostDetailsById:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/hosts/get-host-details/{host_xid}
path: /minglaradmin/hosthub/hosts/get-host-details/{host_xid}
method: get
inviteTeammate:
@@ -105,7 +105,7 @@ inviteTeammate:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /settings/teammates/invite-teammate
path: /minglaradmin/settings/teammates/invite-teammate
method: post
getAllHostApplication:
@@ -121,7 +121,7 @@ getAllHostApplication:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/hosts/get-all-host-applications-am
path: /minglaradmin/hosthub/hosts/get-all-host-applications-am
method: get
getAllHostActivityForAdmin:
@@ -137,7 +137,7 @@ getAllHostActivityForAdmin:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /get-all-activity-of-host/{id}
path: /minglaradmin/get-all-activity-of-host/{id}
method: get
getAllOnboardingHostApplications:
@@ -153,7 +153,7 @@ getAllOnboardingHostApplications:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/onboarding/get-all-host-applications-admin
path: /minglaradmin/hosthub/onboarding/get-all-host-applications-admin
method: get
getAllOnboardingHostApplications_New:
@@ -169,7 +169,7 @@ getAllOnboardingHostApplications_New:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/onboarding/get-all-host-applications-admin-new
path: /minglaradmin/hosthub/onboarding/get-all-host-applications-admin-new
method: get
getAllInvitationDetails:
@@ -184,7 +184,7 @@ getAllInvitationDetails:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /settings/teammates/get-all-invitation-details
path: /minglaradmin/settings/teammates/get-all-invitation-details
method: get
addSuggestion:
@@ -200,7 +200,7 @@ addSuggestion:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/hosts/add-suggestion
path: /minglaradmin/hosthub/hosts/add-suggestion
method: post
getAllCoadminAndAMDetails:
@@ -215,7 +215,7 @@ getAllCoadminAndAMDetails:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /settings/teammates/get-all-coadmin-am
path: /minglaradmin/settings/teammates/get-all-coadmin-am
method: get
getAllInvitedCoadminAndAMDetails:
@@ -230,7 +230,7 @@ getAllInvitedCoadminAndAMDetails:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /settings/teammates/get-all-invited-coadmin-am
path: /minglaradmin/settings/teammates/get-all-invited-coadmin-am
method: get
getAmDetailsbyId:
@@ -245,7 +245,7 @@ getAmDetailsbyId:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /settings/teammates/get-am-details-by-id/{amXid}
path: /minglaradmin/settings/teammates/get-am-details-by-id/{amXid}
method: get
assignAMToHost:
@@ -261,7 +261,7 @@ assignAMToHost:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/onboarding/assign-am
path: /minglaradmin/hosthub/onboarding/assign-am
method: patch
editAgreementDetailsAndAccept:
@@ -277,7 +277,7 @@ editAgreementDetailsAndAccept:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/onboarding/edit-agreement-accept-host
path: /minglaradmin/hosthub/onboarding/edit-agreement-accept-host
method: patch
getAllPqqQuesAnsForAM:
@@ -292,7 +292,7 @@ getAllPqqQuesAnsForAM:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/onboarding/get-all-pqq-ques-ans-for-am
path: /minglaradmin/hosthub/onboarding/get-all-pqq-ques-ans-for-am
method: get
acceptHostApplication:
@@ -308,7 +308,7 @@ acceptHostApplication:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/hosts/accept-host-application
path: /minglaradmin/hosthub/hosts/accept-host-application
method: patch
RejectPQQByAM:
@@ -324,7 +324,7 @@ RejectPQQByAM:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/hosts/reject-pq-by-am
path: /minglaradmin/hosthub/hosts/reject-pq-by-am
method: patch
rejectActivityDetailsApplicationByAM:
@@ -340,7 +340,7 @@ rejectActivityDetailsApplicationByAM:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/hosts/reject-activity-application-by-am
path: /minglaradmin/hosthub/hosts/reject-activity-application-by-am
method: patch
acceptPQByAM:
@@ -356,7 +356,7 @@ acceptPQByAM:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/hosts/accept-pq-by-am
path: /minglaradmin/hosthub/hosts/accept-pq-by-am
method: patch
acceptActivityDetailsApplicationByAM:
@@ -372,7 +372,7 @@ acceptActivityDetailsApplicationByAM:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/hosts/accept-activity-application-by-am
path: /minglaradmin/hosthub/hosts/accept-activity-application-by-am
method: patch
rejectHostApplication:
@@ -388,7 +388,7 @@ rejectHostApplication:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/onboarding/reject-host-application
path: /minglaradmin/hosthub/onboarding/reject-host-application
method: patch
rejectHostApplicationAM:
@@ -404,7 +404,7 @@ rejectHostApplicationAM:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/hosts/reject-host-application-am
path: /minglaradmin/hosthub/hosts/reject-host-application-am
method: patch
addPQQSuggestion:
@@ -420,7 +420,7 @@ addPQQSuggestion:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/hosts/add-Pqq-suggestion
path: /minglaradmin/hosthub/hosts/add-Pqq-suggestion
method: post
addActivitySuggestion:
@@ -436,7 +436,7 @@ addActivitySuggestion:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/hosts/add-Activity-suggestion
path: /minglaradmin/hosthub/hosts/add-Activity-suggestion
method: post
getAllPQPDetailsForAM:
@@ -452,7 +452,7 @@ getAllPQPDetailsForAM:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/pqp/pqp-details-for-am/{activityXid}
path: /minglaradmin/hosthub/pqp/pqp-details-for-am/{activityXid}
method: get
getSuggestionsForAM:
@@ -468,5 +468,5 @@ getSuggestionsForAM:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /hosthub/onboarding/show-suggestion-to-am/{hostXid}
path: /minglaradmin/hosthub/onboarding/show-suggestion-to-am/{hostXid}
method: get

View File

@@ -0,0 +1,29 @@
createActivityAndAllQuestionsEntry:
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/createActivityAndAllQuestionsEntry.handler
memorySize: 384
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/createActivityAndAllQuestionsEntry**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /host/Activity_Hub/OnBoarding/create-activity
method: post
submitPQAnswer:
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQAnswer.handler
memorySize: 384
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQAnswer**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /host/Activity_Hub/OnBoarding/submit-pq-answer
method: patch

View File

@@ -13,7 +13,7 @@ getAllBankAndCurrencyDetails:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /get-all-bank-currency-details
path: /prepopulate/get-all-bank-currency-details
method: get
getCityByState:
@@ -29,7 +29,7 @@ getCityByState:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /get-city-by-state
path: /prepopulate/get-city-by-state
method: get
getBranchByBankXid:
@@ -45,7 +45,7 @@ getBranchByBankXid:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /get-branch-by-bank
path: /prepopulate/get-branch-by-bank
method: get
getAllDocumentCountryStateCityDetails:
@@ -60,7 +60,7 @@ getAllDocumentCountryStateCityDetails:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /get-all-doc-country
path: /prepopulate/get-all-doc-country
method: get
getAllPqqQuesAns:
@@ -75,7 +75,7 @@ getAllPqqQuesAns:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /get-all-pqq-ques-ans
path: /prepopulate/get-all-pqq-ques-ans
method: get
getFrequenciesOfActivity:
@@ -90,7 +90,7 @@ getFrequenciesOfActivity:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /get-all-Frequencies
path: /prepopulate/get-all-Frequencies
method: get
getAddActivityPrePopulate:
@@ -105,5 +105,5 @@ getAddActivityPrePopulate:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /get-add-activity-prepopulate
path: /prepopulate/get-add-activity-prepopulate
method: get

View File

@@ -13,7 +13,7 @@ registerUser:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /register
path: /user/register
method: post
submitPersonalInfo:
@@ -28,7 +28,7 @@ submitPersonalInfo:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /submit-personal-info
path: /user/submit-personal-info
method: post
verifyOtpForUser:
@@ -43,7 +43,7 @@ verifyOtpForUser:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /verify-otp
path: /user/verify-otp
method: post
generateAccessFromRefreshToken:
@@ -58,7 +58,7 @@ generateAccessFromRefreshToken:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /generate-access-from-refresh
path: /user/generate-access-from-refresh
method: post
@@ -74,7 +74,7 @@ setPasscodeForMobile:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /set-passcode
path: /user/set-passcode
method: post
@@ -90,7 +90,7 @@ verifyPasscode:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /verify-passcode
path: /user/verify-passcode
method: post
setUserInterest:
@@ -105,7 +105,7 @@ setUserInterest:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /set-interests
path: /user/set-interests
method: post
setUserLocationss:
@@ -120,7 +120,7 @@ setUserLocationss:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /set-location-user
path: /user/set-location-user
method: post
getLandingPageDetails:
@@ -135,7 +135,7 @@ getLandingPageDetails:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/get-landing-page-details
path: /user/activities/get-landing-page-details
method: get
getSurpriseMePageDetails:
@@ -150,7 +150,7 @@ getSurpriseMePageDetails:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/get-surprise-me-page-details
path: /user/activities/get-surprise-me-page-details
method: get
getActivityDetailsById:
@@ -165,7 +165,7 @@ getActivityDetailsById:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/get-activity-details-by-id/{activity_xid}
path: /user/activities/get-activity-details-by-id/{activity_xid}
method: get
checkAvailabilityDetails:
@@ -180,7 +180,7 @@ checkAvailabilityDetails:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/check-availability/{activity_xid}
path: /user/activities/check-availability/{activity_xid}
method: get
searchActivities:
@@ -195,7 +195,7 @@ searchActivities:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/specific-search
path: /user/activities/specific-search
method: get
searchSchoolsAndCompanies:
@@ -210,7 +210,7 @@ searchSchoolsAndCompanies:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /connections/search-schools-companies
path: /user/connections/search-schools-companies
method: get
searchCities:
@@ -225,7 +225,7 @@ searchCities:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /connections/search-cities
path: /user/connections/search-cities
method: get
addSchoolCompanyDetail:
@@ -240,7 +240,7 @@ addSchoolCompanyDetail:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /connections/add-school-company
path: /user/connections/add-school-company
method: post
removeConnectionDetails:
@@ -255,7 +255,7 @@ removeConnectionDetails:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /connections/remove-connection-details
path: /user/connections/remove-connection-details
method: delete
getAllConnectionOfUser:
@@ -270,7 +270,7 @@ getAllConnectionOfUser:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /connections/get-all-connections-details
path: /user/connections/get-all-connections-details
method: get
getActivityFromConnectionsInterest:
@@ -285,22 +285,7 @@ getActivityFromConnectionsInterest:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /connections/get-activity-from-connections-interest
method: get
searchConnectionPeople:
handler: src/modules/user/handlers/connections/searchConnectionPeople.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/connections/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /connections/search-connection-people
path: /user/connections/get-activity-from-connections-interest
method: get
viewMoreActivitiesByInterest:
@@ -315,7 +300,7 @@ viewMoreActivitiesByInterest:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/view-more-activities
path: /user/activities/view-more-activities
method: get
viewMoreActivitiesUpperSection:
@@ -330,7 +315,7 @@ viewMoreActivitiesUpperSection:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/view-more-activities-upper-section
path: /user/activities/view-more-activities-upper-section
method: get
getRandomActiveActivity:
@@ -345,7 +330,7 @@ getRandomActiveActivity:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/get-random-active-activity
path: /user/activities/get-random-active-activity
method: get
getNearbyActivities:
@@ -360,125 +345,5 @@ getNearbyActivities:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/get-nearby-activities
path: /user/activities/get-nearby-activities
method: get
addActivityToBucketInterested:
handler: src/modules/user/handlers/activities/addToBucketInterested.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/activities/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/add-to-bucket-interested
method: post
removeActivityFromBucketInterested:
handler: src/modules/user/handlers/activities/removeFromBucketInterested.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/activities/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/remove-from-bucket-interested
method: post
getFilteredLandingPageAllDetails:
handler: src/modules/user/handlers/activities/filteredLandingPageAllDetails.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/get-filtered-landing-page-details
method: get
getAllBucketActivities:
handler: src/modules/user/handlers/activities/getAllBucketActivities.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/get-all-bucket-activities
method: get
getUserItineraryDetails:
handler: src/modules/user/handlers/itinerary/getUserItineraryDetails.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /itinerary/get-user-itinerary-details
method: get
saveUserItinerary:
handler: src/modules/user/handlers/itinerary/saveUserItinerary.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /itinerary/save-user-itinerary
method: post
getAllUserSavedItineraries:
handler: src/modules/user/handlers/itinerary/getAllUserSavedItineraries.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /itinerary/get-all-user-saved-itineraries
method: get
getMatchingBucketInterestedActivities:
handler: src/modules/user/handlers/itinerary/getMatchingBucketInterestedActivities.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /itinerary/get-matching-bucket-interested-activities
method: post

View File

@@ -55,7 +55,7 @@ export const ACTIVITY_DISPLAY_STATUS = {
PQ_IN_REVIEW: 'PQ In Review',
PQ_APPROVED: 'PQ Approved',
ACTIVITY_DRAFT: 'Draft',
ACTIVITY_DRAFT: 'Draft - Activity',
ACTIVITY_IN_REVIEW: 'In Review',
ACTIVITY_TO_REVIEW: 'Re-submitted',
NOT_LISTED: 'Not Listed',
@@ -94,7 +94,7 @@ export const ACTIVITY_AM_DISPLAY_STATUS = {
PQ_APPROVED: 'PQ Approved',
REVISED: 'Revised',
ACTIVITY_DRAFT: 'Draft',
ACTIVITY_DRAFT: 'Draft - Activity',
ACTIVITY_NEW: 'New',
ACTIVITY_TO_REVIEW: 'Activity To Review',
ACTIVITY_ENHANCING: 'Enhancing',

View File

@@ -54,7 +54,7 @@ export const EquipmentDto = z.object({
/* ================= NAVIGATION MODE ================= */
export const NavigationModeDto = z.object({
navigationModeName: z.string().optional(),
navigationModeXid: z.number().int(),
isChargeable: z.boolean().optional(),
totalPrice: z.number().int().optional().default(0),
});

View File

@@ -8,7 +8,6 @@ import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHo
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service';
import { sendPQPEmailToAM } from '../../../services/sendHostResubmitEmailToAM.service';
const hostService = new HostService(prismaClient);
@@ -178,15 +177,6 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
const getAllUpdatedQuestionResponse = await hostService.getAllPQUpdatedResponse(activityXid)
const details = await hostService.getSuggestionDetails(user.id);
await sendPQPEmailToAM(
details.hostDetails.accountManager.emailAddress,
details.hostDetails.accountManager.firstName,
details.hostDetails.companyName,
details.hostDetails.user.userRefNumber,
)
// CASE 2 — NO deletion & NO new files => DO NOTHING to existing files
return {

View File

@@ -1,6 +1,6 @@
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service';
@@ -25,8 +25,9 @@ export const handler = safeHandler(async (
// Verify token and get user info
const userInfo = await verifyHostToken(token);
// Accept agreement and get dynamic fields and PDF URL
const result = await hostService.acceptMinglarAgreement(userInfo.id);
// Add suggestion using service
await hostService.acceptMinglarAgreement(userInfo.id);
return {
statusCode: 200,
@@ -37,10 +38,7 @@ export const handler = safeHandler(async (
body: JSON.stringify({
success: true,
message: 'Application accepted successfully',
data: {
filePath: result.filePath,
dynamicFields: result.dynamicFields,
},
data: null,
}),
};
});

View File

@@ -1,54 +0,0 @@
import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service';
const hostService = new HostService(prismaClient);
/**
* Get latest active agreement for a specific host by hostXid.
* Accessible for Minglar Admin / Host Admin using admin-host token.
*/
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Validate admin/host admin token
await verifyMinglarAdminHostToken(token);
const hostXidParam =
event.queryStringParameters?.hostXid ?? event.queryStringParameters?.host_xid;
const hostXid = Number(hostXidParam);
if (!hostXidParam) {
throw new ApiError(400, 'hostXid is required');
}
if (Number.isNaN(hostXid)) {
throw new ApiError(400, 'Invalid hostXid format');
}
const agreement = await hostService.getLatestHostAgreement(hostXid);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Latest host agreement retrieved successfully',
data: agreement,
}),
};
});

View File

@@ -39,7 +39,6 @@ export const handler = safeHandler(async (
data: {
stepper: host?.host?.stepper || null,
emailAddress: host.user?.emailAddress || null,
hostId: host.user?.userRefNumber || null,
},
}),
};

View File

@@ -1,344 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import AWS from 'aws-sdk';
import dayjs from 'dayjs';
import { z } from 'zod';
import { prismaClient } from '../../../common/database/prisma.lambda.service';
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost';
import { ROLE } from '../../../common/utils/constants/common.constant';
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from "../../../common/utils/helper/ApiError";
import { parseMultipartFormData } from '../../../common/utils/helper/parseMultipartFormData';
import config from '../../../config/config';
const s3 = new AWS.S3({
region: config.aws.region,
});
const updateHostProfileSchema = z
.strictObject({
// Personal
fullName: z.string().min(1).optional(),
firstName: z.string().min(1).optional(),
lastName: z.string().min(1).optional(),
isdCode: z.string().min(1).max(6).optional(),
mobileNumber: z.string().min(5).max(15).optional(),
dateOfBirth: z.string().min(1).optional(),
profileImage: z.string().url().optional(),
// Address
address1: z.string().min(1).optional(),
address2: z.string().min(1).optional(),
countryXid: z.number().int().positive().optional(),
stateXid: z.number().int().positive().optional(),
cityXid: z.number().int().positive().optional(),
pinCode: z.string().min(1).optional(),
// explicitly forbidden
emailAddress: z.any().optional(),
})
.strip();
async function uploadProfileImageToS3(buffer: Buffer, mimeType: string, originalName: string, userId: number) {
const sanitizeFileName = (name: string) => {
return name
.toLowerCase()
.replace(/[^a-z0-9.]/g, '_')
.replace(/_+/g, '_')
.replace(/^_+|_+$/g, '');
};
const fileExtension = originalName.split('.').pop() || 'jpg';
const fileName = `profile_image.${fileExtension}`;
const sanitizedFileName = sanitizeFileName(fileName);
const s3Key = `Host/ProfileImages/${userId}/${sanitizedFileName}`;
await s3
.upload({
Bucket: config.aws.bucketName,
Key: s3Key,
Body: buffer,
ContentType: mimeType,
ACL: 'private',
})
.promise();
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
}
function parseDob(dateOfBirth: string): Date {
const parsed = dayjs(dateOfBirth, ['YYYY-MM-DD', 'MM/DD/YYYY', 'DD/MM/YYYY'], true);
if (!parsed.isValid()) {
throw new ApiError(400, 'Invalid dateOfBirth. Use YYYY-MM-DD (recommended) or MM/DD/YYYY.');
}
return parsed.toDate();
}
function splitFullName(fullName: string): { firstName: string; lastName: string | null } {
const parts = fullName.trim().split(/\s+/).filter(Boolean);
const firstName = parts[0] || '';
const lastName = parts.length > 1 ? parts.slice(1).join(' ') : null;
return { firstName, lastName };
}
function getAuthToken(event: APIGatewayProxyEvent): string {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
return token;
}
function parseJsonBody(event: APIGatewayProxyEvent): any {
try {
return event.body ? JSON.parse(event.body) : {};
} catch {
throw new ApiError(400, 'Invalid JSON in request body');
}
}
function validateBody(body: any) {
const parsed = updateHostProfileSchema.safeParse(body);
if (!parsed.success) {
throw new ApiError(400, parsed.error.issues.map((i) => i.message).join(', '));
}
if (parsed.data.emailAddress !== undefined) {
throw new ApiError(400, 'Email address cannot be updated.');
}
return parsed.data;
}
function normalizeNameFields(data: any): { firstName?: string; lastName?: string | null } {
if (data.fullName && !data.firstName && !data.lastName) {
const split = splitFullName(data.fullName);
return { firstName: split.firstName, lastName: split.lastName };
}
return { firstName: data.firstName, lastName: data.lastName };
}
function buildAddressInput(data: any) {
return {
address1: data.address1,
address2: data.address2,
countryXid: data.countryXid,
stateXid: data.stateXid,
cityXid: data.cityXid,
pinCode: data.pinCode,
};
}
function hasAnyDefined(obj: Record<string, unknown>) {
return Object.values(obj).some((v) => v !== undefined);
}
async function ensureHostUser(tx: any, userId: number) {
const user = await tx.user.findUnique({
where: { id: userId, isActive: true },
select: { id: true, roleXid: true },
});
if (!user) throw new ApiError(404, 'User not found');
if (user.roleXid !== ROLE.HOST) throw new ApiError(403, 'Access denied.');
}
async function updateUserIfNeeded(
tx: any,
userId: number,
input: {
firstName?: string;
lastName?: string | null;
isdCode?: string;
mobileNumber?: string;
dateOfBirth?: string;
profileImage?: string;
},
) {
const userUpdateData: any = {};
if (input.firstName !== undefined) userUpdateData.firstName = input.firstName || null;
if (input.lastName !== undefined) userUpdateData.lastName = input.lastName;
if (input.isdCode !== undefined) userUpdateData.isdCode = input.isdCode || null;
if (input.mobileNumber !== undefined) userUpdateData.mobileNumber = input.mobileNumber || null;
if (input.dateOfBirth !== undefined) {
userUpdateData.dateOfBirth = input.dateOfBirth ? parseDob(input.dateOfBirth) : null;
}
if (input.profileImage !== undefined) {
userUpdateData.profileImage = input.profileImage || null;
}
if (!hasAnyDefined(userUpdateData)) return;
await tx.user.update({
where: { id: userId },
data: {
...userUpdateData,
isProfileUpdated: true,
},
});
}
async function upsertAddressIfNeeded(tx: any, userId: number, addressData: Record<string, any>) {
if (!hasAnyDefined(addressData)) return;
const existingAddress = await tx.userAddressDetails.findFirst({
where: { userXid: userId, isActive: true },
select: { id: true },
});
const addressUpdateData: any = {};
if (addressData.address1 !== undefined) addressUpdateData.address1 = addressData.address1;
if (addressData.address2 !== undefined) addressUpdateData.address2 = addressData.address2;
if (addressData.countryXid !== undefined) addressUpdateData.countryXid = addressData.countryXid;
if (addressData.stateXid !== undefined) addressUpdateData.stateXid = addressData.stateXid;
if (addressData.cityXid !== undefined) addressUpdateData.cityXid = addressData.cityXid;
if (addressData.pinCode !== undefined) addressUpdateData.pinCode = addressData.pinCode;
if (existingAddress) {
await tx.userAddressDetails.update({
where: { id: existingAddress.id },
data: addressUpdateData,
});
return;
}
const required = ['address1', 'countryXid', 'stateXid', 'cityXid', 'pinCode'] as const;
const missing = required.filter((k) => addressData[k] === undefined);
if (missing.length) {
throw new ApiError(400, `Missing required address fields: ${missing.join(', ')}`);
}
await tx.userAddressDetails.create({
data: {
userXid: userId,
...addressUpdateData,
},
});
}
async function getProfileSnapshot(tx: any, userId: number) {
const updated = await tx.user.findUnique({
where: { id: userId },
select: {
id: true,
firstName: true,
lastName: true,
emailAddress: true,
isdCode: true,
mobileNumber: true,
dateOfBirth: true,
profileImage: true,
isProfileUpdated: true,
userAddressDetails: {
where: { isActive: true },
take: 1,
select: {
id: true,
address1: true,
address2: true,
countryXid: true,
stateXid: true,
cityXid: true,
pinCode: true,
},
},
},
});
return {
user: updated,
address: updated?.userAddressDetails?.[0] ?? null,
};
}
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
const token = getAuthToken(event);
const userInfo = await verifyHostToken(token);
const userId = Number(userInfo.id);
if (!userId || Number.isNaN(userId)) {
throw new ApiError(400, 'Invalid user id');
}
const contentType = event.headers['Content-Type'] || event.headers['content-type'] || '';
const isMultipart = contentType.includes('multipart/form-data');
let body: any;
if (isMultipart) {
const isBase64Encoded = event.isBase64Encoded || false;
const { fields, files } = parseMultipartFormData(event.body || null, contentType, isBase64Encoded);
const multipartBody: any = {};
const copyIfPresent = (key: string) => {
if (fields[key] !== undefined) {
multipartBody[key] = fields[key];
}
};
['fullName', 'firstName', 'lastName', 'isdCode', 'mobileNumber', 'dateOfBirth', 'address1', 'address2', 'pinCode'].forEach(
copyIfPresent,
);
const parseNumberField = (key: string) => {
if (fields[key] !== undefined) {
const value = Number(fields[key]);
if (!Number.isNaN(value)) {
multipartBody[key] = value;
}
}
};
['countryXid', 'stateXid', 'cityXid'].forEach(parseNumberField);
const profileImageFile = files.find((f) => f.fieldName === 'profileImage');
if (profileImageFile) {
const uploadedUrl = await uploadProfileImageToS3(
profileImageFile.data,
profileImageFile.contentType,
profileImageFile.fileName,
userId,
);
multipartBody.profileImage = uploadedUrl;
} else if (fields.profileImage) {
multipartBody.profileImage = fields.profileImage;
}
body = multipartBody;
} else {
body = parseJsonBody(event);
}
const data = validateBody(body);
const name = normalizeNameFields(data);
const address = buildAddressInput(data);
const result = await prismaClient.$transaction(async (tx) => {
await ensureHostUser(tx, userId);
await updateUserIfNeeded(tx, userId, {
firstName: name.firstName,
lastName: name.lastName,
isdCode: data.isdCode,
mobileNumber: data.mobileNumber,
dateOfBirth: data.dateOfBirth,
profileImage: data.profileImage,
});
await upsertAddressIfNeeded(tx, userId, address);
return getProfileSnapshot(tx, userId);
});
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Profile updated successfully',
data : null// no data payload per request
}),
};
});

View File

@@ -362,9 +362,6 @@ export class SchedulingService {
isActive: true,
startDate: { lte: date },
OR: [{ endDate: null }, { endDate: { gte: date } }],
ScheduleDetails: {
some: {}
}
},
include: {
activityVenue: {

View File

@@ -177,8 +177,8 @@ function computeBasePriceAndTaxes(
return { basePrice, taxDetails };
}
const normalize = (v?: string | null, maxLength: number = 50) =>
v ? v.trim().toLowerCase().substring(0, maxLength) : null;
const normalize = (v?: string | null) =>
v ? v.trim().toLowerCase() : null;
async function renderAgreementPdf(vars: {
effectiveDate: string;
@@ -338,11 +338,9 @@ const findOrCreateState = async (
) => {
if (!stateName || !countryXid) return null;
const trimmedStateName = stateName.trim().substring(0, 50);
const state = await tx.states.findFirst({
where: {
stateName: { equals: trimmedStateName, mode: 'insensitive' },
stateName: { equals: stateName.trim(), mode: 'insensitive' },
countryXid,
isActive: true,
},
@@ -352,7 +350,7 @@ const findOrCreateState = async (
const created = await tx.states.create({
data: {
stateName: trimmedStateName,
stateName: stateName.trim(),
countryXid,
},
});
@@ -367,11 +365,9 @@ const findOrCreateCity = async (
) => {
if (!cityName || !stateXid) return null;
const trimmedCityName = cityName.trim().substring(0, 50);
const city = await tx.cities.findFirst({
where: {
cityName: { equals: trimmedCityName, mode: 'insensitive' },
cityName: { equals: cityName.trim(), mode: 'insensitive' },
stateXid,
isActive: true,
},
@@ -381,7 +377,7 @@ const findOrCreateCity = async (
const created = await tx.cities.create({
data: {
cityName: trimmedCityName,
cityName: cityName.trim(),
stateXid,
},
});
@@ -395,22 +391,6 @@ const s3 = new AWS.S3({
region: config.aws.region,
});
type UpdateHostProfileInput = {
firstName?: string;
lastName?: string | null;
isdCode?: string;
mobileNumber?: string;
dateOfBirth?: Date;
address?: {
address1?: string;
address2?: string;
countryXid?: number;
stateXid?: number;
cityXid?: number;
pinCode?: string;
};
};
@Injectable()
export class HostService {
constructor(private prisma: PrismaClient) { }
@@ -435,8 +415,8 @@ export class HostService {
});
const user = await this.prisma.user.findUnique({
where: { id: user_xid, isActive: true },
select: { id: true, emailAddress: true, userRefNumber: true },
where: { id: user_xid },
select: { id: true, emailAddress: true },
});
return { host, user };
}
@@ -446,46 +426,7 @@ export class HostService {
where: { userXid: id },
include: {
hostParent: {
select: {
id: true,
logoPath: true,
companyName: true,
address1: true,
address2: true,
cities: {
select: {
id: true,
cityName: true
}
},
states: {
select: {
id: true,
stateName: true
}
},
countries: {
select: {
id: true,
countryName: true
}
},
pinCode: true,
registrationNumber: true,
panNumber: true,
gstNumber: true,
formationDate: true,
companyTypes: {
select: {
id: true,
companyTypeName: true
}
},
websiteUrl: true,
instagramUrl: true,
facebookUrl: true,
linkedinUrl: true,
twitterUrl: true,
include: {
HostParenetDocuments: {
select: {
id: true,
@@ -524,39 +465,6 @@ export class HostService {
profileImage: true,
userStatus: true,
userRefNumber: true,
userAddressDetails: {
where: { isActive: true },
select: {
id: true,
address1: true,
address2: true,
locationAddress: true,
locationLat: true,
locationLong: true,
pinCode: true,
cityXid: true,
cities: {
select: {
id: true,
cityName: true,
}
},
stateXid: true,
states: {
select: {
id: true,
stateName: true,
}
},
countryXid: true,
country: {
select: {
id: true,
countryName: true
}
}
}
}
},
},
companyTypes: {
@@ -669,114 +577,6 @@ export class HostService {
return this.prisma.user.delete({ where: { id } });
}
/**
* Update the logged-in Host's personal profile details.
* Email is intentionally NOT editable here.
*/
async updateHostProfileDetails(userId: number, input: UpdateHostProfileInput) {
return this.prisma.$transaction(async (tx) => {
const user = await tx.user.findUnique({
where: { id: userId, isActive: true },
select: { id: true, roleXid: true },
});
if (!user) throw new ApiError(404, 'User not found');
if (user.roleXid !== ROLE.HOST) throw new ApiError(403, 'Access denied.');
// 1) Update `User` (whitelist only)
const userUpdateData: any = {};
if (input.firstName !== undefined) userUpdateData.firstName = input.firstName || null;
if (input.lastName !== undefined) userUpdateData.lastName = input.lastName;
if (input.isdCode !== undefined) userUpdateData.isdCode = input.isdCode || null;
if (input.mobileNumber !== undefined) userUpdateData.mobileNumber = input.mobileNumber || null;
if (input.dateOfBirth !== undefined) userUpdateData.dateOfBirth = input.dateOfBirth;
if (Object.keys(userUpdateData).length > 0) {
await tx.user.update({
where: { id: userId },
data: {
...userUpdateData,
isProfileUpdated: true,
},
});
}
// 2) Update/Create `UserAddressDetails` (if any address field sent)
const addressData = input.address || {};
const hasAnyAddressField = Object.values(addressData).some((v) => v !== undefined);
if (hasAnyAddressField) {
const existingAddress = await tx.userAddressDetails.findFirst({
where: { userXid: userId, isActive: true },
select: { id: true },
});
const addressUpdateData: any = {};
if (addressData.address1 !== undefined) addressUpdateData.address1 = addressData.address1;
if (addressData.address2 !== undefined) addressUpdateData.address2 = addressData.address2;
if (addressData.countryXid !== undefined) addressUpdateData.countryXid = addressData.countryXid;
if (addressData.stateXid !== undefined) addressUpdateData.stateXid = addressData.stateXid;
if (addressData.cityXid !== undefined) addressUpdateData.cityXid = addressData.cityXid;
if (addressData.pinCode !== undefined) addressUpdateData.pinCode = addressData.pinCode;
if (existingAddress) {
await tx.userAddressDetails.update({
where: { id: existingAddress.id },
data: addressUpdateData,
});
} else {
const required = ['address1', 'countryXid', 'stateXid', 'cityXid', 'pinCode'] as const;
const missing = required.filter((k) => addressData[k] === undefined);
if (missing.length) {
throw new ApiError(400, `Missing required address fields: ${missing.join(', ')}`);
}
await tx.userAddressDetails.create({
data: {
userXid: userId,
...addressUpdateData,
},
});
}
}
// 3) Return updated profile snapshot (including read-only email)
const updated = await tx.user.findUnique({
where: { id: userId },
select: {
id: true,
firstName: true,
lastName: true,
emailAddress: true,
isdCode: true,
mobileNumber: true,
dateOfBirth: true,
profileImage: true,
isProfileUpdated: true,
userAddressDetails: {
where: { isActive: true },
take: 1,
select: {
id: true,
address1: true,
address2: true,
countryXid: true,
stateXid: true,
cityXid: true,
pinCode: true,
},
},
},
});
return {
user: updated,
address: updated?.userAddressDetails?.[0] ?? null,
};
});
}
async getHostByEmail(email: string): Promise<User> {
return this.prisma.user.findUnique({ where: { emailAddress: email } });
}
@@ -1119,9 +919,6 @@ export class HostService {
acceptDate,
};
let pdfUrl: string | null = null;
try {
const pdfBuffer = await renderAgreementPdf(agreementVars);
const existingCount = await this.prisma.hostAgreement.count({
@@ -1143,18 +940,7 @@ export class HostService {
})
.promise();
pdfUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${pdfKey}`;
} catch (error) {
console.error('Error generating or uploading PDF:', error);
// Continue without PDF - will return dynamic fields instead
}
try {
const existingCount = await this.prisma.hostAgreement.count({
where: { hostXid: host.id, isActive: true },
});
const nextVersionNumber = `AG${existingCount + 1}`;
const pdfUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${pdfKey}`;
await this.prisma.$transaction(async (tx) => {
// Optional: mark previous agreements inactive
@@ -1182,87 +968,6 @@ export class HostService {
},
});
});
} catch (error) {
console.error('Error creating host agreement record:', error);
// Continue without creating agreement record - will return dynamic fields instead
}
// Return dynamic fields and PDF URL
return {
filePath: pdfUrl,
dynamicFields: agreementVars,
};
}
/**
* Get the latest (active) agreement for a specific host by hostXid.
*/
async getLatestHostAgreement(hostXid: number) {
if (!hostXid || Number.isNaN(hostXid)) {
throw new ApiError(400, 'Valid hostXid is required');
}
const hostHeader = await this.prisma.hostHeader.findFirst({
where: { id: hostXid, isActive: true },
select: {
id: true,
isCommisionBase: true,
commisionPer: true,
durationNumber: true,
durationFrequency: true,
amountPerBooking: true,
agreementStartDate: true,
payoutDurationNum: true,
payoutDurationFrequency: true,
registrationNumber: true,
companyName: true,
companyTypes: {
select: {
id: true,
companyTypeName: true
}
}
}
});
const agreement = await this.prisma.hostAgreement.findFirst({
where: { hostXid, isActive: true },
orderBy: { createdAt: 'desc' },
select: {
id: true,
hostXid: true,
filePath: true,
versionNumber: true,
createdAt: true,
updatedAt: true,
},
});
// ❌ If both missing
if (!agreement && !hostHeader) {
throw new ApiError(404, 'No active agreement found for this host');
}
let presignedUrl = "";
if (agreement?.filePath) {
const key = agreement.filePath.startsWith('http')
? agreement.filePath.split('.com/')[1]
: agreement.filePath;
const bucket = config.aws.bucketName;
presignedUrl = await getPresignedUrl(bucket, key);
}
return {
hostHeader: hostHeader || null,
agreement: agreement
? {
...agreement,
presignedUrl
}
: null
};
}
async getPQQQuestionDetail(question_xid: number, activity_xid: number) {
@@ -1434,7 +1139,7 @@ export class HostService {
hostStatusDisplay = HOST_STATUS_DISPLAY.UNDER_REVIEW;
minglarStatusInternal = MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW;
minglarStatusDisplay = MINGLAR_STATUS_DISPLAY.RE_SUBMITTED;
minglarStatusDisplay = MINGLAR_STATUS_DISPLAY.TO_REVIEW;
}
// CASE 2: Admin has rejected but host can resubmit
else if (
@@ -2669,9 +2374,15 @@ export class HostService {
},
select: {
id: true,
navigationModeName: true,
isInActivityChargeable: true,
navigationModesTotalPrice: true,
navigationMode: {
select: {
id: true,
navigationModeName: true,
navigationModeIcon: true,
},
},
},
},
equipmentAvailable: true,
@@ -3376,34 +3087,6 @@ export class HostService {
throw new ApiError(404, 'Activity not found');
}
const normalizedActivityTitle =
typeof payload.activityTitle === 'string'
? payload.activityTitle.trim()
: '';
if (normalizedActivityTitle) {
payload.activityTitle = normalizedActivityTitle;
const duplicateActivity = await tx.activities.findFirst({
where: {
id: { not: existingActivity.id },
isActive: true,
activityTitle: {
equals: normalizedActivityTitle,
mode: 'insensitive',
},
},
select: { id: true },
});
if (duplicateActivity) {
throw new ApiError(
400,
'Same activity name already exists. Please choose a different name.',
);
}
}
/* --------------------------------
* 3⃣ STATUS DECISION
* -------------------------------- */
@@ -4023,7 +3706,7 @@ export class HostService {
const navMode = await tx.activityNavigationModes.create({
data: {
activityXid,
navigationModeName: mode.navigationModeName,
navigationModeXid: mode.navigationModeXid,
isInActivityChargeable: isChargeable,
navigationModesBasePrice: basePrice,
navigationModesTotalPrice: totalPrice,

View File

@@ -76,41 +76,3 @@ export async function sendEmailToMinglarAdmin(
throw new ApiError(500, "Failed to send OTP to host via email.");
}
}
export async function sendPQPEmailToAM(
emailAddress: string,
minglarAdminName: string,
hostCompanyName: string,
hostRefNumber: string
): Promise<{
sent: boolean;
// messageId: string
}> {
const subject = `New Pre-qualification Questionnaire from : ${hostCompanyName}`;
const htmlContent = `
<p>Dear ${minglarAdminName},</p>
<p>Host ${hostCompanyName} with reference number: <strong>${hostRefNumber}</strong> has submited their pre-qualification questionnaire.</p>
<p>Please review their appliaction and take the necessary action.</p>
<p>Best regards,<br/>Minglar Team</p>
`;
try {
const result = await brevoService.sendEmail({
recipients: [{ email: emailAddress }],
subject,
htmlContent,
});
// console.log("📧 Email sent successfully:", result);
return {
sent: true,
// messageId: result.messageId
};
} catch (err) {
console.error("Brevo email send failed:", err);
throw new ApiError(500, "Failed to send OTP to host via email.");
}
}

View File

@@ -53,10 +53,10 @@ export class TokenService {
config.jwt.secret
);
// Optionally keep existing refresh tokens alive instead of deleting
// Removed deleteMany call so the same refresh token can be used multiple
// times. If you want to limit refresh tokens later you can implement
// rotation or blacklist logic elsewhere.
await this.prisma.token.deleteMany({
where: { userXid: user_xid }
})
await this.prisma.token.create({
data: {
token: refreshToken.token,

View File

@@ -34,7 +34,7 @@ const bucket = config.aws.bucketName;
@Injectable()
export class MinglarService {
constructor(private prisma: PrismaService | PrismaClient) { }
constructor(private prisma: PrismaService | PrismaClient) {}
async createPassword(user_xid: number, password: string): Promise<boolean> {
// Find user by id
@@ -314,8 +314,6 @@ export class MinglarService {
companyName: true,
user: {
select: {
firstName: true,
lastName: true,
userRefNumber: true,
},
},
@@ -377,52 +375,11 @@ export class MinglarService {
const {
paginationService,
} = require('@/common/utils/pagination/pagination.service');
let hostDetails = null;
if (hostXid) {
hostDetails = await this.prisma.hostHeader.findUnique({
where: { id: hostXid },
select: {
companyName: true,
user: {
select: {
firstName: true,
lastName: true,
userRefNumber: true,
},
},
},
});
}
const paginatedResponse = paginationService.createPaginatedResponse(
return paginationService.createPaginatedResponse(
hostActivities,
totalCount,
paginationOptions || { page: 1, limit: 10, skip: 0 },
);
// 👇 ADD THIS BLOCK
if (hostActivities.length === 0 && hostDetails) {
paginatedResponse.data = [
{
id: null,
activityRefNumber: null,
activityTitle: null,
totalScore: null,
activityInternalStatus: null,
activityDisplayStatus: null,
amInternalStatus: null,
amDisplayStatus: null,
createdAt: null,
host: hostDetails,
ActivityAmDetails: [],
activityType: null,
},
];
}
return paginatedResponse;
}
async createUserRevenue(
@@ -875,9 +832,9 @@ export class MinglarService {
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
display: MINGLAR_STATUS_DISPLAY.NEW,
},
Re_Submitted: {
To_Review: {
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
display: MINGLAR_STATUS_DISPLAY.RE_SUBMITTED,
display: MINGLAR_STATUS_DISPLAY.TO_REVIEW,
},
Enhancing: {
internal: MINGLAR_STATUS_INTERNAL.AM_REJECTED,
@@ -988,7 +945,6 @@ export class MinglarService {
const where: any = {
isActive: true,
hostStatusInternal: { notIn: [HOST_STATUS_INTERNAL.DRAFT] },
adminStatusInternal: { notIn: [MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW] },
};
if (search?.trim()) {
@@ -1755,7 +1711,6 @@ export class MinglarService {
isEmailVerfied: true,
isMobileVerfied: true,
isBiometric: true,
createdAt: true,
userAddressDetails: {
select: {
id: true,

View File

@@ -27,21 +27,15 @@ export const handler = safeHandler(async (
// 2) Authenticate user
await verifyMinglarAdminHostToken(token);
// 3) Get stateXid and optional search term from query params
// 3) Get bankXid from query params
const stateXid = Number(event.queryStringParameters?.stateXid);
const search = event.queryStringParameters?.search?.trim();
if (!stateXid || isNaN(stateXid)) {
throw new ApiError(400, "Valid stateXid is required in query params.");
}
// If search is provided, enforce minimum 3 characters
if (search && search.length < 3) {
throw new ApiError(400, "Search term must be at least 3 characters long.");
}
// 4) Fetch cities for the state (optionally filtered by search)
const branches = await prePopulateService.getCityByStateId(stateXid, search);
// 4) Fetch branches for the bank
const branches = await prePopulateService.getCityByStateId(stateXid);
return {
statusCode: 200,

View File

@@ -39,20 +39,12 @@ export class PrePopulateService {
}
async getCityByStateId(stateXid: number, search?: string) {
async getCityByStateId(stateXid: number) {
return await this.prisma.cities.findMany({
where: {
stateXid,
isActive: true,
deletedAt: null,
...(search && search.length >= 3
? {
cityName: {
contains: search,
mode: 'insensitive',
},
}
: {}),
deletedAt: null
},
select: {
id: true,
@@ -140,9 +132,7 @@ export class PrePopulateService {
}),
]);
const adminEmail = config.MinglarAdminEmail;
return { documentDetails, countryDetails, stateDetails, companyTypeDetails, adminEmail };
return { documentDetails, countryDetails, stateDetails, companyTypeDetails };
}
async getAllFrequencies() {
@@ -163,6 +153,7 @@ export class PrePopulateService {
foodType,
cuisineDetails,
vehicleType,
navigationMode,
taxDetails,
energyLevel,
aminitiesDetails,
@@ -180,6 +171,9 @@ export class PrePopulateService {
this.prisma.transportModes.findMany({
where: { isActive: true },
}),
this.prisma.navigationModes.findMany({
where: { isActive: true },
}),
this.prisma.taxes.findMany({
where: { isActive: true },
}),
@@ -221,6 +215,7 @@ export class PrePopulateService {
foodType,
cuisineDetails,
vehicleType,
navigationMode,
taxDetails,
energyLevel,
aminitiesDetails,

View File

@@ -1,74 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Authenticate user using verifyUserToken
const userInfo = await verifyUserToken(token);
const userId = userInfo.id;
if (Number.isNaN(userId)) {
throw new ApiError(400, 'User id must be a number');
}
const user = await userService.getUserById(userId);
if (!user) {
throw new ApiError(404, 'User not found');
}
// Parse request body
let body: { activityXid: number; isBucket: boolean; bucketTypeName: string; };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { activityXid, isBucket, bucketTypeName } = body;
// Validate required fields
if (
typeof activityXid !== 'number' ||
typeof isBucket !== 'boolean' ||
!bucketTypeName
) {
throw new ApiError(400, 'Required fields missing or invalid');
}
// Set the passcode
const counts = await userService.addToBucketInterested(userId, isBucket, bucketTypeName, activityXid);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: `Activity added to ${isBucket ? 'bucket' : 'interested'} successfully`,
data: {
bucketCount: counts.bucketCount,
interestedCount: counts.interestedCount,
coverImage: counts.coverImage,
coverImagePresignedUrl: counts.coverImagePresignedUrl,
}
}),
};
});

View File

@@ -50,22 +50,14 @@ export const handler = safeHandler(async (
const activity = activityDetails.activity;
// Rooms: combine ActivityVenues with their respective slots for the selected date
const Venues = (activity.ActivityVenues || [])
.map((v: any) => {
const header = scheduleDetails.find(
(h: any) => h.activityVenue?.venueXid === v.id
);
const Venues = (activity.ActivityVenues || []).map((v: any) => {
const header = scheduleDetails.find((h: any) => h.activityVenue?.venueXid === v.id);
if (!header || !header.slots?.length) {
return null; // ❌ venue has no slots for selected date
}
const roomSlots = header.slots.map((s: any) => {
let status = "Available";
if (s.maxCapacity === 0) status = "Housefull";
else if (s.maxCapacity <= 2) status = "2 Slots Left";
else if (s.maxCapacity <= 5) status = "Fast Filling";
const roomSlots = (header?.slots || []).map((s: any) => {
let status = 'Available';
if (s.maxCapacity === 0) status = 'Housefull';
else if (s.maxCapacity <= 2) status = '2 Slots Left';
else if (s.maxCapacity <= 5) status = 'Fast Filling';
return {
slotId: s.slotId,
@@ -89,34 +81,33 @@ export const handler = safeHandler(async (
venueMedia: (v.ActivityVenueArtifacts || []).map((media: any) => ({
id: media.id,
mediaType: media.mediaType,
mediaFileName: media.mediaFileName,
presignedUrl: media.presignedUrl,
mediaFileName: media.mediaFileName, // original S3 key / URL
presignedUrl: media.presignedUrl, // presigned URL
})),
};
})
.filter(Boolean); // ✅ removes null venues
});
// derive check-in/out from all room slots (earliest start, latest end)
const allSlots = Venues.flatMap(r => r.slots || []);
const startTimes = allSlots.map(s => s.startTime).filter(Boolean);
const endTimes = allSlots.map(s => s.endTime).filter(Boolean);
const checkInTime = startTimes.length ? startTimes.sort()[0] : null;
const checkOutTime = endTimes.length ? endTimes.sort().reverse()[0] : null;
// derive check-in/out from all room slots (earliest start, latest end)
const allSlots = Venues.flatMap(r => r.slots || []);
const startTimes = allSlots.map(s => s.startTime).filter(Boolean);
const endTimes = allSlots.map(s => s.endTime).filter(Boolean);
const checkInTime = startTimes.length ? startTimes.sort()[0] : null;
const checkOutTime = endTimes.length ? endTimes.sort().reverse()[0] : null;
const responsePayload = {
const responsePayload = {
selectedDate,
Venues,
checkInTime,
checkOutTime,
};
};
return {
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({ success: true, data: responsePayload }),
};
};
});

View File

@@ -1,74 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { FilteredLandingPageService } from '../../services/filteredLandingPage.service';
const filteredLandingPageService = new FilteredLandingPageService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
const page = Number(event.queryStringParameters?.page ?? 1);
const limit = Number(event.queryStringParameters?.limit ?? 20);
const countryName = event.queryStringParameters?.countryName ?? '';
const stateName = event.queryStringParameters?.stateName ?? '';
const cityName = event.queryStringParameters?.cityName ?? '';
const userLat = event.queryStringParameters?.userLat ?? '';
const userLong = event.queryStringParameters?.userLong ?? '';
let activityTypeXids: number[] | undefined;
if (event.queryStringParameters?.activityTypeXids) {
try {
activityTypeXids = JSON.parse(event.queryStringParameters.activityTypeXids);
} catch (error) {
// Handle invalid JSON if needed
}
}
if (page < 1 || limit < 1) {
throw new ApiError(400, 'Invalid pagination values');
}
// Fetch filtered landing page details
const result = await filteredLandingPageService.getFilteredLandingPageAllDetails(
userId,
page,
limit,
countryName,
stateName,
cityName,
userLat,
userLong,
activityTypeXids
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Filtered landing page data retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,46 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
// Fetch user with their HostHeader stepper info
const result = await userService.getAllBucketActivities(
userId
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data: result,
}),
};
});

View File

@@ -27,16 +27,16 @@ export const handler = safeHandler(async (
const longParam = event.queryStringParameters?.long ?? event.queryStringParameters?.lng ?? event.queryStringParameters?.longitude;
const radiusParam = event.queryStringParameters?.radiusKm ?? event.queryStringParameters?.radius;
const userLat = latParam ? Number(latParam) : undefined;
const userLong = longParam ? Number(longParam) : undefined;
const radiusKm = radiusParam ? Number(radiusParam) : 15; // default 15km
if (!latParam || !longParam || !radiusParam) {
throw new ApiError(400, 'lat, long and radiusKm (in km) are required as query parameters');
}
if (
(userLat !== undefined && Number.isNaN(userLat)) ||
(userLong !== undefined && Number.isNaN(userLong)) ||
Number.isNaN(radiusKm)
) {
throw new ApiError(400, 'Invalid lat/long values');
const userLat = Number(latParam);
const userLong = Number(longParam);
const radiusKm = Number(radiusParam);
if (Number.isNaN(userLat) || Number.isNaN(userLong) || Number.isNaN(radiusKm)) {
throw new ApiError(400, 'lat, long and radiusKm must be valid numbers');
}
const page = Number(event.queryStringParameters?.page ?? 1);

View File

@@ -26,11 +26,19 @@ export const handler = safeHandler(async (
}
// Extract query parameters for search
const activityTitle = event.queryStringParameters?.activityTitle?.trim();
const activityType = event.queryStringParameters?.activityType?.trim();
const checkInCity = event.queryStringParameters?.checkInCity?.trim();
// At least one search parameter should be provided
if (!activityTitle && !activityType && !checkInCity) {
throw new ApiError(400, 'At least one search parameter (activityTitle, activityType, or checkInCity) must be provided');
}
// Fetch activities based on search criteria
const result = await userService.searchActivities(
activityType
userId,
{ activityTitle, activityType, checkInCity }
);
return {

View File

@@ -1,71 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Extract token from headers
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']
if (!token) {
throw new ApiError(400, 'This is a protected route. Please provide a valid token.');
}
// Authenticate user using verifyUserToken
const userInfo = await verifyUserToken(token);
const userId = userInfo.id;
if (Number.isNaN(userId)) {
throw new ApiError(400, 'User id must be a number');
}
const user = await userService.getUserById(userId);
if (!user) {
throw new ApiError(404, 'User not found');
}
// Parse request body
let body: { activityXid: number; isBucket: boolean; bucketTypeName: string; };
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { activityXid, isBucket, bucketTypeName } = body;
// Validate required fields
if (
typeof activityXid !== 'number' ||
typeof isBucket !== 'boolean' ||
!bucketTypeName
) {
throw new ApiError(400, 'Required fields missing or invalid');
}
// Remove from bucket/interested
const counts = await userService.removeFromBucketInterested(userId, isBucket, bucketTypeName, activityXid);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: `Activity removed from ${isBucket ? 'bucket' : 'interested'} successfully`,
data: {
bucketCount: counts.bucketCount,
interestedCount: counts.interestedCount,
}
}),
};
});

View File

@@ -86,10 +86,9 @@ export const handler = safeHandler(
body: JSON.stringify({
success: true,
message: 'Access token generated successfully',
data: {
accessToken: newAccessToken.access.token,
accessTokenExpires: newAccessToken.access.expires,
},
data: null,
}),
};
},

View File

@@ -30,17 +30,16 @@ export const handler = safeHandler(async (
const transactionResult = await prismaClient.$transaction(async (tx) => {
const user = await tx.user.findFirst({
where: { mobileNumber: mobileNumber, isActive: true, userStatus: USER_STATUS.ACTIVE },
select: { id: true, userPasscode: true, mobileNumber: true, firstName: true },
select: { id: true, userPasscode: true, mobileNumber: true },
});
let newUserLocal;
let isNewUser = false;
if (user && (!user.userPasscode || !user.firstName)) {
if (user && !user.userPasscode) {
// reuse existing invited user record
newUserLocal = user;
isNewUser = true;
} else if (user) {
// Fully registered user already exists
newUserLocal = user;

View File

@@ -1,44 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import ApiError from '../../../../common/utils/helper/ApiError';
import { UserService } from '../../services/user.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
const userService = new UserService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
const searchQuery = event.queryStringParameters?.searchQuery ?? '';
const result = await userService.searchConnectionPeople(userId, searchQuery);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Connection people retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,43 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { ItineraryService } from '../../services/itinerary.service';
const itineraryService = new ItineraryService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
const result = await itineraryService.getAllUserSavedItineraries(userId);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Saved itineraries retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,95 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { ItineraryService } from '../../services/itinerary.service';
const itineraryService = new ItineraryService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || Number.isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
let body: Record<string, any> = {};
if (event.body) {
try {
body = JSON.parse(event.body);
} catch {
throw new ApiError(400, 'Invalid JSON body');
}
}
const payload = {
userLat: Number(body.userLat),
userLong: Number(body.userLong),
startDate: body.startDate,
endDate: body.endDate,
startTime: body.startTime,
endTime: body.endTime,
energyLevelXid:
body.energyLevelXid !== undefined && body.energyLevelXid !== null
? Number(body.energyLevelXid)
: undefined,
entryTypeXid: Number(body.entryTypeXid),
groupCount:
body.groupCount !== undefined && body.groupCount !== null
? Number(body.groupCount)
: undefined,
page: body.page !== undefined ? Number(body.page) : 1,
limit: body.limit !== undefined ? Number(body.limit) : 20,
};
if (
Number.isNaN(payload.userLat) ||
Number.isNaN(payload.userLong) ||
!payload.startDate ||
!payload.endDate ||
!payload.startTime ||
!payload.endTime ||
(payload.energyLevelXid !== undefined &&
Number.isNaN(payload.energyLevelXid)) ||
Number.isNaN(payload.entryTypeXid) ||
(payload.groupCount !== undefined && Number.isNaN(payload.groupCount)) ||
Number.isNaN(payload.page) ||
Number.isNaN(payload.limit)
) {
throw new ApiError(
400,
'userLat, userLong, startDate, endDate, startTime, endTime, entryTypeXid, page and limit are required. energyLevelXid is optional.',
);
}
const result = await itineraryService.getMatchingBucketInterestedActivities(
userId,
payload,
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Matching itinerary activities retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,43 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { ItineraryService } from '../../services/itinerary.service';
const itineraryService = new ItineraryService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
const result = await itineraryService.getUserItineraryDetails(userId);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Itinerary details retrieved successfully',
data: result,
}),
};
});

View File

@@ -1,145 +0,0 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../common/utils/helper/ApiError';
import { ItineraryService } from '../../services/itinerary.service';
const itineraryService = new ItineraryService(prismaClient);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
const userInfo = await verifyUserToken(token);
const userId = Number(userInfo.id);
if (!userId || Number.isNaN(userId)) {
throw new ApiError(400, 'Invalid user ID');
}
let body: Record<string, any> = {};
if (event.body) {
try {
body = JSON.parse(event.body);
} catch {
throw new ApiError(400, 'Invalid JSON body');
}
}
const activities = Array.isArray(body.activities) ? body.activities : [];
if (!body.startDate || !body.endDate || !body.startTime || !body.endTime) {
throw new ApiError(
400,
'startDate, endDate, startTime and endTime are required.',
);
}
if (!activities.length) {
throw new ApiError(400, 'At least one activity is required.');
}
for (const activity of activities) {
if (
!activity.activityXid ||
!activity.venueXid ||
!activity.scheduleHeaderXid ||
!activity.modeOfTravel ||
activity.travelTimeBetweenPointsMins === undefined ||
activity.travelTimeBetweenPointsMins === null
) {
throw new ApiError(
400,
'Each activity must include activityXid, venueXid, scheduleHeaderXid, modeOfTravel and travelTimeBetweenPointsMins.',
);
}
}
const payload = {
title: body.title,
startDate: body.startDate,
endDate: body.endDate,
startTime: body.startTime,
endTime: body.endTime,
activities: activities.map((activity: any) => ({
activityXid: Number(activity.activityXid),
venueXid: Number(activity.venueXid),
scheduleHeaderXid: Number(activity.scheduleHeaderXid),
modeOfTravel: activity.modeOfTravel,
travelTimeBetweenPointsMins: Number(
activity.travelTimeBetweenPointsMins,
),
kmForNextPoint:
activity.kmForNextPoint !== undefined &&
activity.kmForNextPoint !== null
? Number(activity.kmForNextPoint)
: undefined,
occurenceDate: activity.occurenceDate,
selectedStartTime: activity.selectedStartTime,
selectedEndTime: activity.selectedEndTime,
itineraryType: activity.itineraryType,
paxCount:
activity.paxCount !== undefined && activity.paxCount !== null
? Number(activity.paxCount)
: undefined,
totalAmount:
activity.totalAmount !== undefined && activity.totalAmount !== null
? Number(activity.totalAmount)
: undefined,
locationLat:
activity.locationLat !== undefined && activity.locationLat !== null
? Number(activity.locationLat)
: undefined,
locationLong:
activity.locationLong !== undefined && activity.locationLong !== null
? Number(activity.locationLong)
: undefined,
locationAddress: activity.locationAddress,
})),
};
if (
payload.activities.some(
(activity) =>
Number.isNaN(activity.activityXid) ||
Number.isNaN(activity.venueXid) ||
Number.isNaN(activity.scheduleHeaderXid) ||
Number.isNaN(activity.travelTimeBetweenPointsMins) ||
(activity.kmForNextPoint !== undefined &&
Number.isNaN(activity.kmForNextPoint)) ||
(activity.paxCount !== undefined && Number.isNaN(activity.paxCount)) ||
(activity.totalAmount !== undefined &&
Number.isNaN(activity.totalAmount)) ||
(activity.locationLat !== undefined &&
Number.isNaN(activity.locationLat)) ||
(activity.locationLong !== undefined &&
Number.isNaN(activity.locationLong)),
)
) {
throw new ApiError(400, 'One or more numeric itinerary values are invalid.');
}
const result = await itineraryService.saveUserItinerary(userId, payload);
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Itinerary saved successfully',
data: result,
}),
};
});

View File

@@ -1,938 +0,0 @@
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { getPresignedUrl } from '../../../common/middlewares/aws/getPreSignedUrl';
import {
ACTIVITY_AM_INTERNAL_STATUS,
ACTIVITY_INTERNAL_STATUS,
} from '../../../common/utils/constants/host.constant';
import config from '../../../config/config';
const bucket = config.aws.bucketName;
const attachPresignedUrl = async (file: string | null | undefined) => {
if (!file) return null;
const key = file.startsWith('http')
? new URL(file).pathname.replace(/^\/+/, '')
: file;
return getPresignedUrl(bucket, key);
};
const attachMediaWithPresignedUrl = async (mediaArr: any[] = []) => {
return Promise.all(
mediaArr.map(async (m) => ({
...m,
presignedUrl: await attachPresignedUrl(m.mediaFileName),
})),
);
};
@Injectable()
export class FilteredLandingPageService {
constructor(private readonly prisma: PrismaClient) { }
normalizeName = (name: string): string => {
return name
.toLowerCase()
.replace(/[^a-z0-9\s]/g, '')
.replace(/\s+/g, ' ')
.trim();
};
findOrCreateLocation = async (
countryName: string,
stateName: string,
cityName: string,
tx: any,
) => {
const normalizedCountry = this.normalizeName(countryName);
const normalizedState = this.normalizeName(stateName);
const normalizedCity = this.normalizeName(cityName);
let country = await tx.countries.findFirst({
where: {
countryName: { contains: normalizedCountry, mode: 'insensitive' },
},
});
if (!country) {
country = await tx.countries.create({
data: {
countryName: countryName.trim(),
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
},
});
}
let state = await tx.states.findFirst({
where: {
countryXid: country.id,
stateName: { contains: normalizedState, mode: 'insensitive' },
},
});
if (!state) {
state = await tx.states.create({
data: {
countryXid: country.id,
stateName: stateName.trim(),
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
},
});
}
let city = await tx.cities.findFirst({
where: {
stateXid: state.id,
cityName: { contains: normalizedCity, mode: 'insensitive' },
},
});
if (!city) {
city = await tx.cities.create({
data: {
stateXid: state.id,
cityName: cityName.trim(),
isActive: true,
createdAt: new Date(),
updatedAt: new Date(),
},
});
}
return {
countryXid: country.id,
stateXid: state.id,
cityXid: city.id,
};
};
// attachMediaWithPresignedUrl = async (mediaArr = []) => {
// return (
// await Promise.all(
// mediaArr.map(async (m) => {
// return {
// ...m,
// presignedUrl: await this.attachPresignedUrl(m.mediaFileName),
// };
// }),
// )
// );
// };
calculateDistance = (
lat1: number | null,
lon1: number | null,
lat2: number | null,
lon2: number | null,
) => {
if (!lat1 || !lon1 || !lat2 || !lon2) return null;
const R = 6371; // km
const dLat = ((lat2 - lat1) * Math.PI) / 180;
const dLon = ((lon2 - lon1) * Math.PI) / 180;
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos((lat1 * Math.PI) / 180) *
Math.cos((lat2 * Math.PI) / 180) *
Math.sin(dLon / 2) *
Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const distance = R * c;
// return distance rounded to 2 decimals
return Number(distance.toFixed(2));
};
async rankAndPaginateActivities(
tx: any,
whereClause: any,
page: number,
limit: number,
connectionInterestMap: Map<number, number>,
) {
const skip = (page - 1) * limit;
// Get total count
const totalCount = await tx.activities.count({ where: whereClause });
// Fetch activities with ranking metadata
const activities = await tx.activities.findMany({
where: whereClause,
skip,
take: limit,
select: {
id: true,
activityTitle: true,
sustainabilityScore: true,
totalScore: true,
activityType: {
select: {
interestXid: true,
energyLevel: {
select: {
id: true,
energyLevelName: true,
energyColor: true,
energyIcon: true,
},
},
},
},
ActivitiesMedia: {
where: { isActive: true },
select: {
id: true,
mediaFileName: true,
mediaType: true,
},
},
// Fetch ranking metadata
ItineraryActivities: {
select: {
ActivityFeedbacks: {
select: { activityStars: true },
},
},
},
ActivityVenues: {
select: {
ActivityPrices: {
select: { sellPrice: true },
},
},
},
},
});
// Sort and format
const sortedActivities = activities
.map((act) => {
const feedbacks = act.ItineraryActivities.flatMap(
(ia) => ia.ActivityFeedbacks,
);
const totalStars = feedbacks.reduce(
(sum, f) => sum + f.activityStars,
0,
);
const avgRating =
feedbacks.length > 0 ? totalStars / feedbacks.length : 0;
const prices = act.ActivityVenues.flatMap((v) =>
v.ActivityPrices.map((p) => p.sellPrice),
).filter((p) => p !== null) as number[];
const minPrice = prices.length > 0 ? Math.min(...prices) : Infinity;
return {
...act,
avgRating,
minPrice,
sustainabilityScore: act.sustainabilityScore ?? 0,
totalScore: act.totalScore ?? 0,
};
})
.sort((a, b) => {
if (b.avgRating !== a.avgRating) return b.avgRating - a.avgRating;
if (a.minPrice !== b.minPrice) return a.minPrice - b.minPrice;
if (b.sustainabilityScore !== a.sustainabilityScore)
return b.sustainabilityScore - a.sustainabilityScore;
return b.totalScore - a.totalScore;
});
const formattedActivities = await Promise.all(
sortedActivities.map(async (activity) => ({
interestXid: activity.activityType.interestXid,
activityId: activity.id,
connectionInterestedCount:
connectionInterestMap.get(activity.id) ?? 0,
activityTitle: activity.activityTitle,
sustainabilityScore: activity.sustainabilityScore,
cheapestPrice: activity.minPrice === Infinity ? null : activity.minPrice,
distance: 0,
rating: activity.avgRating,
energyLevel: activity.activityType.energyLevel
? {
...activity.activityType.energyLevel,
presignedUrl: await attachPresignedUrl(
activity.activityType.energyLevel.energyIcon
),
}
: null,
media: await attachMediaWithPresignedUrl(activity.ActivitiesMedia),
})),
);
return {
page,
limit,
totalCount,
hasMore: skip + limit < totalCount,
activities: formattedActivities,
};
}
async getFilteredLandingPageAllDetails(
userId: number,
page: number,
limit: number,
countryName: string,
stateName: string,
cityName: string,
userLat: string,
userLong: string,
activityTypeXids?: number[],
) {
const data = await this.prisma.$transaction(async (tx) => {
const userAddressDetails = await tx.userAddressDetails.findFirst({
where: { userXid: userId, isActive: true },
select: {
id: true,
address1: true,
address2: true,
pinCode: true,
locationName: true,
stateXid: true,
cityXid: true,
countryXid: true,
locationLat: true,
locationLong: true,
},
});
const userLatitude = userAddressDetails?.locationLat ?? null;
const userLongitude = userAddressDetails?.locationLong ?? null;
let effectiveLocation: {
countryXid?: number | null;
stateXid?: number | null;
cityXid?: number | null;
} | null = null;
const hasRequestLocation = countryName && stateName && cityName;
if (hasRequestLocation) {
effectiveLocation = await this.findOrCreateLocation(
countryName!,
stateName!,
cityName!,
tx,
);
} else if (userAddressDetails) {
effectiveLocation = {
countryXid: userAddressDetails.countryXid,
stateXid: userAddressDetails.stateXid,
cityXid: userAddressDetails.cityXid,
};
}
const effectiveCountryXid = effectiveLocation?.countryXid ?? null;
const effectiveStateXid = effectiveLocation?.stateXid ?? null;
// Get all activity types for user interests, filtered by selected activity types if provided
const activityTypeWhere: any = {
isActive: true,
};
if (activityTypeXids && activityTypeXids.length > 0) {
activityTypeWhere.id = { in: activityTypeXids };
}
const activityTypesWithInterests = await tx.activityTypes.findMany({
where: activityTypeWhere,
select: {
id: true,
activityTypeName: true,
interestXid: true,
interests: {
select: {
id: true,
interestName: true,
interestColor: true,
interestImage: true,
displayOrder: true,
},
},
},
});
if (!activityTypesWithInterests.length) {
return {
userAddressDetails,
interests: [],
activityTypes: [],
otherStatesActivities: null,
overSeasActivities: null,
};
}
const userBucketInterested = await tx.userBucketInterested.findMany({
where: {
userXid: userId,
isActive: true,
},
select: {
activityXid: true,
isBucket: true,
},
});
const userBucketActivityIds = userBucketInterested
.filter(u => u.isBucket)
.map(u => u.activityXid);
const userInterestedActivityIds = userBucketInterested
.filter(u => !u.isBucket)
.map(u => u.activityXid);
const allUserExcludedActivityIds = userBucketInterested.map(
u => u.activityXid,
);
const userConnectionDetails = await tx.connectDetails.findMany({
where: { userXid: userId, isActive: true },
select: {
id: true,
schoolCompanyXid: true,
}
});
const otherConnectionUsers = await tx.connectDetails.findMany({
where: { userXid: { notIn: [userId] }, isActive: true, schoolCompanyXid: { in: userConnectionDetails.map((u) => u.schoolCompanyXid) } },
select: {
id: true,
userXid: true,
}
});
const connectionUserIds =
otherConnectionUsers.length > 0
? otherConnectionUsers.map(u => u.userXid)
: [-1];
const connectionInterestByActivity = await tx.userBucketInterested.groupBy({
by: ['activityXid'],
where: {
userXid: { in: connectionUserIds },
isActive: true,
},
_count: {
activityXid: true,
},
});
const connectionInterestMap = new Map(
connectionInterestByActivity.map(item => [
item.activityXid,
item._count.activityXid,
])
);
const skip = (page - 1) * limit;
// Group activity types by interest
const activityTypesByInterest = activityTypesWithInterests.reduce((acc, at) => {
if (!acc[at.interestXid]) {
acc[at.interestXid] = {
interest: at.interests,
activityTypes: [],
};
}
acc[at.interestXid].activityTypes.push({
activityTypeId: at.id,
activityTypeName: at.activityTypeName,
});
return acc;
}, {} as any);
// Fetch activities for each activity type with excluded activities filter
const activitiesByActivityType = await Promise.all(
activityTypesWithInterests.map(async (activityType) => {
const activities = await tx.activities.findMany({
where: {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
activityTypeXid: activityType.id,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
},
skip,
take: limit,
orderBy: { id: 'desc' },
select: {
id: true,
activityTitle: true,
activityDurationMins: true,
sustainabilityScore: true,
checkInLat: true,
checkInLong: true,
activityType: {
select: {
id: true,
activityTypeName: true,
interestXid: true,
energyLevel: {
select: {
id: true,
energyLevelName: true,
energyColor: true,
energyIcon: true,
},
},
},
},
ActivityVenues: {
select: {
ActivityPrices: {
select: {
sellPrice: true,
},
},
},
},
ActivitiesMedia: {
where: { isActive: true },
select: {
id: true,
mediaFileName: true,
mediaType: true,
},
},
},
});
const formattedActivities = await Promise.all(
activities.map(async (activity) => {
const cheapestPrice =
activity.ActivityVenues.flatMap((v) => v.ActivityPrices)
.map((p) => p.sellPrice)
.filter(Boolean)
.sort((a, b) => a - b)[0] ?? null;
const distance = this.calculateDistance(
userLatitude,
userLongitude,
activity.checkInLat,
activity.checkInLong,
);
return {
activityId: activity.id,
connectionInterestedCount:
connectionInterestMap.get(activity.id) ?? 0,
activityTitle: activity.activityTitle,
activityDurationMins: activity.activityDurationMins,
sustainabilityScore: activity.sustainabilityScore,
cheapestPrice,
distance,
rating: 0,
energyLevel: activity.activityType.energyLevel
? {
...activity.activityType.energyLevel,
presignedUrl: await attachPresignedUrl(
activity.activityType.energyLevel.energyIcon
),
}
: null,
media: await attachMediaWithPresignedUrl(activity.ActivitiesMedia),
};
}),
);
return {
activityTypeId: activityType.id,
activityTypeName: activityType.activityTypeName,
interestXid: activityType.interestXid,
activities: formattedActivities,
pagination: {
page,
limit,
hasMore: formattedActivities.length === limit,
},
};
}),
);
// Group by interests for the final structure
const interestsWithActivities = await Promise.all(
Object.values(activityTypesByInterest).map(async (interestGroup: any) => {
// collect all activities belonging to this interest
const activitiesForInterest = activitiesByActivityType
.filter(a => a.interestXid === interestGroup.interest.id)
.flatMap(a => a.activities);
return {
interestId: interestGroup.interest.id,
interestName: interestGroup.interest.interestName,
interestColor: interestGroup.interest.interestColor,
interestImage: interestGroup.interest.interestImage,
interestImagePresignedUrl: await attachPresignedUrl(
interestGroup.interest.interestImage
),
displayOrder: interestGroup.interest.displayOrder,
page,
limit,
hasMore: activitiesForInterest.length === limit,
activities: activitiesForInterest
};
})
);
// Most Hyped Activities with filtering
const mostHypedGrouped = await tx.userBucketInterested.groupBy({
by: ['activityXid'],
where: {
isActive: true,
isBucket: false,
activityXid: {
notIn: allUserExcludedActivityIds.length ? allUserExcludedActivityIds : [-1],
},
},
_count: {
activityXid: true,
},
orderBy: {
_count: {
activityXid: 'desc',
},
},
});
// Filter most hyped activities by activity type if provided
let filteredMostHypedActivityIds = mostHypedGrouped.map((a) => a.activityXid);
if (activityTypeXids && activityTypeXids.length > 0) {
const activitiesWithTypes = await tx.activities.findMany({
where: {
id: { in: filteredMostHypedActivityIds },
activityTypeXid: { in: activityTypeXids },
},
select: { id: true },
});
filteredMostHypedActivityIds = activitiesWithTypes.map(a => a.id);
}
const finalMostHypedGrouped = mostHypedGrouped
.filter(group => filteredMostHypedActivityIds.includes(group.activityXid))
.slice(skip, skip + limit);
const totalHypedActivities = filteredMostHypedActivityIds.length;
const mostHypedActivityIds = finalMostHypedGrouped.map((a) => a.activityXid);
const mostHypedActivitiesRaw = await tx.activities.findMany({
where: {
id: {
in: mostHypedActivityIds,
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
},
select: {
id: true,
activityTitle: true,
sustainabilityScore: true,
totalScore: true,
activityType: {
select: {
energyLevel: {
select: {
id: true,
energyLevelName: true,
energyColor: true,
energyIcon: true,
},
},
},
},
ActivitiesMedia: {
where: { isActive: true },
select: {
id: true,
mediaFileName: true,
mediaType: true,
},
},
ItineraryActivities: {
select: {
ActivityFeedbacks: {
select: { activityStars: true },
},
},
},
ActivityVenues: {
select: {
ActivityPrices: {
select: { sellPrice: true },
},
},
},
},
});
// Sort Most Hyped by the 4 criteria
const mostHypedSorted = mostHypedActivitiesRaw
.map((act) => {
const feedbacks = act.ItineraryActivities.flatMap(
(ia) => ia.ActivityFeedbacks,
);
const totalStars = feedbacks.reduce(
(sum, f) => sum + f.activityStars,
0,
);
const avgRating =
feedbacks.length > 0 ? totalStars / feedbacks.length : 0;
const prices = act.ActivityVenues.flatMap((v) =>
v.ActivityPrices.map((p) => p.sellPrice),
).filter((p) => p !== null) as number[];
const minPrice = prices.length > 0 ? Math.min(...prices) : Infinity;
return {
...act,
avgRating,
minPrice,
sustainabilityScore: act.sustainabilityScore ?? 0,
totalScore: act.totalScore ?? 0,
hypeCount:
finalMostHypedGrouped.find((g) => g.activityXid === act.id)?._count
.activityXid ?? 0,
};
})
.sort((a, b) => {
if (b.avgRating !== a.avgRating) return b.avgRating - a.avgRating;
if (a.minPrice !== b.minPrice) return a.minPrice - b.minPrice;
if (b.sustainabilityScore !== a.sustainabilityScore)
return b.sustainabilityScore - a.sustainabilityScore;
return b.totalScore - a.totalScore;
});
const mostHypedActivities = await Promise.all(
mostHypedSorted.map(async (activity) => ({
activityId: activity.id,
activityTitle: activity.activityTitle,
connectionInterestedCount:
connectionInterestMap.get(activity.id) ?? 0,
hypeCount: activity.hypeCount,
distance: 0,
rating: 0,
energyLevel: activity.activityType.energyLevel
? {
...activity.activityType.energyLevel,
presignedUrl: await attachPresignedUrl(
activity.activityType.energyLevel.energyIcon
),
}
: null,
media: await attachMediaWithPresignedUrl(activity.ActivitiesMedia),
})),
);
const formattedMostHypedActivities = {
page,
limit,
totalCount: totalHypedActivities,
hasMore: skip + limit < totalHypedActivities,
activities: mostHypedActivities,
};
// New Arrivals with filtering
const newArrivalsWhere: any = {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
createdAt: { gte: new Date(Date.now() - 31 * 24 * 60 * 60 * 1000) },
};
if (activityTypeXids && activityTypeXids.length > 0) {
newArrivalsWhere.activityTypeXid = { in: activityTypeXids };
}
const formattedNewArrivalsActivities = await this.rankAndPaginateActivities(
tx,
newArrivalsWhere,
page,
limit,
connectionInterestMap
);
// Other States Activities with filtering
const otherStatesWhere: any = {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
};
if (effectiveCountryXid) {
otherStatesWhere.checkInCountryXid = effectiveCountryXid;
}
if (effectiveStateXid) {
otherStatesWhere.checkInStateXid = { not: effectiveStateXid };
}
if (activityTypeXids && activityTypeXids.length > 0) {
otherStatesWhere.activityTypeXid = { in: activityTypeXids };
}
const formattedOtherStatesActivities = await this.rankAndPaginateActivities(
tx,
otherStatesWhere,
page,
limit,
connectionInterestMap
);
// Random Activities with filtering
const totalActiveCount = await tx.activities.count({
where: {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
deletedAt: null,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
...(activityTypeXids && activityTypeXids.length > 0 && {
activityTypeXid: { in: activityTypeXids },
}),
},
});
let randomActivities: any[] = [];
if (totalActiveCount > 0) {
const takeCount = Math.min(5, totalActiveCount);
const randomOffsets = new Set<number>();
while (randomOffsets.size < takeCount) {
randomOffsets.add(Math.floor(Math.random() * totalActiveCount));
}
const randomFetched = await Promise.all(
Array.from(randomOffsets).map((offset) =>
tx.activities.findFirst({
skip: offset,
where: {
isActive: true,
activityInternalStatus:
ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus:
ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
deletedAt: null,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
...(activityTypeXids && activityTypeXids.length > 0 && {
activityTypeXid: { in: activityTypeXids },
}),
},
select: {
id: true,
activityTitle: true,
ActivitiesMedia: {
where: { isActive: true, isCoverImage: true },
orderBy: { displayOrder: 'asc' },
take: 1,
select: {
mediaFileName: true,
},
},
},
}),
),
);
randomActivities = await Promise.all(
randomFetched
.filter(Boolean)
.map(async (activity) => {
const cover = activity!.ActivitiesMedia?.[0];
return {
activityId: activity!.id,
activityTitle: activity!.activityTitle,
coverImage: cover?.mediaFileName ?? null,
coverImagePresignedUrl: await attachPresignedUrl(
cover?.mediaFileName
),
};
}),
);
}
// Overseas Activities with filtering
const overseasWhere: any = {
isActive: true,
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
id: {
notIn: allUserExcludedActivityIds.length
? allUserExcludedActivityIds
: [-1],
},
};
if (effectiveCountryXid) {
overseasWhere.checkInCountryXid = { not: effectiveCountryXid };
}
if (activityTypeXids && activityTypeXids.length > 0) {
overseasWhere.activityTypeXid = { in: activityTypeXids };
}
const formattedOverSeasActivities = await this.rankAndPaginateActivities(
tx,
overseasWhere,
page,
limit,
connectionInterestMap
);
return {
userAddressDetails,
experiencesLogged: 0,
citiesDiscovered: 0,
loggedInNetworkCount: 0,
citiesInNetworkCount: 0,
rating: 0,
interestedCount: userInterestedActivityIds.length,
bucketCount: userBucketActivityIds.length,
pagination: {
page,
limit,
},
randomActivities,
interests: interestsWithActivities,
otherStatesActivities: formattedOtherStatesActivities,
overSeasActivities: formattedOverSeasActivities,
newArrivalsActivities: formattedNewArrivalsActivities,
mostHypedActivities: formattedMostHypedActivities,
};
});
return data;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff