Compare commits
76 Commits
split-serv
...
split-sche
| Author | SHA1 | Date | |
|---|---|---|---|
| 50f93bbeae | |||
| 3ce9d1d180 | |||
| cb819088a0 | |||
| 8f5f01c287 | |||
| e4a2a04045 | |||
| 50ce8e39c5 | |||
| 19e57f0e7f | |||
| ad5e343b66 | |||
| 8c3ece6ebd | |||
| 092f425bb3 | |||
| b1a3afd3a1 | |||
| 97f431260d | |||
| bf6d9ae00b | |||
| 518ec4eb21 | |||
| 95b061b400 | |||
| 92992797ab | |||
| c96e3b0c1a | |||
| f23b93801c | |||
| f1801a3210 | |||
| 2588ca4317 | |||
| e809ba4480 | |||
| 678be7c905 | |||
| 08b4231e5f | |||
|
|
4a61e2c412 | ||
| a3ab9db5a2 | |||
|
|
7167eae07e | ||
|
|
4f3d7fd737 | ||
|
|
199013b0f5 | ||
| 0b81dbf7b1 | |||
|
|
9722e1988c | ||
| cf2bbbf138 | |||
|
|
2ca785248f | ||
| 0aa2b9b53e | |||
| b5cdb20c4f | |||
| 00e07113e5 | |||
| c8f0f93792 | |||
| 87779664d1 | |||
| 5b31e5f2a9 | |||
| 2a073c44a2 | |||
|
|
220d309087 | ||
| f45c33ba83 | |||
| 22b3593150 | |||
| d186681ee4 | |||
| 8f428fc1cb | |||
| 0b503cf8bb | |||
| 7110d0462c | |||
| 96648fe37e | |||
|
|
2095f8e124 | ||
| 21c8799502 | |||
|
|
ad9e8e1a3f | ||
| b200e2cb94 | |||
| cae66237d2 | |||
| 25be8a5647 | |||
| 7a4aecdd45 | |||
| 5cced2981a | |||
|
|
b9fbab3717 | ||
|
|
90c897ad48 | ||
| 4a069cc67a | |||
|
|
5d046c4bcf | ||
| accfc4b769 | |||
| e149884f72 | |||
| a31ec97640 | |||
| 0c97412057 | |||
|
|
b4ff39c0d7 | ||
| bb5da7647b | |||
| 3f19bb4087 | |||
| be8b9cef7d | |||
|
|
77cef98091 | ||
| 97f9c2b26e | |||
|
|
b93cd6b32c | ||
|
|
51319a69fc | ||
| 5ad46309ef | |||
|
|
781212277a | ||
| 6b0ee461c5 | |||
| cc2fa3eb6b | |||
| fe6bb59cc7 |
@@ -1,40 +0,0 @@
|
||||
# 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
533
package-lock.json
generated
@@ -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.310.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.928.0",
|
||||
"@aws/lambda-invoke-store": "^0.2.1",
|
||||
"@nestjs/common": "^10.3.0",
|
||||
"@nestjs/config": "^3.1.1",
|
||||
@@ -31,18 +31,27 @@
|
||||
"@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",
|
||||
@@ -73,6 +82,7 @@
|
||||
"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",
|
||||
@@ -4650,6 +4660,24 @@
|
||||
"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",
|
||||
@@ -5751,6 +5779,13 @@
|
||||
"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",
|
||||
@@ -6546,6 +6581,15 @@
|
||||
"@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",
|
||||
@@ -6624,6 +6668,16 @@
|
||||
"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",
|
||||
@@ -6964,6 +7018,19 @@
|
||||
"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",
|
||||
@@ -6992,6 +7059,13 @@
|
||||
"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",
|
||||
@@ -7257,6 +7331,16 @@
|
||||
"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",
|
||||
@@ -8386,7 +8470,6 @@
|
||||
"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": {
|
||||
@@ -8539,6 +8622,12 @@
|
||||
"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",
|
||||
@@ -8633,6 +8722,21 @@
|
||||
"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",
|
||||
@@ -8763,6 +8867,50 @@
|
||||
"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",
|
||||
@@ -9031,6 +9179,39 @@
|
||||
"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",
|
||||
@@ -10022,6 +10203,12 @@
|
||||
"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",
|
||||
@@ -10199,6 +10386,31 @@
|
||||
"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",
|
||||
@@ -10437,6 +10649,16 @@
|
||||
"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",
|
||||
@@ -10499,6 +10721,20 @@
|
||||
"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",
|
||||
@@ -10514,6 +10750,20 @@
|
||||
"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",
|
||||
@@ -10556,7 +10806,6 @@
|
||||
"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": {
|
||||
@@ -10661,6 +10910,16 @@
|
||||
"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",
|
||||
@@ -11986,7 +12245,6 @@
|
||||
"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",
|
||||
@@ -11999,7 +12257,6 @@
|
||||
"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",
|
||||
@@ -12015,14 +12272,12 @@
|
||||
"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"
|
||||
@@ -12149,7 +12404,6 @@
|
||||
"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"
|
||||
@@ -12548,6 +12802,12 @@
|
||||
"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",
|
||||
@@ -12698,6 +12958,24 @@
|
||||
"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",
|
||||
@@ -12722,6 +13000,16 @@
|
||||
"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",
|
||||
@@ -12873,6 +13161,12 @@
|
||||
"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",
|
||||
@@ -13118,6 +13412,40 @@
|
||||
"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",
|
||||
@@ -13128,7 +13456,6 @@
|
||||
"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": {
|
||||
@@ -13219,6 +13546,16 @@
|
||||
"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",
|
||||
@@ -13293,6 +13630,21 @@
|
||||
"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",
|
||||
@@ -13304,6 +13656,24 @@
|
||||
"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",
|
||||
@@ -13438,6 +13808,21 @@
|
||||
"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",
|
||||
@@ -13689,11 +14074,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
@@ -13756,6 +14149,36 @@
|
||||
"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",
|
||||
@@ -14756,6 +15179,23 @@
|
||||
"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",
|
||||
@@ -14792,7 +15232,6 @@
|
||||
"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": {
|
||||
@@ -14923,6 +15362,47 @@
|
||||
"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",
|
||||
@@ -15504,6 +15984,13 @@
|
||||
"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",
|
||||
@@ -17076,6 +17563,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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",
|
||||
|
||||
@@ -97,7 +97,9 @@
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "rhel-openssl-3.0.x"] // Add Linux target
|
||||
binaryTargets = ["native", "rhel-openssl-3.0.x"] // Lambda Node 18/20 (Amazon Linux) target
|
||||
previewFeatures = ["multiSchema"]
|
||||
engineType = "library"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
@@ -554,20 +555,6 @@ 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)
|
||||
@@ -1052,13 +1039,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.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)
|
||||
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
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
@@ -1456,8 +1443,7 @@ model ActivityNavigationModes {
|
||||
id Int @id @default(autoincrement())
|
||||
activityXid Int @map("activity_xid")
|
||||
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
|
||||
navigationModeXid Int @map("navigation_mode_xid")
|
||||
navigationMode NavigationModes @relation(fields: [navigationModeXid], references: [id], onDelete: Restrict)
|
||||
navigationModeName String @map("navigation_mode_name") @db.VarChar(30)
|
||||
isInActivityChargeable Boolean @default(false) @map("is_in_activity_chargeable")
|
||||
navigationModesBasePrice Int @map("navigation_modes_base_price")
|
||||
navigationModesTotalPrice Int @map("navigation_modes_total_price")
|
||||
@@ -1637,8 +1623,8 @@ model Cancellations {
|
||||
scheduleHeaderXid Int @map("schedule_header_xid")
|
||||
scheduleHeader ScheduleHeader @relation(fields: [scheduleHeaderXid], references: [id], onDelete: Cascade)
|
||||
occurenceDate DateTime? @map("occurence_date")
|
||||
startTime String? @map("start_time") @db.VarChar(30)
|
||||
endTime String? @map("end_time") @db.VarChar(30)
|
||||
startTime String? @map("start_time") @db.VarChar(30)
|
||||
endTime String? @map("end_time") @db.VarChar(30)
|
||||
cancellationReason String? @map("cancellation_reason")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
@@ -1742,8 +1728,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")
|
||||
|
||||
@@ -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: 'Fur Fam' },
|
||||
where: { interestName: 'Pet space' },
|
||||
update: {},
|
||||
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' },
|
||||
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' },
|
||||
});
|
||||
const dogoodfeelgood = await prisma.interests.upsert({
|
||||
where: { interestName: 'Do Good, Feel Good' },
|
||||
@@ -693,16 +693,6 @@ 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: [
|
||||
|
||||
@@ -1,132 +1,13 @@
|
||||
service: minglar-host
|
||||
|
||||
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/**'
|
||||
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/host.yml)}
|
||||
- ${file(./serverless/functions/pqq.yml)}
|
||||
|
||||
plugins:
|
||||
- serverless-offline
|
||||
|
||||
13
serverless.minglaradmin.yml
Normal file
13
serverless.minglaradmin.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
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)}
|
||||
|
||||
@@ -1,133 +1,13 @@
|
||||
service: minglar-prepopulate
|
||||
|
||||
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/**'
|
||||
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/prepopulate.yml)}
|
||||
|
||||
plugins:
|
||||
- serverless-offline
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
service: minglar-layers
|
||||
service: minglar-prisma-layer
|
||||
|
||||
useDotenv: true
|
||||
|
||||
@@ -15,9 +15,13 @@ 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
|
||||
@@ -26,5 +30,3 @@ layers:
|
||||
compatibleRuntimes:
|
||||
- nodejs22.x
|
||||
retain: false
|
||||
|
||||
plugins: []
|
||||
@@ -1,133 +1,13 @@
|
||||
service: minglar-user
|
||||
|
||||
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/**'
|
||||
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/user.yml)}
|
||||
|
||||
plugins:
|
||||
- serverless-offline
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Legacy monolith config. For new deployments use serverless.*.yml files.
|
||||
service: minglar
|
||||
|
||||
|
||||
@@ -16,6 +17,11 @@ 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
|
||||
@@ -23,7 +29,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:
|
||||
- '*/*'
|
||||
@@ -73,16 +79,12 @@ 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: node22
|
||||
target: node20
|
||||
platform: node
|
||||
# Mark as external so they're not bundled into the JS
|
||||
external:
|
||||
@@ -125,7 +127,6 @@ layers:
|
||||
|
||||
package:
|
||||
individually: true
|
||||
excludeDevDependencies: true
|
||||
patterns:
|
||||
- '!node_modules/**'
|
||||
- '!node_modules/@prisma/**'
|
||||
@@ -146,8 +147,18 @@ 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
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
service: minglar-admin
|
||||
|
||||
useDotenv: true
|
||||
|
||||
params:
|
||||
@@ -15,12 +13,17 @@ 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-layers-${sls:stage}.PrismaLambdaLayerQualifiedArn}
|
||||
httpApi:
|
||||
id: ${cf:minglar-host-${sls:stage}.HttpApiId}
|
||||
- ${cf:minglar-prisma-layer-${sls:stage}.PrismaLambdaLayerQualifiedArn}
|
||||
apiGateway:
|
||||
binaryMediaTypes:
|
||||
- '*/*'
|
||||
@@ -70,17 +73,14 @@ 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: node22
|
||||
target: node20
|
||||
platform: node
|
||||
# Mark as external so they're not bundled into the JS
|
||||
external:
|
||||
- '@prisma/client'
|
||||
- '.prisma/client'
|
||||
@@ -91,6 +91,7 @@ build:
|
||||
- '@aws-sdk/*'
|
||||
- '@smithy/*'
|
||||
- '@aws-crypto/*'
|
||||
# Exclude prevents npm install of these packages in the zip
|
||||
exclude:
|
||||
- 'aws-sdk'
|
||||
- '@aws-sdk/*'
|
||||
@@ -110,7 +111,6 @@ build:
|
||||
|
||||
package:
|
||||
individually: true
|
||||
excludeDevDependencies: true
|
||||
patterns:
|
||||
- '!node_modules/**'
|
||||
- '!node_modules/@prisma/**'
|
||||
@@ -126,8 +126,9 @@ package:
|
||||
- '!.git/**'
|
||||
- '!.github/**'
|
||||
|
||||
functions:
|
||||
- ${file(./serverless/functions/minglaradmin.yml)}
|
||||
|
||||
plugins:
|
||||
- serverless-offline
|
||||
|
||||
custom:
|
||||
serverless-offline:
|
||||
reloadHandler: true
|
||||
@@ -14,7 +14,7 @@ getHosts:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host
|
||||
path: /
|
||||
method: get
|
||||
|
||||
verifyOTP:
|
||||
@@ -30,7 +30,7 @@ verifyOTP:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Host_Admin/onboarding/verify-otp
|
||||
path: /Host_Admin/onboarding/verify-otp
|
||||
method: post
|
||||
|
||||
login:
|
||||
@@ -46,7 +46,7 @@ login:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Host_Admin/onboarding/login
|
||||
path: /Host_Admin/onboarding/login
|
||||
method: post
|
||||
|
||||
signUp:
|
||||
@@ -62,7 +62,7 @@ signUp:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Host_Admin/onboarding/registration
|
||||
path: /Host_Admin/onboarding/registration
|
||||
method: post
|
||||
|
||||
createPassword:
|
||||
@@ -78,7 +78,7 @@ createPassword:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Host_Admin/onboarding/create-password
|
||||
path: /Host_Admin/onboarding/create-password
|
||||
method: post
|
||||
|
||||
updateBankDetails:
|
||||
@@ -94,7 +94,7 @@ updateBankDetails:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Host_Admin/onboarding/add-payment-details
|
||||
path: /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: /host/Activity_Hub/OnBoarding/add-activity
|
||||
path: /Activity_Hub/OnBoarding/add-activity
|
||||
method: post
|
||||
|
||||
getHostById:
|
||||
@@ -126,7 +126,7 @@ getHostById:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/getById
|
||||
path: /getById
|
||||
method: get
|
||||
|
||||
getPQQ_ByQuestionId:
|
||||
@@ -142,7 +142,7 @@ getPQQ_ByQuestionId:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Activity_Hub/OnBoarding/get-pqq-question-details
|
||||
path: /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: /host/Activity_Hub/OnBoarding/get-latest-pqq-question-details
|
||||
path: /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: /host/Activity_Hub/OnBoarding/prepopulate-new-activity
|
||||
path: /Activity_Hub/OnBoarding/prepopulate-new-activity
|
||||
method: get
|
||||
|
||||
createNewActivity:
|
||||
@@ -191,7 +191,7 @@ createNewActivity:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Activity_Hub/OnBoarding/create-new-activity
|
||||
path: /Activity_Hub/OnBoarding/create-new-activity
|
||||
method: patch
|
||||
|
||||
showSuggestion:
|
||||
@@ -207,7 +207,7 @@ showSuggestion:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/get-suggestion
|
||||
path: /get-suggestion
|
||||
method: get
|
||||
|
||||
getAllActivitySuggestion:
|
||||
@@ -223,7 +223,7 @@ getAllActivitySuggestion:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/get-Activity-suggestion
|
||||
path: /get-Activity-suggestion
|
||||
method: get
|
||||
|
||||
getAllHostActivity:
|
||||
@@ -239,7 +239,7 @@ getAllHostActivity:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Activity_Hub/OnBoarding/get-all-host-activity
|
||||
path: /Activity_Hub/OnBoarding/get-all-host-activity
|
||||
method: get
|
||||
|
||||
acceptAggrement:
|
||||
@@ -255,9 +255,25 @@ acceptAggrement:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Host_Admin/onboarding/accept-agreement
|
||||
path: /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
|
||||
@@ -276,6 +292,22 @@ 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
|
||||
@@ -288,7 +320,7 @@ submitCompanyDetails:
|
||||
- 'src/common/**'
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Host_Admin/onboarding/add-company-details
|
||||
path: /Host_Admin/onboarding/add-company-details
|
||||
method: patch
|
||||
|
||||
submitPQQ_Answer:
|
||||
@@ -304,7 +336,7 @@ submitPQQ_Answer:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Activity_Hub/OnBoarding/submit-pqq-answer
|
||||
path: /Activity_Hub/OnBoarding/submit-pqq-answer
|
||||
method: patch
|
||||
|
||||
updatePQQ_LastAnswer:
|
||||
@@ -320,7 +352,7 @@ updatePQQ_LastAnswer:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Activity_Hub/OnBoarding/submit-final-pqq-answer
|
||||
path: /Activity_Hub/OnBoarding/submit-final-pqq-answer
|
||||
method: post
|
||||
|
||||
submitPQQForReview:
|
||||
@@ -336,7 +368,7 @@ submitPQQForReview:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Activity_Hub/OnBoarding/submit-pqq-for-review
|
||||
path: /Activity_Hub/OnBoarding/submit-pqq-for-review
|
||||
method: patch
|
||||
|
||||
getAllPQQwithSubmittedAns:
|
||||
@@ -351,7 +383,7 @@ getAllPQQwithSubmittedAns:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Activity_Hub/OnBoarding/get-all-pqq-ques-submited-ans
|
||||
path: /Activity_Hub/OnBoarding/get-all-pqq-ques-submited-ans
|
||||
method: get
|
||||
|
||||
getAllDetailsOfActivityAndVenue:
|
||||
@@ -366,7 +398,7 @@ getAllDetailsOfActivityAndVenue:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Activity_Hub/OnBoarding/get-all-details-activity-venue/{activityXid}
|
||||
path: /Activity_Hub/OnBoarding/get-all-details-activity-venue/{activityXid}
|
||||
method: get
|
||||
|
||||
updateSuggestionAsReviewed:
|
||||
@@ -381,7 +413,7 @@ updateSuggestionAsReviewed:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /host/Activity_Hub/OnBoarding/update-suggestion-reviewed
|
||||
path: /Activity_Hub/OnBoarding/update-suggestion-reviewed
|
||||
method: patch
|
||||
|
||||
resendOTPmail:
|
||||
@@ -523,3 +555,33 @@ 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
|
||||
|
||||
@@ -13,7 +13,7 @@ minglarRegistration:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/registration
|
||||
path: /registration
|
||||
method: post
|
||||
|
||||
minglarLoginForAdmin:
|
||||
@@ -28,7 +28,7 @@ minglarLoginForAdmin:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/login
|
||||
path: /login
|
||||
method: post
|
||||
|
||||
minglarCreatePassword:
|
||||
@@ -43,7 +43,7 @@ minglarCreatePassword:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/create-password
|
||||
path: /create-password
|
||||
method: post
|
||||
|
||||
updateMinglarProfile:
|
||||
@@ -60,7 +60,7 @@ updateMinglarProfile:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/update-profile
|
||||
path: /update-profile
|
||||
method: patch
|
||||
|
||||
prepopulateRole:
|
||||
@@ -75,7 +75,7 @@ prepopulateRole:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/prepopulate-Roles
|
||||
path: /prepopulate-Roles
|
||||
method: get
|
||||
|
||||
getHostDetailsById:
|
||||
@@ -90,7 +90,7 @@ getHostDetailsById:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/hosts/get-host-details/{host_xid}
|
||||
path: /hosthub/hosts/get-host-details/{host_xid}
|
||||
method: get
|
||||
|
||||
inviteTeammate:
|
||||
@@ -105,7 +105,7 @@ inviteTeammate:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/settings/teammates/invite-teammate
|
||||
path: /settings/teammates/invite-teammate
|
||||
method: post
|
||||
|
||||
getAllHostApplication:
|
||||
@@ -121,7 +121,7 @@ getAllHostApplication:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/hosts/get-all-host-applications-am
|
||||
path: /hosthub/hosts/get-all-host-applications-am
|
||||
method: get
|
||||
|
||||
getAllHostActivityForAdmin:
|
||||
@@ -137,7 +137,7 @@ getAllHostActivityForAdmin:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/get-all-activity-of-host/{id}
|
||||
path: /get-all-activity-of-host/{id}
|
||||
method: get
|
||||
|
||||
getAllOnboardingHostApplications:
|
||||
@@ -153,7 +153,7 @@ getAllOnboardingHostApplications:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/onboarding/get-all-host-applications-admin
|
||||
path: /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: /minglaradmin/hosthub/onboarding/get-all-host-applications-admin-new
|
||||
path: /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: /minglaradmin/settings/teammates/get-all-invitation-details
|
||||
path: /settings/teammates/get-all-invitation-details
|
||||
method: get
|
||||
|
||||
addSuggestion:
|
||||
@@ -200,7 +200,7 @@ addSuggestion:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/hosts/add-suggestion
|
||||
path: /hosthub/hosts/add-suggestion
|
||||
method: post
|
||||
|
||||
getAllCoadminAndAMDetails:
|
||||
@@ -215,7 +215,7 @@ getAllCoadminAndAMDetails:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/settings/teammates/get-all-coadmin-am
|
||||
path: /settings/teammates/get-all-coadmin-am
|
||||
method: get
|
||||
|
||||
getAllInvitedCoadminAndAMDetails:
|
||||
@@ -230,7 +230,7 @@ getAllInvitedCoadminAndAMDetails:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/settings/teammates/get-all-invited-coadmin-am
|
||||
path: /settings/teammates/get-all-invited-coadmin-am
|
||||
method: get
|
||||
|
||||
getAmDetailsbyId:
|
||||
@@ -245,7 +245,7 @@ getAmDetailsbyId:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/settings/teammates/get-am-details-by-id/{amXid}
|
||||
path: /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: /minglaradmin/hosthub/onboarding/assign-am
|
||||
path: /hosthub/onboarding/assign-am
|
||||
method: patch
|
||||
|
||||
editAgreementDetailsAndAccept:
|
||||
@@ -277,7 +277,7 @@ editAgreementDetailsAndAccept:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/onboarding/edit-agreement-accept-host
|
||||
path: /hosthub/onboarding/edit-agreement-accept-host
|
||||
method: patch
|
||||
|
||||
getAllPqqQuesAnsForAM:
|
||||
@@ -292,7 +292,7 @@ getAllPqqQuesAnsForAM:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/onboarding/get-all-pqq-ques-ans-for-am
|
||||
path: /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: /minglaradmin/hosthub/hosts/accept-host-application
|
||||
path: /hosthub/hosts/accept-host-application
|
||||
method: patch
|
||||
|
||||
RejectPQQByAM:
|
||||
@@ -324,7 +324,7 @@ RejectPQQByAM:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/hosts/reject-pq-by-am
|
||||
path: /hosthub/hosts/reject-pq-by-am
|
||||
method: patch
|
||||
|
||||
rejectActivityDetailsApplicationByAM:
|
||||
@@ -340,7 +340,7 @@ rejectActivityDetailsApplicationByAM:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/hosts/reject-activity-application-by-am
|
||||
path: /hosthub/hosts/reject-activity-application-by-am
|
||||
method: patch
|
||||
|
||||
acceptPQByAM:
|
||||
@@ -356,7 +356,7 @@ acceptPQByAM:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/hosts/accept-pq-by-am
|
||||
path: /hosthub/hosts/accept-pq-by-am
|
||||
method: patch
|
||||
|
||||
acceptActivityDetailsApplicationByAM:
|
||||
@@ -372,7 +372,7 @@ acceptActivityDetailsApplicationByAM:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/hosts/accept-activity-application-by-am
|
||||
path: /hosthub/hosts/accept-activity-application-by-am
|
||||
method: patch
|
||||
|
||||
rejectHostApplication:
|
||||
@@ -388,7 +388,7 @@ rejectHostApplication:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/onboarding/reject-host-application
|
||||
path: /hosthub/onboarding/reject-host-application
|
||||
method: patch
|
||||
|
||||
rejectHostApplicationAM:
|
||||
@@ -404,7 +404,7 @@ rejectHostApplicationAM:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/hosts/reject-host-application-am
|
||||
path: /hosthub/hosts/reject-host-application-am
|
||||
method: patch
|
||||
|
||||
addPQQSuggestion:
|
||||
@@ -420,7 +420,7 @@ addPQQSuggestion:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/hosts/add-Pqq-suggestion
|
||||
path: /hosthub/hosts/add-Pqq-suggestion
|
||||
method: post
|
||||
|
||||
addActivitySuggestion:
|
||||
@@ -436,7 +436,7 @@ addActivitySuggestion:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/hosts/add-Activity-suggestion
|
||||
path: /hosthub/hosts/add-Activity-suggestion
|
||||
method: post
|
||||
|
||||
getAllPQPDetailsForAM:
|
||||
@@ -452,7 +452,7 @@ getAllPQPDetailsForAM:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/pqp/pqp-details-for-am/{activityXid}
|
||||
path: /hosthub/pqp/pqp-details-for-am/{activityXid}
|
||||
method: get
|
||||
|
||||
getSuggestionsForAM:
|
||||
@@ -468,5 +468,5 @@ getSuggestionsForAM:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /minglaradmin/hosthub/onboarding/show-suggestion-to-am/{hostXid}
|
||||
path: /hosthub/onboarding/show-suggestion-to-am/{hostXid}
|
||||
method: get
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
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
|
||||
@@ -13,7 +13,7 @@ getAllBankAndCurrencyDetails:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /prepopulate/get-all-bank-currency-details
|
||||
path: /get-all-bank-currency-details
|
||||
method: get
|
||||
|
||||
getCityByState:
|
||||
@@ -29,7 +29,7 @@ getCityByState:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /prepopulate/get-city-by-state
|
||||
path: /get-city-by-state
|
||||
method: get
|
||||
|
||||
getBranchByBankXid:
|
||||
@@ -45,7 +45,7 @@ getBranchByBankXid:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /prepopulate/get-branch-by-bank
|
||||
path: /get-branch-by-bank
|
||||
method: get
|
||||
|
||||
getAllDocumentCountryStateCityDetails:
|
||||
@@ -60,7 +60,7 @@ getAllDocumentCountryStateCityDetails:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /prepopulate/get-all-doc-country
|
||||
path: /get-all-doc-country
|
||||
method: get
|
||||
|
||||
getAllPqqQuesAns:
|
||||
@@ -75,7 +75,7 @@ getAllPqqQuesAns:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /prepopulate/get-all-pqq-ques-ans
|
||||
path: /get-all-pqq-ques-ans
|
||||
method: get
|
||||
|
||||
getFrequenciesOfActivity:
|
||||
@@ -90,7 +90,7 @@ getFrequenciesOfActivity:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /prepopulate/get-all-Frequencies
|
||||
path: /get-all-Frequencies
|
||||
method: get
|
||||
|
||||
getAddActivityPrePopulate:
|
||||
@@ -105,5 +105,5 @@ getAddActivityPrePopulate:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /prepopulate/get-add-activity-prepopulate
|
||||
path: /get-add-activity-prepopulate
|
||||
method: get
|
||||
|
||||
@@ -13,7 +13,7 @@ registerUser:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/register
|
||||
path: /register
|
||||
method: post
|
||||
|
||||
submitPersonalInfo:
|
||||
@@ -28,7 +28,7 @@ submitPersonalInfo:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/submit-personal-info
|
||||
path: /submit-personal-info
|
||||
method: post
|
||||
|
||||
verifyOtpForUser:
|
||||
@@ -43,7 +43,7 @@ verifyOtpForUser:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/verify-otp
|
||||
path: /verify-otp
|
||||
method: post
|
||||
|
||||
generateAccessFromRefreshToken:
|
||||
@@ -58,7 +58,7 @@ generateAccessFromRefreshToken:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/generate-access-from-refresh
|
||||
path: /generate-access-from-refresh
|
||||
method: post
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ setPasscodeForMobile:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/set-passcode
|
||||
path: /set-passcode
|
||||
method: post
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ verifyPasscode:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/verify-passcode
|
||||
path: /verify-passcode
|
||||
method: post
|
||||
|
||||
setUserInterest:
|
||||
@@ -105,7 +105,7 @@ setUserInterest:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/set-interests
|
||||
path: /set-interests
|
||||
method: post
|
||||
|
||||
setUserLocationss:
|
||||
@@ -120,7 +120,7 @@ setUserLocationss:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/set-location-user
|
||||
path: /set-location-user
|
||||
method: post
|
||||
|
||||
getLandingPageDetails:
|
||||
@@ -135,7 +135,7 @@ getLandingPageDetails:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/activities/get-landing-page-details
|
||||
path: /activities/get-landing-page-details
|
||||
method: get
|
||||
|
||||
getSurpriseMePageDetails:
|
||||
@@ -150,7 +150,7 @@ getSurpriseMePageDetails:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/activities/get-surprise-me-page-details
|
||||
path: /activities/get-surprise-me-page-details
|
||||
method: get
|
||||
|
||||
getActivityDetailsById:
|
||||
@@ -165,7 +165,7 @@ getActivityDetailsById:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/activities/get-activity-details-by-id/{activity_xid}
|
||||
path: /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: /user/activities/check-availability/{activity_xid}
|
||||
path: /activities/check-availability/{activity_xid}
|
||||
method: get
|
||||
|
||||
searchActivities:
|
||||
@@ -195,7 +195,7 @@ searchActivities:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/activities/specific-search
|
||||
path: /activities/specific-search
|
||||
method: get
|
||||
|
||||
searchSchoolsAndCompanies:
|
||||
@@ -210,7 +210,7 @@ searchSchoolsAndCompanies:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/connections/search-schools-companies
|
||||
path: /connections/search-schools-companies
|
||||
method: get
|
||||
|
||||
searchCities:
|
||||
@@ -225,7 +225,7 @@ searchCities:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/connections/search-cities
|
||||
path: /connections/search-cities
|
||||
method: get
|
||||
|
||||
addSchoolCompanyDetail:
|
||||
@@ -240,7 +240,7 @@ addSchoolCompanyDetail:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/connections/add-school-company
|
||||
path: /connections/add-school-company
|
||||
method: post
|
||||
|
||||
removeConnectionDetails:
|
||||
@@ -255,7 +255,7 @@ removeConnectionDetails:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/connections/remove-connection-details
|
||||
path: /connections/remove-connection-details
|
||||
method: delete
|
||||
|
||||
getAllConnectionOfUser:
|
||||
@@ -270,7 +270,7 @@ getAllConnectionOfUser:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/connections/get-all-connections-details
|
||||
path: /connections/get-all-connections-details
|
||||
method: get
|
||||
|
||||
getActivityFromConnectionsInterest:
|
||||
@@ -285,7 +285,22 @@ getActivityFromConnectionsInterest:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/connections/get-activity-from-connections-interest
|
||||
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
|
||||
method: get
|
||||
|
||||
viewMoreActivitiesByInterest:
|
||||
@@ -300,7 +315,7 @@ viewMoreActivitiesByInterest:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/activities/view-more-activities
|
||||
path: /activities/view-more-activities
|
||||
method: get
|
||||
|
||||
viewMoreActivitiesUpperSection:
|
||||
@@ -315,7 +330,7 @@ viewMoreActivitiesUpperSection:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/activities/view-more-activities-upper-section
|
||||
path: /activities/view-more-activities-upper-section
|
||||
method: get
|
||||
|
||||
getRandomActiveActivity:
|
||||
@@ -330,7 +345,7 @@ getRandomActiveActivity:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/activities/get-random-active-activity
|
||||
path: /activities/get-random-active-activity
|
||||
method: get
|
||||
|
||||
getNearbyActivities:
|
||||
@@ -345,5 +360,125 @@ getNearbyActivities:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/activities/get-nearby-activities
|
||||
path: /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
|
||||
|
||||
@@ -55,7 +55,7 @@ export const ACTIVITY_DISPLAY_STATUS = {
|
||||
PQ_IN_REVIEW: 'PQ In Review',
|
||||
PQ_APPROVED: 'PQ Approved',
|
||||
|
||||
ACTIVITY_DRAFT: 'Draft - Activity',
|
||||
ACTIVITY_DRAFT: 'Draft',
|
||||
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',
|
||||
ACTIVITY_DRAFT: 'Draft',
|
||||
ACTIVITY_NEW: 'New',
|
||||
ACTIVITY_TO_REVIEW: 'Activity To Review',
|
||||
ACTIVITY_ENHANCING: 'Enhancing',
|
||||
|
||||
@@ -54,7 +54,7 @@ export const EquipmentDto = z.object({
|
||||
|
||||
/* ================= NAVIGATION MODE ================= */
|
||||
export const NavigationModeDto = z.object({
|
||||
navigationModeXid: z.number().int(),
|
||||
navigationModeName: z.string().optional(),
|
||||
isChargeable: z.boolean().optional(),
|
||||
totalPrice: z.number().int().optional().default(0),
|
||||
});
|
||||
|
||||
@@ -8,6 +8,7 @@ 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);
|
||||
|
||||
@@ -177,6 +178,15 @@ 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 {
|
||||
|
||||
@@ -13,6 +13,40 @@ const hostService = new HostService(prismaClient);
|
||||
|
||||
const s3 = new AWS.S3({ region: config.aws.region });
|
||||
|
||||
function parseMultipartFieldValue(val: string) {
|
||||
if (val === '' || val === 'null' || val === 'undefined') return null;
|
||||
|
||||
const cleaned = val.trim();
|
||||
const looksLikeJson =
|
||||
(cleaned.startsWith('{') && cleaned.endsWith('}')) ||
|
||||
(cleaned.startsWith('[') && cleaned.endsWith(']')) ||
|
||||
(cleaned.startsWith('"') && cleaned.endsWith('"'));
|
||||
|
||||
if (!looksLikeJson) return val;
|
||||
|
||||
try {
|
||||
return JSON.parse(cleaned);
|
||||
} catch {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeComments(comments: unknown): string | null {
|
||||
if (comments === null || comments === undefined || comments === '') return null;
|
||||
|
||||
const value = String(comments).trim();
|
||||
if (!value) return null;
|
||||
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
return value.slice(1, -1);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Function to extract S3 key from URL
|
||||
function getS3KeyFromUrl(url: string): string {
|
||||
const bucketBaseUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
|
||||
@@ -122,22 +156,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
|
||||
bb.on("field", (fieldname, val) => {
|
||||
console.log(`FIELD RAW: ${fieldname} =`, val);
|
||||
if (val === '' || val === 'null' || val === 'undefined') fields[fieldname] = null;
|
||||
else {
|
||||
try {
|
||||
const cleaned = val.trim();
|
||||
|
||||
// If it starts and ends with quotes, remove them
|
||||
const withoutQuotes =
|
||||
(cleaned.startsWith('"') && cleaned.endsWith('"'))
|
||||
? cleaned.slice(1, -1)
|
||||
: cleaned;
|
||||
|
||||
fields[fieldname] = JSON.parse(withoutQuotes);
|
||||
} catch {
|
||||
fields[fieldname] = val;
|
||||
}
|
||||
}
|
||||
fields[fieldname] = parseMultipartFieldValue(val);
|
||||
});
|
||||
|
||||
bb.on("close", () => resolve());
|
||||
@@ -154,7 +173,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
const activityXid = Number(fields.activityXid);
|
||||
const pqqQuestionXid = Number(fields.pqqQuestionXid);
|
||||
const pqqAnswerXid = Number(fields.pqqAnswerXid);
|
||||
const comments = fields.comments || null;
|
||||
const comments = normalizeComments(fields.comments);
|
||||
|
||||
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Please provide a valid activity");
|
||||
if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Please select a valid question");
|
||||
|
||||
@@ -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,9 +25,8 @@ export const handler = safeHandler(async (
|
||||
// Verify token and get user info
|
||||
const userInfo = await verifyHostToken(token);
|
||||
|
||||
|
||||
// Add suggestion using service
|
||||
await hostService.acceptMinglarAgreement(userInfo.id);
|
||||
// Accept agreement and get dynamic fields and PDF URL
|
||||
const result = await hostService.acceptMinglarAgreement(userInfo.id);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
@@ -38,7 +37,10 @@ export const handler = safeHandler(async (
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Application accepted successfully',
|
||||
data: null,
|
||||
data: {
|
||||
filePath: result.filePath,
|
||||
dynamicFields: result.dynamicFields,
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -142,6 +142,10 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
|
||||
const deletedFiles = normalizeJsonField(fields, "deletedFiles") || [];
|
||||
const parentDeletedFiles = normalizeJsonField(fields, "parentDeletedFiles") || [];
|
||||
const deleteCompanyLogo =
|
||||
fields.deleteCompanyLogo === 'true' || fields.deleteCompanyLogo === true;
|
||||
const deleteParentCompanyLogo =
|
||||
fields.deleteParentCompanyLogo === 'true' || fields.deleteParentCompanyLogo === true;
|
||||
|
||||
/** 4) Extract and clean isDraft flag */
|
||||
const isDraft = fields.isDraft === 'true' || fields.isDraft === true;
|
||||
@@ -379,6 +383,63 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
});
|
||||
}
|
||||
|
||||
/** DELETE EXISTING LOGO IF REQUESTED */
|
||||
if (deleteCompanyLogo) {
|
||||
const existingHost = await prismaClient.hostHeader.findFirst({
|
||||
where: { userXid: userInfo.id },
|
||||
select: { logoPath: true },
|
||||
});
|
||||
|
||||
if (existingHost?.logoPath) {
|
||||
try {
|
||||
const s3Key = getS3KeyFromUrl(existingHost.logoPath);
|
||||
await deleteFromS3(s3Key);
|
||||
} catch (e) {
|
||||
console.error('S3 delete failed for company logo:', existingHost.logoPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
parsedCompany.logoPath = null;
|
||||
}
|
||||
|
||||
/** DELETE EXISTING PARENT COMPANY LOGO IF REQUESTED */
|
||||
if (deleteParentCompanyLogo && parsedCompany.isSubsidairy) {
|
||||
const existingHost = await prismaClient.hostHeader.findFirst({
|
||||
where: { userXid: userInfo.id },
|
||||
select: {
|
||||
id: true,
|
||||
hostParent: {
|
||||
select: {
|
||||
id: true,
|
||||
logoPath: true,
|
||||
},
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const existingParent = Array.isArray(existingHost?.hostParent)
|
||||
? existingHost.hostParent[0]
|
||||
: existingHost?.hostParent;
|
||||
|
||||
if (existingParent?.logoPath) {
|
||||
try {
|
||||
const s3Key = getS3KeyFromUrl(existingParent.logoPath);
|
||||
await deleteFromS3(s3Key);
|
||||
} catch (e) {
|
||||
console.error('S3 delete failed for parent company logo:', existingParent.logoPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedParentCompany) {
|
||||
parsedParentCompany.logoPath = null;
|
||||
} else {
|
||||
parsedParentCompany = {
|
||||
logoPath: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** UPLOAD LOGO (if provided) */
|
||||
const logoFile = files.find(
|
||||
(f) => f.fieldName === 'companyLogo' || f.fieldName === 'companyLogoFile'
|
||||
@@ -449,6 +510,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
parsedParentCompany,
|
||||
uploadedParentDocs,
|
||||
isDraft,
|
||||
{ deleteCompanyLogo, deleteParentCompanyLogo },
|
||||
);
|
||||
|
||||
if (!createdOrUpdated) throw new ApiError(400, 'Failed to add/update company details.');
|
||||
|
||||
@@ -39,6 +39,7 @@ export const handler = safeHandler(async (
|
||||
data: {
|
||||
stepper: host?.host?.stepper || null,
|
||||
emailAddress: host.user?.emailAddress || null,
|
||||
hostId: host.user?.userRefNumber || null,
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
344
src/modules/host/handlers/updateHostProfile.ts
Normal file
344
src/modules/host/handlers/updateHostProfile.ts
Normal file
@@ -0,0 +1,344 @@
|
||||
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
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -362,6 +362,9 @@ export class SchedulingService {
|
||||
isActive: true,
|
||||
startDate: { lte: date },
|
||||
OR: [{ endDate: null }, { endDate: { gte: date } }],
|
||||
ScheduleDetails: {
|
||||
some: {}
|
||||
}
|
||||
},
|
||||
include: {
|
||||
activityVenue: {
|
||||
|
||||
@@ -177,8 +177,8 @@ function computeBasePriceAndTaxes(
|
||||
return { basePrice, taxDetails };
|
||||
}
|
||||
|
||||
const normalize = (v?: string | null) =>
|
||||
v ? v.trim().toLowerCase() : null;
|
||||
const normalize = (v?: string | null, maxLength: number = 50) =>
|
||||
v ? v.trim().toLowerCase().substring(0, maxLength) : null;
|
||||
|
||||
async function renderAgreementPdf(vars: {
|
||||
effectiveDate: string;
|
||||
@@ -338,9 +338,11 @@ const findOrCreateState = async (
|
||||
) => {
|
||||
if (!stateName || !countryXid) return null;
|
||||
|
||||
const trimmedStateName = stateName.trim().substring(0, 50);
|
||||
|
||||
const state = await tx.states.findFirst({
|
||||
where: {
|
||||
stateName: { equals: stateName.trim(), mode: 'insensitive' },
|
||||
stateName: { equals: trimmedStateName, mode: 'insensitive' },
|
||||
countryXid,
|
||||
isActive: true,
|
||||
},
|
||||
@@ -350,7 +352,7 @@ const findOrCreateState = async (
|
||||
|
||||
const created = await tx.states.create({
|
||||
data: {
|
||||
stateName: stateName.trim(),
|
||||
stateName: trimmedStateName,
|
||||
countryXid,
|
||||
},
|
||||
});
|
||||
@@ -365,9 +367,11 @@ const findOrCreateCity = async (
|
||||
) => {
|
||||
if (!cityName || !stateXid) return null;
|
||||
|
||||
const trimmedCityName = cityName.trim().substring(0, 50);
|
||||
|
||||
const city = await tx.cities.findFirst({
|
||||
where: {
|
||||
cityName: { equals: cityName.trim(), mode: 'insensitive' },
|
||||
cityName: { equals: trimmedCityName, mode: 'insensitive' },
|
||||
stateXid,
|
||||
isActive: true,
|
||||
},
|
||||
@@ -377,7 +381,7 @@ const findOrCreateCity = async (
|
||||
|
||||
const created = await tx.cities.create({
|
||||
data: {
|
||||
cityName: cityName.trim(),
|
||||
cityName: trimmedCityName,
|
||||
stateXid,
|
||||
},
|
||||
});
|
||||
@@ -391,6 +395,22 @@ 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) { }
|
||||
@@ -415,8 +435,8 @@ export class HostService {
|
||||
});
|
||||
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: user_xid },
|
||||
select: { id: true, emailAddress: true },
|
||||
where: { id: user_xid, isActive: true },
|
||||
select: { id: true, emailAddress: true, userRefNumber: true },
|
||||
});
|
||||
return { host, user };
|
||||
}
|
||||
@@ -426,7 +446,46 @@ export class HostService {
|
||||
where: { userXid: id },
|
||||
include: {
|
||||
hostParent: {
|
||||
include: {
|
||||
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,
|
||||
HostParenetDocuments: {
|
||||
select: {
|
||||
id: true,
|
||||
@@ -459,12 +518,46 @@ export class HostService {
|
||||
select: {
|
||||
id: true,
|
||||
emailAddress: true,
|
||||
dateOfBirth: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
mobileNumber: true,
|
||||
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: {
|
||||
@@ -577,6 +670,114 @@ 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 } });
|
||||
}
|
||||
@@ -919,55 +1120,150 @@ export class HostService {
|
||||
acceptDate,
|
||||
};
|
||||
|
||||
const pdfBuffer = await renderAgreementPdf(agreementVars);
|
||||
let pdfUrl: string | null = null;
|
||||
|
||||
const existingCount = await this.prisma.hostAgreement.count({
|
||||
where: { hostXid: host.id, isActive: true },
|
||||
});
|
||||
try {
|
||||
const pdfBuffer = await renderAgreementPdf(agreementVars);
|
||||
|
||||
const nextVersionNumber = `AG${existingCount + 1}`;
|
||||
const baseKey = `Documents/Host/${host.id}/agreements/${nextVersionNumber}`;
|
||||
|
||||
const pdfKey = `${baseKey}.pdf`;
|
||||
|
||||
await s3
|
||||
.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: pdfKey,
|
||||
Body: pdfBuffer,
|
||||
ContentType: 'application/pdf',
|
||||
ACL: 'private',
|
||||
})
|
||||
.promise();
|
||||
|
||||
const pdfUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${pdfKey}`;
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
// Optional: mark previous agreements inactive
|
||||
await tx.hostAgreement.updateMany({
|
||||
const existingCount = await this.prisma.hostAgreement.count({
|
||||
where: { hostXid: host.id, isActive: true },
|
||||
data: { isActive: false },
|
||||
});
|
||||
|
||||
await tx.hostAgreement.create({
|
||||
data: {
|
||||
hostXid: host.id,
|
||||
filePath: pdfUrl,
|
||||
versionNumber: nextVersionNumber,
|
||||
isActive: true,
|
||||
},
|
||||
const nextVersionNumber = `AG${existingCount + 1}`;
|
||||
const baseKey = `Documents/Host/${host.id}/agreements/${nextVersionNumber}`;
|
||||
|
||||
const pdfKey = `${baseKey}.pdf`;
|
||||
|
||||
await s3
|
||||
.upload({
|
||||
Bucket: config.aws.bucketName,
|
||||
Key: pdfKey,
|
||||
Body: pdfBuffer,
|
||||
ContentType: 'application/pdf',
|
||||
ACL: 'private',
|
||||
})
|
||||
.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 },
|
||||
});
|
||||
|
||||
await tx.hostHeader.update({
|
||||
where: { id: host.id },
|
||||
data: {
|
||||
stepper: STEPPER.AGREEMENT_ACCEPTED,
|
||||
isApproved: true,
|
||||
agreementAccepted: true,
|
||||
agreementStartDate: host.agreementStartDate || new Date(),
|
||||
},
|
||||
const nextVersionNumber = `AG${existingCount + 1}`;
|
||||
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
// Optional: mark previous agreements inactive
|
||||
await tx.hostAgreement.updateMany({
|
||||
where: { hostXid: host.id, isActive: true },
|
||||
data: { isActive: false },
|
||||
});
|
||||
|
||||
await tx.hostAgreement.create({
|
||||
data: {
|
||||
hostXid: host.id,
|
||||
filePath: pdfUrl,
|
||||
versionNumber: nextVersionNumber,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
await tx.hostHeader.update({
|
||||
where: { id: host.id },
|
||||
data: {
|
||||
stepper: STEPPER.AGREEMENT_ACCEPTED,
|
||||
isApproved: true,
|
||||
agreementAccepted: true,
|
||||
agreementStartDate: host.agreementStartDate || new Date(),
|
||||
},
|
||||
});
|
||||
});
|
||||
} 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) {
|
||||
@@ -1095,6 +1391,10 @@ export class HostService {
|
||||
parentCompanyData?: any | null,
|
||||
parentDocuments?: HostDocumentInput[],
|
||||
isDraft: boolean = false,
|
||||
options?: {
|
||||
deleteCompanyLogo?: boolean;
|
||||
deleteParentCompanyLogo?: boolean;
|
||||
},
|
||||
) {
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
// Check if host already has a company
|
||||
@@ -1139,7 +1439,7 @@ export class HostService {
|
||||
hostStatusDisplay = HOST_STATUS_DISPLAY.UNDER_REVIEW;
|
||||
|
||||
minglarStatusInternal = MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW;
|
||||
minglarStatusDisplay = MINGLAR_STATUS_DISPLAY.TO_REVIEW;
|
||||
minglarStatusDisplay = MINGLAR_STATUS_DISPLAY.RE_SUBMITTED;
|
||||
}
|
||||
// CASE 2: Admin has rejected but host can resubmit
|
||||
else if (
|
||||
@@ -1355,7 +1655,9 @@ export class HostService {
|
||||
? { connect: { id: Number(companyData.countryXid) } }
|
||||
: undefined,
|
||||
pinCode: companyData.pinCode,
|
||||
logoPath: companyData.logoPath || existingHostCompany.logoPath,
|
||||
logoPath: options?.deleteCompanyLogo
|
||||
? companyData.logoPath ?? null
|
||||
: companyData.logoPath || existingHostCompany.logoPath,
|
||||
isSubsidairy: companyData.isSubsidairy,
|
||||
registrationNumber: companyData.registrationNumber,
|
||||
panNumber: companyData.panNumber,
|
||||
@@ -1496,10 +1798,11 @@ export class HostService {
|
||||
? { connect: { id: Number(parentCompanyData.countryXid) } }
|
||||
: undefined,
|
||||
pinCode: parentCompanyData.pinCode || null,
|
||||
logoPath:
|
||||
parentCompanyData?.logoPath ||
|
||||
existingParentCompany?.logoPath ||
|
||||
null,
|
||||
logoPath: options?.deleteParentCompanyLogo
|
||||
? parentCompanyData?.logoPath ?? null
|
||||
: parentCompanyData?.logoPath ||
|
||||
existingParentCompany?.logoPath ||
|
||||
null,
|
||||
registrationNumber: parentCompanyData.registrationNumber || null,
|
||||
panNumber: parentCompanyData.panNumber || null,
|
||||
gstNumber: parentCompanyData.gstNumber || null,
|
||||
@@ -1554,10 +1857,11 @@ export class HostService {
|
||||
? { connect: { id: Number(parentCompanyData.countryXid) } }
|
||||
: undefined,
|
||||
pinCode: parentCompanyData.pinCode || null,
|
||||
logoPath:
|
||||
parentCompanyData?.logoPath ||
|
||||
existingParentCompany?.logoPath ||
|
||||
null,
|
||||
logoPath: options?.deleteParentCompanyLogo
|
||||
? parentCompanyData?.logoPath ?? null
|
||||
: parentCompanyData?.logoPath ||
|
||||
existingParentCompany?.logoPath ||
|
||||
null,
|
||||
registrationNumber: parentCompanyData.registrationNumber || null,
|
||||
panNumber: parentCompanyData.panNumber || null,
|
||||
gstNumber: parentCompanyData.gstNumber || null,
|
||||
@@ -2374,15 +2678,9 @@ export class HostService {
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
navigationModeName: true,
|
||||
isInActivityChargeable: true,
|
||||
navigationModesTotalPrice: true,
|
||||
navigationMode: {
|
||||
select: {
|
||||
id: true,
|
||||
navigationModeName: true,
|
||||
navigationModeIcon: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
equipmentAvailable: true,
|
||||
@@ -3087,6 +3385,34 @@ 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
|
||||
* -------------------------------- */
|
||||
@@ -3706,7 +4032,7 @@ export class HostService {
|
||||
const navMode = await tx.activityNavigationModes.create({
|
||||
data: {
|
||||
activityXid,
|
||||
navigationModeXid: mode.navigationModeXid,
|
||||
navigationModeName: mode.navigationModeName,
|
||||
isInActivityChargeable: isChargeable,
|
||||
navigationModesBasePrice: basePrice,
|
||||
navigationModesTotalPrice: totalPrice,
|
||||
|
||||
@@ -76,3 +76,41 @@ 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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,10 +53,10 @@ export class TokenService {
|
||||
config.jwt.secret
|
||||
);
|
||||
|
||||
await this.prisma.token.deleteMany({
|
||||
where: { userXid: user_xid }
|
||||
})
|
||||
|
||||
// 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.create({
|
||||
data: {
|
||||
token: refreshToken.token,
|
||||
|
||||
@@ -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,6 +314,8 @@ export class MinglarService {
|
||||
companyName: true,
|
||||
user: {
|
||||
select: {
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
userRefNumber: true,
|
||||
},
|
||||
},
|
||||
@@ -375,11 +377,52 @@ export class MinglarService {
|
||||
const {
|
||||
paginationService,
|
||||
} = require('@/common/utils/pagination/pagination.service');
|
||||
return paginationService.createPaginatedResponse(
|
||||
|
||||
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(
|
||||
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(
|
||||
@@ -818,7 +861,7 @@ export class MinglarService {
|
||||
if (
|
||||
userStatus &&
|
||||
userStatus.trim().toLowerCase() ===
|
||||
MINGLAR_STATUS_DISPLAY.NEW.toLowerCase()
|
||||
MINGLAR_STATUS_DISPLAY.NEW.toLowerCase()
|
||||
) {
|
||||
filters.adminStatusInternal = MINGLAR_STATUS_INTERNAL.ADMIN_TO_REVIEW;
|
||||
}
|
||||
@@ -832,9 +875,9 @@ export class MinglarService {
|
||||
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
|
||||
display: MINGLAR_STATUS_DISPLAY.NEW,
|
||||
},
|
||||
To_Review: {
|
||||
Re_Submitted: {
|
||||
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
|
||||
display: MINGLAR_STATUS_DISPLAY.TO_REVIEW,
|
||||
display: MINGLAR_STATUS_DISPLAY.RE_SUBMITTED,
|
||||
},
|
||||
Enhancing: {
|
||||
internal: MINGLAR_STATUS_INTERNAL.AM_REJECTED,
|
||||
@@ -945,6 +988,7 @@ 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()) {
|
||||
@@ -1187,15 +1231,15 @@ export class MinglarService {
|
||||
// Build search filter if search term is provided
|
||||
const searchFilter = search
|
||||
? {
|
||||
OR: [
|
||||
{ email: { contains: search, mode: 'insensitive' as const } },
|
||||
{ firstName: { contains: search, mode: 'insensitive' as const } },
|
||||
{ lastName: { contains: search, mode: 'insensitive' as const } },
|
||||
{
|
||||
userRefNumber: { contains: search, mode: 'insensitive' as const },
|
||||
},
|
||||
],
|
||||
}
|
||||
OR: [
|
||||
{ email: { contains: search, mode: 'insensitive' as const } },
|
||||
{ firstName: { contains: search, mode: 'insensitive' as const } },
|
||||
{ lastName: { contains: search, mode: 'insensitive' as const } },
|
||||
{
|
||||
userRefNumber: { contains: search, mode: 'insensitive' as const },
|
||||
},
|
||||
],
|
||||
}
|
||||
: {};
|
||||
|
||||
// 1. Fetch all required users (Admin, Co-Admin, AM)
|
||||
@@ -1711,6 +1755,7 @@ export class MinglarService {
|
||||
isEmailVerfied: true,
|
||||
isMobileVerfied: true,
|
||||
isBiometric: true,
|
||||
createdAt: true,
|
||||
userAddressDetails: {
|
||||
select: {
|
||||
id: true,
|
||||
|
||||
@@ -27,15 +27,21 @@ export const handler = safeHandler(async (
|
||||
// 2) Authenticate user
|
||||
await verifyMinglarAdminHostToken(token);
|
||||
|
||||
// 3) Get bankXid from query params
|
||||
// 3) Get stateXid and optional search term 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.");
|
||||
}
|
||||
|
||||
// 4) Fetch branches for the bank
|
||||
const branches = await prePopulateService.getCityByStateId(stateXid);
|
||||
// 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);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
||||
@@ -39,12 +39,20 @@ export class PrePopulateService {
|
||||
}
|
||||
|
||||
|
||||
async getCityByStateId(stateXid: number) {
|
||||
async getCityByStateId(stateXid: number, search?: string) {
|
||||
return await this.prisma.cities.findMany({
|
||||
where: {
|
||||
stateXid,
|
||||
isActive: true,
|
||||
deletedAt: null
|
||||
deletedAt: null,
|
||||
...(search && search.length >= 3
|
||||
? {
|
||||
cityName: {
|
||||
contains: search,
|
||||
mode: 'insensitive',
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
@@ -132,7 +140,9 @@ export class PrePopulateService {
|
||||
}),
|
||||
]);
|
||||
|
||||
return { documentDetails, countryDetails, stateDetails, companyTypeDetails };
|
||||
const adminEmail = config.MinglarAdminEmail;
|
||||
|
||||
return { documentDetails, countryDetails, stateDetails, companyTypeDetails, adminEmail };
|
||||
}
|
||||
|
||||
async getAllFrequencies() {
|
||||
@@ -153,7 +163,6 @@ export class PrePopulateService {
|
||||
foodType,
|
||||
cuisineDetails,
|
||||
vehicleType,
|
||||
navigationMode,
|
||||
taxDetails,
|
||||
energyLevel,
|
||||
aminitiesDetails,
|
||||
@@ -171,9 +180,6 @@ export class PrePopulateService {
|
||||
this.prisma.transportModes.findMany({
|
||||
where: { isActive: true },
|
||||
}),
|
||||
this.prisma.navigationModes.findMany({
|
||||
where: { isActive: true },
|
||||
}),
|
||||
this.prisma.taxes.findMany({
|
||||
where: { isActive: true },
|
||||
}),
|
||||
@@ -215,7 +221,6 @@ export class PrePopulateService {
|
||||
foodType,
|
||||
cuisineDetails,
|
||||
vehicleType,
|
||||
navigationMode,
|
||||
taxDetails,
|
||||
energyLevel,
|
||||
aminitiesDetails,
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
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,
|
||||
}
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -50,64 +50,73 @@ 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
|
||||
);
|
||||
|
||||
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';
|
||||
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";
|
||||
|
||||
return {
|
||||
slotId: s.slotId,
|
||||
startTime: s.startTime,
|
||||
endTime: s.endTime,
|
||||
status,
|
||||
maxCapacity: s.maxCapacity,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
slotId: s.slotId,
|
||||
startTime: s.startTime,
|
||||
endTime: s.endTime,
|
||||
status,
|
||||
maxCapacity: s.maxCapacity,
|
||||
venueXid: v.id,
|
||||
venueName: v.venueName,
|
||||
venueLabel: v.venueLabel,
|
||||
venueCapacity: v.venueCapacity,
|
||||
availableSeats: v.availableSeats ?? null,
|
||||
price: v.ActivityPrices?.[0]?.sellPrice ?? null,
|
||||
endDate: header?.endDate ?? null,
|
||||
slots: roomSlots,
|
||||
slotsCount: roomSlots.length,
|
||||
venueMedia: (v.ActivityVenueArtifacts || []).map((media: any) => ({
|
||||
id: media.id,
|
||||
mediaType: media.mediaType,
|
||||
mediaFileName: media.mediaFileName,
|
||||
presignedUrl: media.presignedUrl,
|
||||
})),
|
||||
};
|
||||
});
|
||||
})
|
||||
.filter(Boolean); // ✅ removes null venues
|
||||
|
||||
return {
|
||||
venueXid: v.id,
|
||||
venueName: v.venueName,
|
||||
venueLabel: v.venueLabel,
|
||||
venueCapacity: v.venueCapacity,
|
||||
availableSeats: v.availableSeats ?? null,
|
||||
price: v.ActivityPrices?.[0]?.sellPrice ?? null,
|
||||
endDate: header?.endDate ?? null,
|
||||
slots: roomSlots,
|
||||
slotsCount: roomSlots.length,
|
||||
venueMedia: (v.ActivityVenueArtifacts || []).map((media: any) => ({
|
||||
id: media.id,
|
||||
mediaType: media.mediaType,
|
||||
mediaFileName: media.mediaFileName, // original S3 key / URL
|
||||
presignedUrl: media.presignedUrl, // presigned URL
|
||||
})),
|
||||
// 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 = {
|
||||
selectedDate,
|
||||
Venues,
|
||||
checkInTime,
|
||||
checkOutTime,
|
||||
};
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({ success: true, data: responsePayload }),
|
||||
};
|
||||
});
|
||||
|
||||
// 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 = {
|
||||
selectedDate,
|
||||
Venues,
|
||||
checkInTime,
|
||||
checkOutTime,
|
||||
};
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({ success: true, data: responsePayload }),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,46 @@
|
||||
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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
|
||||
if (!latParam || !longParam || !radiusParam) {
|
||||
throw new ApiError(400, 'lat, long and radiusKm (in km) are required as query parameters');
|
||||
}
|
||||
const userLat = latParam ? Number(latParam) : undefined;
|
||||
const userLong = longParam ? Number(longParam) : undefined;
|
||||
const radiusKm = radiusParam ? Number(radiusParam) : 15; // default 15km
|
||||
|
||||
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');
|
||||
if (
|
||||
(userLat !== undefined && Number.isNaN(userLat)) ||
|
||||
(userLong !== undefined && Number.isNaN(userLong)) ||
|
||||
Number.isNaN(radiusKm)
|
||||
) {
|
||||
throw new ApiError(400, 'Invalid lat/long values');
|
||||
}
|
||||
|
||||
const page = Number(event.queryStringParameters?.page ?? 1);
|
||||
|
||||
@@ -26,19 +26,11 @@ 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(
|
||||
userId,
|
||||
{ activityTitle, activityType, checkInCity }
|
||||
activityType
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
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,
|
||||
}
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -86,9 +86,10 @@ 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,
|
||||
},
|
||||
}),
|
||||
};
|
||||
},
|
||||
|
||||
@@ -30,16 +30,17 @@ 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 },
|
||||
select: { id: true, userPasscode: true, mobileNumber: true, firstName: true },
|
||||
});
|
||||
|
||||
let newUserLocal;
|
||||
let isNewUser = false;
|
||||
|
||||
|
||||
if (user && !user.userPasscode) {
|
||||
if (user && (!user.userPasscode || !user.firstName)) {
|
||||
// reuse existing invited user record
|
||||
newUserLocal = user;
|
||||
isNewUser = true;
|
||||
} else if (user) {
|
||||
// Fully registered user already exists
|
||||
newUserLocal = user;
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
145
src/modules/user/handlers/itinerary/saveUserItinerary.ts
Normal file
145
src/modules/user/handlers/itinerary/saveUserItinerary.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
938
src/modules/user/services/filteredLandingPage.service.ts
Normal file
938
src/modules/user/services/filteredLandingPage.service.ts
Normal file
@@ -0,0 +1,938 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
1815
src/modules/user/services/itinerary.service.ts
Normal file
1815
src/modules/user/services/itinerary.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user