Compare commits
94 Commits
split-serv
...
fcdb813fb7
| Author | SHA1 | Date | |
|---|---|---|---|
| fcdb813fb7 | |||
| 5239e04621 | |||
| fe0a2eb95b | |||
| 9df8c5443c | |||
| be0667d8e9 | |||
| a44321044f | |||
| fcac64e0a9 | |||
| 6ea2ebe5e1 | |||
| 388f3079a1 | |||
| 6a11c15f39 | |||
| ea461b6056 | |||
| 6703dc784d | |||
| e22c37bc65 | |||
| d32915c865 | |||
| 1c7ad52d0e | |||
| f1f1f199e8 | |||
| 1c32c18e03 | |||
| 0d3c71ab5a | |||
| 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",
|
||||
|
||||
@@ -70,6 +70,7 @@
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pizzip": "^3.2.0",
|
||||
"prisma": "^7.0.1",
|
||||
"razorpay": "^2.9.6",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.1",
|
||||
"serverless": "4.24.0",
|
||||
@@ -97,7 +98,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 {
|
||||
@@ -11,30 +12,32 @@ datasource db {
|
||||
}
|
||||
|
||||
model User {
|
||||
id Int @id @default(autoincrement())
|
||||
firstName String? @map("first_name") @db.VarChar(50)
|
||||
lastName String? @map("last_name") @db.VarChar(50)
|
||||
roleXid Int? @map("role_xid")
|
||||
dateOfBirth DateTime? @map("date_of_birth")
|
||||
genderName String? @map("gender_name") @db.VarChar(20)
|
||||
role Roles? @relation(fields: [roleXid], references: [id], onDelete: Restrict)
|
||||
emailAddress String? @unique @map("email_address") @db.VarChar(150)
|
||||
isdCode String? @map("isd_code") @db.VarChar(6) // +91, +1, +971 etc.
|
||||
mobileNumber String? @unique @map("mobile_number") @db.VarChar(15) // international safe limit
|
||||
userPassword String? @map("user_password") @db.VarChar(255) // hashed passwords
|
||||
userPasscode String? @map("user_passcode") @db.VarChar(255) // 4–6 digit passcode
|
||||
profileImage String? @map("profile_image") @db.VarChar(500) // S3 key or URL
|
||||
userLat String? @map("user_lat") @db.VarChar(20) // "-23.44444"
|
||||
userLong String? @map("user_long") @db.VarChar(20)
|
||||
userStatus String? @default("pending") @map("user_status") @db.VarChar(20)
|
||||
isEmailVerfied Boolean? @default(false) @map("is_email_verified")
|
||||
isMobileVerfied Boolean? @default(false) @map("is_mobile_verified")
|
||||
isProfileUpdated Boolean? @default(false) @map("is_profile_updated")
|
||||
userRefNumber String? @unique @map("user_ref_number") @db.VarChar(20)
|
||||
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")
|
||||
id Int @id @default(autoincrement())
|
||||
firstName String? @map("first_name") @db.VarChar(50)
|
||||
lastName String? @map("last_name") @db.VarChar(50)
|
||||
roleXid Int? @map("role_xid")
|
||||
dateOfBirth DateTime? @map("date_of_birth")
|
||||
genderName String? @map("gender_name") @db.VarChar(20)
|
||||
role Roles? @relation(fields: [roleXid], references: [id], onDelete: Restrict)
|
||||
emailAddress String? @unique @map("email_address") @db.VarChar(150)
|
||||
isdCode String? @map("isd_code") @db.VarChar(6) // +91, +1, +971 etc.
|
||||
mobileNumber String? @unique @map("mobile_number") @db.VarChar(15) // international safe limit
|
||||
userPassword String? @map("user_password") @db.VarChar(255) // hashed passwords
|
||||
userPasscode String? @map("user_passcode") @db.VarChar(255) // 4–6 digit passcode
|
||||
profileImage String? @map("profile_image") @db.VarChar(500) // S3 key or URL
|
||||
userLat String? @map("user_lat") @db.VarChar(20) // "-23.44444"
|
||||
userLong String? @map("user_long") @db.VarChar(20)
|
||||
userStatus String? @default("pending") @map("user_status") @db.VarChar(20)
|
||||
isEmailVerfied Boolean? @default(false) @map("is_email_verified")
|
||||
isMobileVerfied Boolean? @default(false) @map("is_mobile_verified")
|
||||
isProfileUpdated Boolean? @default(false) @map("is_profile_updated")
|
||||
userRefNumber String? @unique @map("user_ref_number") @db.VarChar(20)
|
||||
dataConsentAccepted Boolean? @default(false) @map("data_consent_accepted")
|
||||
dataConsentAcceptedOn DateTime? @map("data_consent_accepted_on")
|
||||
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")
|
||||
|
||||
// Relations
|
||||
UserOtp UserOtp[]
|
||||
@@ -58,6 +61,7 @@ model User {
|
||||
ActivitySOSDetails ActivitySOSDetails[]
|
||||
ActivityFeedbacks ActivityFeedbacks[]
|
||||
ItineraryDetails ItineraryDetails[]
|
||||
paymentOrders PaymentOrders[]
|
||||
inviteDetails InviteDetails[] @relation("InvitedUser")
|
||||
invitedInviteDetails InviteDetails[] @relation("InviterUser")
|
||||
userRevenues UserRevenue[]
|
||||
@@ -554,20 +558,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 +1042,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")
|
||||
@@ -1371,15 +1361,16 @@ model ActivityFoodCost {
|
||||
}
|
||||
|
||||
model ActivityFoodTypes {
|
||||
id Int @id @default(autoincrement())
|
||||
activityXid Int @map("activity_xid")
|
||||
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
|
||||
foodTypeXid Int @map("food_type_xid")
|
||||
foodType FoodTypes @relation(fields: [foodTypeXid], references: [id], onDelete: Restrict)
|
||||
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")
|
||||
id Int @id @default(autoincrement())
|
||||
activityXid Int @map("activity_xid")
|
||||
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
|
||||
foodTypeXid Int @map("food_type_xid")
|
||||
foodType FoodTypes @relation(fields: [foodTypeXid], references: [id], onDelete: Restrict)
|
||||
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")
|
||||
itineraryActivitySelectionFoodTypes ItineraryActivitySelectionFoodType[]
|
||||
|
||||
@@map("activity_food_types")
|
||||
@@schema("act")
|
||||
@@ -1418,18 +1409,19 @@ model ActivityFoodTaxes {
|
||||
}
|
||||
|
||||
model ActivityEquipments {
|
||||
id Int @id @default(autoincrement())
|
||||
activityXid Int @map("activity_xid")
|
||||
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
|
||||
equipmentName String @map("equipment_name") @db.VarChar(30)
|
||||
isEquipmentChargeable Boolean @default(false) @map("is_equipment_chargeable")
|
||||
equipmentBasePrice Int @map("equipment_base_price")
|
||||
equipmentTotalPrice Int @map("equipment_total_price")
|
||||
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")
|
||||
ActivityEquipmentTaxes ActivityEquipmentTaxes[]
|
||||
id Int @id @default(autoincrement())
|
||||
activityXid Int @map("activity_xid")
|
||||
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
|
||||
equipmentName String @map("equipment_name") @db.VarChar(30)
|
||||
isEquipmentChargeable Boolean @default(false) @map("is_equipment_chargeable")
|
||||
equipmentBasePrice Int @map("equipment_base_price")
|
||||
equipmentTotalPrice Int @map("equipment_total_price")
|
||||
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")
|
||||
ActivityEquipmentTaxes ActivityEquipmentTaxes[]
|
||||
itineraryActivitySelectionEquipments ItineraryActivitySelectionEquipment[]
|
||||
|
||||
@@map("activity_equipments")
|
||||
@@schema("act")
|
||||
@@ -1456,8 +1448,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")
|
||||
@@ -1466,6 +1457,7 @@ model ActivityNavigationModes {
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
ActivityNavigationModesTaxes ActivityNavigationModesTaxes[]
|
||||
ItineraryActivitySelections ItineraryActivitySelection[]
|
||||
|
||||
@@map("activity_navigation_modes")
|
||||
@@schema("act")
|
||||
@@ -1637,8 +1629,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")
|
||||
@@ -1669,29 +1661,31 @@ model ItineraryHeader {
|
||||
ItineraryMembers ItineraryMembers[]
|
||||
ItineraryStartStopDetails ItineraryStartStopDetails[]
|
||||
ItineraryActivities ItineraryActivities[]
|
||||
paymentOrders PaymentOrders[]
|
||||
|
||||
@@map("itinerary_header")
|
||||
@@schema("itn")
|
||||
}
|
||||
|
||||
model ItineraryMembers {
|
||||
id Int @id @default(autoincrement())
|
||||
itineraryHeaderXid Int @map("itinerary_header_xid")
|
||||
itineraryHeader ItineraryHeader @relation(fields: [itineraryHeaderXid], references: [id], onDelete: Cascade)
|
||||
memberXid Int @map("member_xid")
|
||||
member User @relation("MemberUser", fields: [memberXid], references: [id], onDelete: Restrict)
|
||||
memberRole String @map("member_role") @db.VarChar(30)
|
||||
memberStatus String @default("pending") @map("member_status") @db.VarChar(30)
|
||||
invitedByXid Int @map("invited_by_xid")
|
||||
invitedBy User @relation("InvitedByUser", fields: [invitedByXid], references: [id], onDelete: Restrict)
|
||||
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")
|
||||
ItineraryStartStopDetails ItineraryStartStopDetails[]
|
||||
User User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
ItineraryDetails ItineraryDetails[]
|
||||
id Int @id @default(autoincrement())
|
||||
itineraryHeaderXid Int @map("itinerary_header_xid")
|
||||
itineraryHeader ItineraryHeader @relation(fields: [itineraryHeaderXid], references: [id], onDelete: Cascade)
|
||||
memberXid Int @map("member_xid")
|
||||
member User @relation("MemberUser", fields: [memberXid], references: [id], onDelete: Restrict)
|
||||
memberRole String @map("member_role") @db.VarChar(30)
|
||||
memberStatus String @default("pending") @map("member_status") @db.VarChar(30)
|
||||
invitedByXid Int @map("invited_by_xid")
|
||||
invitedBy User @relation("InvitedByUser", fields: [invitedByXid], references: [id], onDelete: Restrict)
|
||||
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")
|
||||
ItineraryStartStopDetails ItineraryStartStopDetails[]
|
||||
User User? @relation(fields: [userId], references: [id])
|
||||
userId Int?
|
||||
ItineraryDetails ItineraryDetails[]
|
||||
itineraryActivitySelections ItineraryActivitySelection[]
|
||||
|
||||
@@map("itinerary_members")
|
||||
@@schema("itn")
|
||||
@@ -1722,41 +1716,124 @@ model ItineraryStartStopDetails {
|
||||
}
|
||||
|
||||
model ItineraryActivities {
|
||||
id Int @id @default(autoincrement())
|
||||
itineraryHeaderXid Int @map("itinerary_header_xid")
|
||||
itineraryHeader ItineraryHeader @relation(fields: [itineraryHeaderXid], references: [id], onDelete: Cascade)
|
||||
itineraryType String @map("itinerary_type") @db.VarChar(30)
|
||||
activityXid Int @map("activity_xid")
|
||||
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Restrict)
|
||||
scheduledHeaderXid Int @map("scheduled_header_xid")
|
||||
scheduledHeader ScheduleHeader @relation(fields: [scheduledHeaderXid], references: [id], onDelete: Restrict)
|
||||
occurenceDate DateTime @map("occurence_date")
|
||||
startTime String @map("start_time") @db.VarChar(30)
|
||||
endTime String @map("end_time") @db.VarChar(30)
|
||||
endDate DateTime @map("end_date")
|
||||
venueXid Int @map("venue_xid")
|
||||
venue ActivityVenues @relation(fields: [venueXid], references: [id], onDelete: Restrict)
|
||||
locationLat Float? @map("location_lat")
|
||||
locationLong Float? @map("location_long")
|
||||
locationAddress Json? @map("location_address")
|
||||
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")
|
||||
bookingStatus String @default("pending") @map("booking_status") @db.VarChar(30)
|
||||
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")
|
||||
ActivitySOSDetails ActivitySOSDetails[]
|
||||
ActivityFeedbacks ActivityFeedbacks[]
|
||||
ItineraryDetails ItineraryDetails[]
|
||||
id Int @id @default(autoincrement())
|
||||
itineraryHeaderXid Int @map("itinerary_header_xid")
|
||||
itineraryHeader ItineraryHeader @relation(fields: [itineraryHeaderXid], references: [id], onDelete: Cascade)
|
||||
displayOrder Int @default(0) @map("display_order")
|
||||
itineraryType String @map("itinerary_type") @db.VarChar(30)
|
||||
activityXid Int? @map("activity_xid")
|
||||
activity Activities? @relation(fields: [activityXid], references: [id], onDelete: Restrict)
|
||||
scheduledHeaderXid Int? @map("scheduled_header_xid")
|
||||
scheduledHeader ScheduleHeader? @relation(fields: [scheduledHeaderXid], references: [id], onDelete: Restrict)
|
||||
occurenceDate DateTime @map("occurence_date")
|
||||
startTime String @map("start_time") @db.VarChar(30)
|
||||
endTime String @map("end_time") @db.VarChar(30)
|
||||
endDate DateTime @map("end_date")
|
||||
venueXid Int? @map("venue_xid")
|
||||
venue ActivityVenues? @relation(fields: [venueXid], references: [id], onDelete: Restrict)
|
||||
locationLat Float? @map("location_lat")
|
||||
locationLong Float? @map("location_long")
|
||||
locationAddress Json? @map("location_address")
|
||||
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")
|
||||
bookingStatus String @default("pending") @map("booking_status") @db.VarChar(30)
|
||||
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")
|
||||
ActivitySOSDetails ActivitySOSDetails[]
|
||||
ActivityFeedbacks ActivityFeedbacks[]
|
||||
ItineraryDetails ItineraryDetails[]
|
||||
itineraryActivitySelections ItineraryActivitySelection[]
|
||||
|
||||
@@map("itinerary_activities")
|
||||
@@schema("itn")
|
||||
}
|
||||
|
||||
model PaymentOrders {
|
||||
id Int @id @default(autoincrement())
|
||||
userXid Int @map("user_xid")
|
||||
user User @relation(fields: [userXid], references: [id], onDelete: Cascade)
|
||||
itineraryHeaderXid Int? @map("itinerary_header_xid")
|
||||
itineraryHeader ItineraryHeader? @relation(fields: [itineraryHeaderXid], references: [id], onDelete: Cascade)
|
||||
razorpayOrderId String? @unique @map("razorpay_order_id") @db.VarChar(100)
|
||||
razorpayPaymentId String? @unique @map("razorpay_payment_id") @db.VarChar(100)
|
||||
razorpaySignature String? @map("razorpay_signature") @db.VarChar(255)
|
||||
receipt String @unique @map("receipt") @db.VarChar(100)
|
||||
amount Int @map("amount")
|
||||
currency String @default("INR") @map("currency") @db.VarChar(10)
|
||||
paymentStatus String @default("created") @map("payment_status") @db.VarChar(30)
|
||||
notes Json? @map("notes")
|
||||
verifiedAt DateTime? @map("verified_at")
|
||||
paidAt DateTime? @map("paid_at")
|
||||
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")
|
||||
|
||||
@@index([userXid, itineraryHeaderXid])
|
||||
@@map("payment_orders")
|
||||
@@schema("itn")
|
||||
}
|
||||
|
||||
model ItineraryActivitySelection {
|
||||
id Int @id @default(autoincrement())
|
||||
itineraryActivityXid Int @map("itinerary_activity_xid")
|
||||
itineraryActivity ItineraryActivities @relation(fields: [itineraryActivityXid], references: [id], onDelete: Cascade)
|
||||
itineraryMemberXid Int @map("itinerary_member_xid")
|
||||
itineraryMember ItineraryMembers @relation(fields: [itineraryMemberXid], references: [id], onDelete: Cascade)
|
||||
isFoodOpted Boolean @default(false) @map("is_food_opted")
|
||||
isTrainerOpted Boolean @default(false) @map("is_trainer_opted")
|
||||
isInActivityNavigationOpted Boolean @default(false) @map("is_in_activity_navigation_opted")
|
||||
activityNavigationModeXid Int? @map("activity_navigation_mode_xid")
|
||||
activityNavigationMode ActivityNavigationModes? @relation(fields: [activityNavigationModeXid], references: [id], onDelete: Restrict)
|
||||
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")
|
||||
selectedFoodTypes ItineraryActivitySelectionFoodType[]
|
||||
selectedEquipments ItineraryActivitySelectionEquipment[]
|
||||
|
||||
@@unique([itineraryActivityXid, itineraryMemberXid])
|
||||
@@map("itinerary_activity_selection")
|
||||
@@schema("itn")
|
||||
}
|
||||
|
||||
model ItineraryActivitySelectionFoodType {
|
||||
id Int @id @default(autoincrement())
|
||||
itineraryActivitySelectionXid Int @map("itinerary_activity_selection_xid")
|
||||
itineraryActivitySelection ItineraryActivitySelection @relation(fields: [itineraryActivitySelectionXid], references: [id], onDelete: Cascade)
|
||||
activityFoodTypeXid Int @map("activity_food_type_xid")
|
||||
activityFoodType ActivityFoodTypes @relation(fields: [activityFoodTypeXid], references: [id], onDelete: Cascade)
|
||||
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")
|
||||
|
||||
@@unique([itineraryActivitySelectionXid, activityFoodTypeXid])
|
||||
@@map("itinerary_activity_selection_food_type")
|
||||
@@schema("itn")
|
||||
}
|
||||
|
||||
model ItineraryActivitySelectionEquipment {
|
||||
id Int @id @default(autoincrement())
|
||||
itineraryActivitySelectionXid Int @map("itinerary_activity_selection_xid")
|
||||
itineraryActivitySelection ItineraryActivitySelection @relation(fields: [itineraryActivitySelectionXid], references: [id], onDelete: Cascade)
|
||||
activityEquipmentXid Int @map("activity_equipment_xid")
|
||||
activityEquipment ActivityEquipments @relation(fields: [activityEquipmentXid], references: [id], onDelete: Cascade)
|
||||
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")
|
||||
|
||||
@@unique([itineraryActivitySelectionXid, activityEquipmentXid])
|
||||
@@map("itinerary_activity_selection_equipment")
|
||||
@@schema("itn")
|
||||
}
|
||||
|
||||
model ActivitySOSDetails {
|
||||
id Int @id @default(autoincrement())
|
||||
itineraryActivityXid Int @map("itinerary_activity_xid")
|
||||
@@ -1809,8 +1886,8 @@ model ItineraryDetails {
|
||||
activityStatus String @map("activity_status") @db.VarChar(30)
|
||||
isChargeable Boolean @default(false) @map("is_chargeable")
|
||||
baseAmount Int @map("base_amount")
|
||||
totalAmount Int @map("total_amount")
|
||||
itineraryStatus String @map("itinerary_status") @db.VarChar(30)
|
||||
totalAmount Int? @map("total_amount")
|
||||
itineraryStatus String? @map("itinerary_status") @db.VarChar(30)
|
||||
isPaid Boolean @default(false) @map("is_paid")
|
||||
paidByXid Int? @map("paid_by_xid")
|
||||
paidBy User? @relation(fields: [paidByXid], references: [id], onDelete: Restrict)
|
||||
|
||||
@@ -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-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:
|
||||
@@ -522,4 +554,34 @@ openCanceledSlotForActivity:
|
||||
events:
|
||||
- httpApi:
|
||||
path: /scheduling/open-canceled-slot
|
||||
method: patch
|
||||
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,170 @@ getNearbyActivities:
|
||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||
events:
|
||||
- httpApi:
|
||||
path: /user/activities/get-nearby-activities
|
||||
method: get
|
||||
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
|
||||
|
||||
saveItineraryActivitySelections:
|
||||
handler: src/modules/user/handlers/itinerary/saveItineraryActivitySelections.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-itinerary-activity-selections
|
||||
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
|
||||
|
||||
createRazorpayOrder:
|
||||
handler: src/modules/user/handlers/payment/createOrder.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: /payment/create-order
|
||||
method: post
|
||||
|
||||
verifyRazorpayPayment:
|
||||
handler: src/modules/user/handlers/payment/verifyPayment.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: /payment/verify-payment
|
||||
method: post
|
||||
|
||||
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,
|
||||
},
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import { prismaClient } from '../../../../../common/database/prisma.lambda.servi
|
||||
import { HostService } from '../../../services/host.service';
|
||||
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||
import { sendWelcomeEmailToHost } from '../../../services/sendOTPEmail.service';
|
||||
|
||||
const hostService = new HostService(prismaClient);
|
||||
|
||||
@@ -46,7 +47,8 @@ export const handler = safeHandler(async (
|
||||
throw new ApiError(400, 'Password must be at least 8 characters long');
|
||||
}
|
||||
|
||||
await hostService.createPassword(user_xid, password);
|
||||
const result = await hostService.createPassword(user_xid, password);
|
||||
await sendWelcomeEmailToHost(result.emailAddress);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -4,13 +4,9 @@ import { prismaClient } from '../../../../../common/database/prisma.lambda.servi
|
||||
import { ROLE } from '../../../../../common/utils/constants/common.constant';
|
||||
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||
import { encryptUserId } from '../../../../../common/utils/helper/CodeGenerator';
|
||||
import { OtpGeneratorSixDigit } from '../../../../../common/utils/helper/OtpGenerator';
|
||||
import { HostService } from '../../../services/host.service';
|
||||
import { sendOtpEmailForHost } from '@/modules/host/services/sendOTPEmail.service';
|
||||
|
||||
const hostService = new HostService(prismaClient);
|
||||
|
||||
export async function generateHostRefNumber(tx: any) {
|
||||
const lastrecord = await tx.user.findFirst({
|
||||
orderBy: {
|
||||
@@ -45,13 +41,23 @@ export const handler = safeHandler(async (
|
||||
throw new ApiError(400, 'Email is required');
|
||||
}
|
||||
|
||||
const emailToLowerCase = email.toLowerCase()
|
||||
const emailToLowerCase = email.trim().toLowerCase();
|
||||
|
||||
if (!emailToLowerCase) {
|
||||
throw new ApiError(400, 'Email is required');
|
||||
}
|
||||
|
||||
// Use a single transaction for user creation/lookup and OTP storage
|
||||
const transactionResult = await prismaClient.$transaction(async (tx) => {
|
||||
const user = await tx.user.findUnique({
|
||||
where: { emailAddress: emailToLowerCase },
|
||||
select: { emailAddress: true, id: true, userPassword: true },
|
||||
select: {
|
||||
emailAddress: true,
|
||||
id: true,
|
||||
userPassword: true,
|
||||
dataConsentAccepted: true,
|
||||
dataConsentAcceptedOn: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (user && user.userPassword) {
|
||||
@@ -93,9 +99,18 @@ export const handler = safeHandler(async (
|
||||
},
|
||||
});
|
||||
|
||||
const encryptedId = encryptUserId(String(newUserLocal.id));
|
||||
await tx.user.update({
|
||||
where: { id: Number(newUserLocal.id) },
|
||||
data: {
|
||||
dataConsentAccepted: true,
|
||||
dataConsentAcceptedOn:
|
||||
user?.dataConsentAccepted && user?.dataConsentAcceptedOn
|
||||
? user.dataConsentAcceptedOn
|
||||
: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
return { newUser: newUserLocal, otp, encryptedId };
|
||||
return { newUser: newUserLocal, otp };
|
||||
});
|
||||
|
||||
if (!transactionResult || !transactionResult.otp) {
|
||||
|
||||
@@ -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;
|
||||
@@ -172,14 +176,46 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
if (fields.userProfile) {
|
||||
const userProfileRaw = normalizeJsonField(fields, 'userProfile');
|
||||
if (userProfileRaw) {
|
||||
const { firstName, lastName, mobileNumber } = userProfileRaw;
|
||||
const firstName =
|
||||
typeof userProfileRaw.firstName === 'string'
|
||||
? userProfileRaw.firstName.trim()
|
||||
: undefined;
|
||||
const lastName =
|
||||
typeof userProfileRaw.lastName === 'string'
|
||||
? userProfileRaw.lastName.trim()
|
||||
: undefined;
|
||||
const mobileNumber =
|
||||
typeof userProfileRaw.mobileNumber === 'string'
|
||||
? userProfileRaw.mobileNumber.trim()
|
||||
: undefined;
|
||||
|
||||
if (mobileNumber) {
|
||||
const existingUser = await prismaClient.user.findFirst({
|
||||
where: {
|
||||
mobileNumber,
|
||||
id: {
|
||||
not: Number(userInfo.id),
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
throw new ApiError(
|
||||
409,
|
||||
'Mobile number already exists for another user. Please use a different mobile number.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
await prismaClient.user.update({
|
||||
where: { id: userInfo.id },
|
||||
data: {
|
||||
...(firstName && { firstName }),
|
||||
...(lastName && { lastName }),
|
||||
...(mobileNumber && { mobileNumber }),
|
||||
...(firstName !== undefined && { firstName }),
|
||||
...(lastName !== undefined && { lastName }),
|
||||
...(mobileNumber !== undefined && { mobileNumber }),
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -379,6 +415,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 +542,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,10 +395,74 @@ const s3 = new AWS.S3({
|
||||
region: config.aws.region,
|
||||
});
|
||||
|
||||
function getS3KeyFromStoredPath(path?: string | null) {
|
||||
if (!path) return null;
|
||||
return path.startsWith('http') ? path.split('.com/')[1] || null : path;
|
||||
}
|
||||
|
||||
function resolveIncomingLogoPath(path?: string | null) {
|
||||
if (typeof path !== 'string') return null;
|
||||
const trimmed = path.trim();
|
||||
return trimmed.length ? trimmed : null;
|
||||
}
|
||||
|
||||
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) { }
|
||||
|
||||
private async getValidLogoUrl(
|
||||
model: 'hostHeader' | 'hostParent',
|
||||
recordId: number,
|
||||
logoPath?: string | null,
|
||||
) {
|
||||
const key = getS3KeyFromStoredPath(logoPath);
|
||||
if (!key) return null;
|
||||
|
||||
try {
|
||||
await s3
|
||||
.headObject({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
})
|
||||
.promise();
|
||||
|
||||
return await getPresignedUrl(bucket, key);
|
||||
} catch (error: any) {
|
||||
const statusCode = error?.statusCode;
|
||||
const errorCode = error?.code;
|
||||
const isMissingObject =
|
||||
statusCode === 404 ||
|
||||
errorCode === 'NotFound' ||
|
||||
errorCode === 'NoSuchKey';
|
||||
|
||||
if (isMissingObject) {
|
||||
await (this.prisma as any)[model].update({
|
||||
where: { id: recordId },
|
||||
data: { logoPath: null },
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async createHost(data: CreateHostDto) {
|
||||
return this.prisma.user.create({ data });
|
||||
}
|
||||
@@ -415,8 +483,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 };
|
||||
}
|
||||
@@ -424,9 +492,83 @@ export class HostService {
|
||||
async getHostById(id: number) {
|
||||
const host = await this.prisma.hostHeader.findFirst({
|
||||
where: { userXid: id },
|
||||
include: {
|
||||
select: {
|
||||
id: true,
|
||||
logoPath: true,
|
||||
companyName: true,
|
||||
address1: true,
|
||||
address2: true,
|
||||
pinCode: true,
|
||||
isSubsidairy: true,
|
||||
registrationNumber: true,
|
||||
panNumber: true,
|
||||
gstNumber: true,
|
||||
formationDate: true,
|
||||
companyTypeXid: true,
|
||||
websiteUrl: true,
|
||||
instagramUrl: true,
|
||||
facebookUrl: true,
|
||||
linkedinUrl: true,
|
||||
twitterUrl: true,
|
||||
stepper: true,
|
||||
hostStatusInternal: true,
|
||||
hostStatusDisplay: true,
|
||||
adminStatusInternal: true,
|
||||
adminStatusDisplay: true,
|
||||
amStatus: true,
|
||||
agreementAccepted: true,
|
||||
assignedOn: true,
|
||||
agreementStartDate: true,
|
||||
isApproved: true,
|
||||
durationNumber: true,
|
||||
durationFrequency: true,
|
||||
isCommisionBase: true,
|
||||
commisionPer: true,
|
||||
amountPerBooking: true,
|
||||
payoutDurationNum: true,
|
||||
payoutDurationFrequency: true,
|
||||
referencedBy: true,
|
||||
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 +601,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: {
|
||||
@@ -522,11 +698,15 @@ export class HostService {
|
||||
}
|
||||
|
||||
if (host?.logoPath) {
|
||||
const key = host.logoPath.startsWith('http')
|
||||
? host.logoPath.split('.com/')[1]
|
||||
: host.logoPath;
|
||||
|
||||
host.logoPath = await getPresignedUrl(bucket, key);
|
||||
const resolvedLogoUrl = await this.getValidLogoUrl(
|
||||
'hostHeader',
|
||||
host.id,
|
||||
host.logoPath,
|
||||
);
|
||||
if (!resolvedLogoUrl) {
|
||||
host.logoPath = null;
|
||||
}
|
||||
(host as any).logoPresignedUrl = resolvedLogoUrl;
|
||||
}
|
||||
|
||||
if (host.accountManager?.profileImage) {
|
||||
@@ -542,11 +722,15 @@ export class HostService {
|
||||
|
||||
// Parent company logo
|
||||
if (parent.logoPath) {
|
||||
const key = parent.logoPath.startsWith('http')
|
||||
? parent.logoPath.split('.com/')[1]
|
||||
: parent.logoPath;
|
||||
|
||||
parent.logoPath = await getPresignedUrl(bucket, key);
|
||||
const resolvedParentLogoUrl = await this.getValidLogoUrl(
|
||||
'hostParent',
|
||||
parent.id,
|
||||
parent.logoPath,
|
||||
);
|
||||
if (!resolvedParentLogoUrl) {
|
||||
parent.logoPath = null;
|
||||
}
|
||||
(parent as any).logoPresignedUrl = resolvedParentLogoUrl;
|
||||
}
|
||||
|
||||
// Parent documents
|
||||
@@ -577,6 +761,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 } });
|
||||
}
|
||||
@@ -676,10 +968,10 @@ export class HostService {
|
||||
return newUser;
|
||||
}
|
||||
|
||||
async createPassword(user_xid: number, password: string): Promise<boolean> {
|
||||
async createPassword(user_xid: number, password: string): Promise<Partial<User>> {
|
||||
// Find user by id
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: user_xid },
|
||||
where: { id: user_xid, isActive: true },
|
||||
select: { id: true, emailAddress: true, userPassword: true },
|
||||
});
|
||||
|
||||
@@ -709,7 +1001,7 @@ export class HostService {
|
||||
},
|
||||
});
|
||||
|
||||
return true;
|
||||
return user;
|
||||
}
|
||||
|
||||
async getBankBranchById(bankBranchXid: number) {
|
||||
@@ -919,55 +1211,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 +1482,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 +1530,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 +1746,11 @@ export class HostService {
|
||||
? { connect: { id: Number(companyData.countryXid) } }
|
||||
: undefined,
|
||||
pinCode: companyData.pinCode,
|
||||
logoPath: companyData.logoPath || existingHostCompany.logoPath,
|
||||
logoPath: options?.deleteCompanyLogo
|
||||
? null
|
||||
: resolveIncomingLogoPath(companyData.logoPath) ??
|
||||
existingHostCompany.logoPath ??
|
||||
null,
|
||||
isSubsidairy: companyData.isSubsidairy,
|
||||
registrationNumber: companyData.registrationNumber,
|
||||
panNumber: companyData.panNumber,
|
||||
@@ -1496,9 +1891,10 @@ export class HostService {
|
||||
? { connect: { id: Number(parentCompanyData.countryXid) } }
|
||||
: undefined,
|
||||
pinCode: parentCompanyData.pinCode || null,
|
||||
logoPath:
|
||||
parentCompanyData?.logoPath ||
|
||||
existingParentCompany?.logoPath ||
|
||||
logoPath: options?.deleteParentCompanyLogo
|
||||
? null
|
||||
: resolveIncomingLogoPath(parentCompanyData?.logoPath) ??
|
||||
existingParentCompany?.logoPath ??
|
||||
null,
|
||||
registrationNumber: parentCompanyData.registrationNumber || null,
|
||||
panNumber: parentCompanyData.panNumber || null,
|
||||
@@ -1554,9 +1950,10 @@ export class HostService {
|
||||
? { connect: { id: Number(parentCompanyData.countryXid) } }
|
||||
: undefined,
|
||||
pinCode: parentCompanyData.pinCode || null,
|
||||
logoPath:
|
||||
parentCompanyData?.logoPath ||
|
||||
existingParentCompany?.logoPath ||
|
||||
logoPath: options?.deleteParentCompanyLogo
|
||||
? null
|
||||
: resolveIncomingLogoPath(parentCompanyData?.logoPath) ??
|
||||
existingParentCompany?.logoPath ??
|
||||
null,
|
||||
registrationNumber: parentCompanyData.registrationNumber || null,
|
||||
panNumber: parentCompanyData.panNumber || null,
|
||||
@@ -2374,15 +2771,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 +3478,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 +4125,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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { brevoService } from "@/common/email/brevoApi";
|
||||
import ApiError from "@/common/utils/helper/ApiError";
|
||||
import { brevoService } from "../../../common/email/brevoApi";
|
||||
import ApiError from "../../../common/utils/helper/ApiError";
|
||||
import config from "../../../config/config";
|
||||
|
||||
export async function sendOtpEmailForHost(
|
||||
emailAddress: string,
|
||||
@@ -9,14 +10,60 @@ export async function sendOtpEmailForHost(
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = "OTP for Host Registration";
|
||||
const subject = "Your Minglar Verification Code";
|
||||
|
||||
const htmlContent = `
|
||||
<p>Dear Host,</p>
|
||||
<p>You’re almost all set! 🎉</p>
|
||||
<p>Enter <strong>${otp}</strong> to wrap your registration.</p>
|
||||
<p>This code will be valid for the next 5 minutes.</p>
|
||||
<p>Warm regards,<br/>Minglar Team</p>
|
||||
<p>Hi there 👋</p>
|
||||
<p>Here’s your verification code to get started:</p>
|
||||
<p><strong>${otp}</strong></p>
|
||||
<p>This code is valid for the next 5 minutes.</p>
|
||||
<p>Once verified, you can continue setting up your Minglar account. If you didn’t request this, you can safely ignore this email.</p>
|
||||
<p>Need help? Reach out to us at info@minglargroup.com.</p>
|
||||
<p>Warm regards,<br/><strong>Team Minglar</strong></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.");
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendWelcomeEmailToHost(
|
||||
emailAddress: string,
|
||||
): Promise<{
|
||||
sent: boolean;
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = "Get Started as a Minglar Host";
|
||||
|
||||
const htmlContent = `
|
||||
<p>Hi ${emailAddress}</p><br/>
|
||||
<p>We’re excited to have you join Minglar as a host. Welcome aboard! 🌟</p><br/>
|
||||
<p>To get started and bring your activities live, here’s what comes next:</p><br/>
|
||||
<p><strong>Your next steps:</strong></p><br/>
|
||||
<p>1. Complete your host profile</p><br/>
|
||||
<p>2. Complete the pre-qualification process for all your activities</p><br/>
|
||||
<p>3. Submit your activity details for review</p><br/>
|
||||
<p>4. Go live and start receiving bookings</p><br/>
|
||||
<p><strong>👉 Access your Host Portal:</strong></p><br/>
|
||||
<p>${config.HOST_LINK}</p><br/><br/>
|
||||
<p>If you need any support along the way, our team is always here to help. You can reach us anytime at info@minglargroup.com.</p><br/>
|
||||
<p>We’re looking forward to seeing your experiences come to life on Minglar.</p><br/>
|
||||
<p>Warm regards,<br/>Team Minglar</p>
|
||||
`;
|
||||
|
||||
try {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -50,7 +50,7 @@ export const handler = safeHandler(async (
|
||||
if (!hostDetails?.emailAddress) {
|
||||
throw new ApiError(404, 'Host details or email address not found');
|
||||
}
|
||||
await sendEmailToHostForRejectedApplication(hostDetails.emailAddress)
|
||||
await sendEmailToHostForRejectedApplication(hostDetails.emailAddress, hostDetails.firstName || 'Host');
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
|
||||
@@ -83,15 +83,16 @@ export async function sendAMPQQAcceptanceMailtoHost(
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = "Approval for your activity onboarding application";
|
||||
const subject = "Your Activity Has Been Qualified for Onboarding";
|
||||
|
||||
const htmlContent = `
|
||||
<p>Dear ${name},</p>
|
||||
<p>Congratulations, Your activity onboarding application to minglar admin has been approved.</p>
|
||||
<p>You can start adding other details of your activity through the host panel.</p>
|
||||
<p> You can login to your account using the link below:<br/>
|
||||
<strong>Link:</strong> ${config.HOST_LINK_PQ} </p>
|
||||
<p>Best regards,<br/>Minglar Team</p>
|
||||
<p>Hi ${name},</p>
|
||||
<p>We’re pleased to inform you that your activity has been qualified on the Minglar platform.</p>
|
||||
<p>You can now proceed to complete the details of your activity through the Host portal.</p>
|
||||
<p>Please click the link below to log in to your account and continue:<br/>
|
||||
<p><a href="${config.HOST_LINK_PQ}" target="_blank">${config.HOST_LINK_PQ}</a></p>
|
||||
<p>If you have any questions or need assistance, feel free to reach out at info@minglargroup.com.</p>
|
||||
<p>Warm regards,<br/><strong>Team Minglar</strong></p>
|
||||
`;
|
||||
|
||||
try {
|
||||
|
||||
@@ -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
|
||||
@@ -144,10 +144,10 @@ export class MinglarService {
|
||||
|
||||
async getUserDetails(id: number) {
|
||||
const hostDetail = await this.prisma.hostHeader.findFirst({
|
||||
where: { id: id },
|
||||
where: { id: id, isActive: true },
|
||||
});
|
||||
const userDetails = await this.prisma.user.findUnique({
|
||||
where: { id: hostDetail.userXid },
|
||||
where: { id: hostDetail.userXid, isActive: true },
|
||||
});
|
||||
return userDetails;
|
||||
}
|
||||
@@ -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,
|
||||
@@ -1826,8 +1871,8 @@ export class MinglarService {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
async rejectActivityApplicationByAM(activityId: number, user_xid: number) {
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
await tx.activities.update({
|
||||
|
||||
@@ -4,19 +4,21 @@ import config from "../../../config/config";
|
||||
|
||||
export async function sendEmailToHostForRejectedApplication(
|
||||
emailAddress: string,
|
||||
firstName: string
|
||||
): Promise<{
|
||||
sent: boolean;
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = "Rejection for your application";
|
||||
const subject = "Action Needed: Host Onboarding Application";
|
||||
|
||||
const htmlContent = `
|
||||
<p>Dear Host,</p>
|
||||
<p>Sorry to say that, But your application to minglar admin has been rejected.</p>
|
||||
<p>Please update your application and resubmit it.</p>
|
||||
<p>If you have any questions please contact to minglar admin.</p>
|
||||
<p>Best regards,<br/>Minglar Team</p>
|
||||
<p>Hi ${firstName},</p><br/><br/>
|
||||
<p>After reviewing your submission, we’re unable to proceed at this stage, as some details require further updates.<br/>
|
||||
We encourage you to log in to your Host portal to review the feedback provided and make the necessary changes.</p><br/><br/>
|
||||
<p>Host portal login : ${config.HOST_LINK}</p><br/><br/>
|
||||
<p>We appreciate your interest in Minglar and look forward to reviewing your updated application.</p><br/><br/>
|
||||
<p>Warm regards,<br/><strong>Team Minglar</strong></p>
|
||||
`;
|
||||
|
||||
try {
|
||||
@@ -47,21 +49,19 @@ export async function sendAMRejectionMailtoHost(
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = "Improvement of your application";
|
||||
const subject = "Action Needed: Host Onboarding Application";
|
||||
|
||||
const htmlContent = `
|
||||
<p>Dear ${name},</p>
|
||||
<p> Your account manager has reviewed your application and provided some suggestions. <br/>
|
||||
Please make the necessary improvements and re-submit your application to proceed with the onboarding process on Minglar.</p>
|
||||
<p> You may access your application using the link below:<br/>
|
||||
<strong>Link:</strong>
|
||||
<a href="${link}" target="_blank">
|
||||
<p>Hi ${name},</p>
|
||||
<p>After reviewing your submission, we’re unable to proceed at this stage, as some details require further updates. <br/>
|
||||
We encourage you to log in to your Host portal to review the feedback provided and make the necessary changes.</p><br/><br/>
|
||||
<p><a href="${link}" target="_blank">
|
||||
${link}
|
||||
</a>
|
||||
</p>
|
||||
<p> If you have any questions, please feel free to contact the Minglar Support Team. </p>
|
||||
<p> Best regards,<br/>
|
||||
<strong>Minglar Team</strong> </p>
|
||||
<p>We appreciate your interest in Minglar and look forward to reviewing your updated application.</p>
|
||||
<p>Warm regards,<br/>
|
||||
<strong>Team Minglar</strong></p>
|
||||
`;
|
||||
|
||||
try {
|
||||
@@ -92,20 +92,21 @@ export async function sendAMPQQRejectionMailtoHost(
|
||||
// messageId: string
|
||||
}> {
|
||||
|
||||
const subject = "Improvement of your activity onboarding application";
|
||||
const subject = "Action Needed: Activity Pre-qualification";
|
||||
|
||||
const htmlContent = `
|
||||
<p>Dear ${name},</p>
|
||||
<p>Hi ${name},</p><br/><br/>
|
||||
|
||||
<p>Your account manager has reviewed your activity application and provided some suggestions.<br/>
|
||||
Please make the necessary improvements and re-submit your activity application along with the pre-qualification answers to proceed with the onboarding process on Minglar.</p>
|
||||
<p>Thank you for taking the time to submit your activity pre-qualification detaills on the Minglar platform.<br/><br/>
|
||||
After reviewing your submission, we’re unable to approve the application at this stage. However, we encourage you to make the suggested updates and refinements, as many applications are successfully approved after revision.</p><br/><br/>
|
||||
|
||||
<p>You may access your activity onboarding application using the link below:<br/>
|
||||
<strong>Link:</strong> ${config.HOST_LINK}</p>
|
||||
<p>You can log in to the Host portal to review the feedback and continue updating your application:<br/>
|
||||
<a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a></p><br/><br/>
|
||||
|
||||
<p>If you have any questions, please feel free to contact the Minglar Support Team.</p>
|
||||
<p>If you need any guidance, feel free to reach out to us at info@minglargroup.com.</p><br/><br/>
|
||||
<p>We appreciate your interest in partnering with Minglar and look forward to reviewing your updated submission.</p><br/><br/>
|
||||
|
||||
<p>Best regards,<br/>
|
||||
<p>Warm regards,<br/>
|
||||
<strong>Minglar Team</strong></p>
|
||||
|
||||
`;
|
||||
|
||||
@@ -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,62 @@
|
||||
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 itineraryHeaderXidRaw =
|
||||
event.queryStringParameters?.itineraryHeaderXid ?? null;
|
||||
|
||||
let itineraryHeaderXid: number | undefined;
|
||||
if (
|
||||
itineraryHeaderXidRaw !== null &&
|
||||
itineraryHeaderXidRaw !== undefined &&
|
||||
itineraryHeaderXidRaw !== ''
|
||||
) {
|
||||
itineraryHeaderXid = Number(itineraryHeaderXidRaw);
|
||||
|
||||
if (!Number.isInteger(itineraryHeaderXid) || itineraryHeaderXid <= 0) {
|
||||
throw new ApiError(400, 'Invalid itineraryHeaderXid');
|
||||
}
|
||||
}
|
||||
|
||||
const result = await itineraryService.getAllUserSavedItineraries(
|
||||
userId,
|
||||
itineraryHeaderXid,
|
||||
);
|
||||
|
||||
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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1,98 @@
|
||||
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 itineraryActivityXid = Number(body.itineraryActivityXid);
|
||||
if (!Number.isInteger(itineraryActivityXid) || itineraryActivityXid <= 0) {
|
||||
throw new ApiError(400, 'itineraryActivityXid is required.');
|
||||
}
|
||||
|
||||
const selectedEquipmentIds = Array.isArray(body.selectedEquipmentIds)
|
||||
? body.selectedEquipmentIds.map((id: unknown) => Number(id))
|
||||
: [];
|
||||
const selectedFoodTypeIds = Array.isArray(body.selectedFoodTypeIds)
|
||||
? body.selectedFoodTypeIds.map((id: unknown) => Number(id))
|
||||
: [];
|
||||
|
||||
if (selectedEquipmentIds.some((id) => !Number.isInteger(id) || id <= 0)) {
|
||||
throw new ApiError(400, 'selectedEquipmentIds must contain valid ids.');
|
||||
}
|
||||
|
||||
if (selectedFoodTypeIds.some((id) => !Number.isInteger(id) || id <= 0)) {
|
||||
throw new ApiError(400, 'selectedFoodTypeIds must contain valid ids.');
|
||||
}
|
||||
|
||||
const toOptionalId = (value: unknown) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsed = Number(value);
|
||||
if (!Number.isInteger(parsed) || parsed <= 0) {
|
||||
throw new ApiError(400, 'One or more selected option ids are invalid.');
|
||||
}
|
||||
|
||||
return parsed;
|
||||
};
|
||||
|
||||
const result = await itineraryService.saveItineraryActivitySelections(userId, {
|
||||
itineraryActivityXid,
|
||||
isFoodOpted:
|
||||
body.isFoodOpted === undefined ? false : Boolean(body.isFoodOpted),
|
||||
selectedFoodTypeIds,
|
||||
isTrainerOpted:
|
||||
body.isTrainerOpted === undefined ? false : Boolean(body.isTrainerOpted),
|
||||
isInActivityNavigationOpted:
|
||||
body.isInActivityNavigationOpted === undefined
|
||||
? false
|
||||
: Boolean(body.isInActivityNavigationOpted),
|
||||
selectedNavigationModeXid: toOptionalId(body.selectedNavigationModeXid),
|
||||
selectedEquipmentIds,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Itinerary activity selections saved successfully.',
|
||||
data: result,
|
||||
}),
|
||||
};
|
||||
});
|
||||
261
src/modules/user/handlers/itinerary/saveUserItinerary.ts
Normal file
261
src/modules/user/handlers/itinerary/saveUserItinerary.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
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 (
|
||||
body.startLocationAddress === undefined ||
|
||||
body.startLocationAddress === null ||
|
||||
body.startLocationLat === undefined ||
|
||||
body.startLocationLat === null ||
|
||||
body.startLocationLong === undefined ||
|
||||
body.startLocationLong === null ||
|
||||
body.endLocationAddress === undefined ||
|
||||
body.endLocationAddress === null ||
|
||||
body.endLocationLat === undefined ||
|
||||
body.endLocationLat === null ||
|
||||
body.endLocationLong === undefined ||
|
||||
body.endLocationLong === null
|
||||
) {
|
||||
throw new ApiError(
|
||||
400,
|
||||
'startLocationAddress, startLocationLat, startLocationLong, endLocationAddress, endLocationLat and endLocationLong are required.',
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
(typeof body.startLocationAddress === 'string' &&
|
||||
!body.startLocationAddress.trim()) ||
|
||||
(typeof body.endLocationAddress === 'string' &&
|
||||
!body.endLocationAddress.trim())
|
||||
) {
|
||||
throw new ApiError(400, 'Location addresses cannot be empty.');
|
||||
}
|
||||
|
||||
if (!activities.length) {
|
||||
throw new ApiError(400, 'At least one activity is required.');
|
||||
}
|
||||
|
||||
for (const activity of activities) {
|
||||
const itineraryType =
|
||||
typeof activity.itineraryType === 'string'
|
||||
? activity.itineraryType.trim().toUpperCase().replace(/\s+/g, '_')
|
||||
: 'ACTIVITY';
|
||||
const isCustomItineraryType =
|
||||
itineraryType === 'STAY' || itineraryType === 'FREE_TIME';
|
||||
|
||||
if (
|
||||
!activity.modeOfTravel ||
|
||||
activity.travelTimeBetweenPointsMins === undefined ||
|
||||
activity.travelTimeBetweenPointsMins === null
|
||||
) {
|
||||
throw new ApiError(
|
||||
400,
|
||||
'Each itinerary item must include modeOfTravel and travelTimeBetweenPointsMins.',
|
||||
);
|
||||
}
|
||||
|
||||
if (isCustomItineraryType) {
|
||||
const customStartDate = activity.startDate || activity.occurenceDate;
|
||||
const customEndDate = activity.endDate || activity.occurenceDate;
|
||||
|
||||
if (
|
||||
!customStartDate ||
|
||||
!customEndDate ||
|
||||
!activity.selectedStartTime ||
|
||||
!activity.selectedEndTime
|
||||
) {
|
||||
throw new ApiError(
|
||||
400,
|
||||
`${itineraryType} items must include startDate, endDate, selectedStartTime and selectedEndTime.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (itineraryType === 'STAY' && !activity.locationAddress) {
|
||||
throw new ApiError(
|
||||
400,
|
||||
'STAY items must include locationAddress.',
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!activity.activityXid || !activity.venueXid || !activity.scheduleHeaderXid) {
|
||||
throw new ApiError(
|
||||
400,
|
||||
'ACTIVITY items must include activityXid, venueXid and scheduleHeaderXid.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const payload = {
|
||||
title: body.title,
|
||||
startDate: body.startDate,
|
||||
endDate: body.endDate,
|
||||
startTime: body.startTime,
|
||||
endTime: body.endTime,
|
||||
startLocationAddress: body.startLocationAddress,
|
||||
startLocationLat:
|
||||
body.startLocationLat !== null && body.startLocationLat !== undefined
|
||||
? Number(body.startLocationLat)
|
||||
: undefined,
|
||||
startLocationLong:
|
||||
body.startLocationLong !== null && body.startLocationLong !== undefined
|
||||
? Number(body.startLocationLong)
|
||||
: undefined,
|
||||
endLocationAddress: body.endLocationAddress,
|
||||
endLocationLat:
|
||||
body.endLocationLat !== null && body.endLocationLat !== undefined
|
||||
? Number(body.endLocationLat)
|
||||
: undefined,
|
||||
endLocationLong:
|
||||
body.endLocationLong !== null && body.endLocationLong !== undefined
|
||||
? Number(body.endLocationLong)
|
||||
: undefined,
|
||||
activities: activities.map((activity: any) => {
|
||||
const itineraryType =
|
||||
typeof activity.itineraryType === 'string'
|
||||
? activity.itineraryType.trim().toUpperCase().replace(/\s+/g, '_')
|
||||
: 'ACTIVITY';
|
||||
const isCustomItineraryType =
|
||||
itineraryType === 'STAY' || itineraryType === 'FREE_TIME';
|
||||
|
||||
return {
|
||||
activityXid:
|
||||
!isCustomItineraryType &&
|
||||
activity.activityXid !== undefined &&
|
||||
activity.activityXid !== null
|
||||
? Number(activity.activityXid)
|
||||
: undefined,
|
||||
venueXid:
|
||||
!isCustomItineraryType &&
|
||||
activity.venueXid !== undefined &&
|
||||
activity.venueXid !== null
|
||||
? Number(activity.venueXid)
|
||||
: undefined,
|
||||
scheduleHeaderXid:
|
||||
!isCustomItineraryType &&
|
||||
activity.scheduleHeaderXid !== undefined &&
|
||||
activity.scheduleHeaderXid !== null
|
||||
? Number(activity.scheduleHeaderXid)
|
||||
: undefined,
|
||||
modeOfTravel: activity.modeOfTravel,
|
||||
travelTimeBetweenPointsMins: Number(
|
||||
activity.travelTimeBetweenPointsMins,
|
||||
),
|
||||
kmForNextPoint:
|
||||
activity.kmForNextPoint !== undefined &&
|
||||
activity.kmForNextPoint !== null
|
||||
? Number(activity.kmForNextPoint)
|
||||
: undefined,
|
||||
startDate: activity.startDate ?? activity.occurenceDate,
|
||||
endDate: activity.endDate ?? activity.occurenceDate,
|
||||
occurenceDate: activity.occurenceDate,
|
||||
selectedStartTime: activity.selectedStartTime,
|
||||
selectedEndTime: activity.selectedEndTime,
|
||||
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) =>
|
||||
(activity.activityXid !== undefined &&
|
||||
Number.isNaN(activity.activityXid)) ||
|
||||
(activity.venueXid !== undefined && Number.isNaN(activity.venueXid)) ||
|
||||
(activity.scheduleHeaderXid !== undefined &&
|
||||
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)),
|
||||
Number.isNaN(payload.startLocationLat) ||
|
||||
Number.isNaN(payload.startLocationLong) ||
|
||||
Number.isNaN(payload.endLocationLat) ||
|
||||
Number.isNaN(payload.endLocationLong),
|
||||
)
|
||||
) {
|
||||
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,
|
||||
}),
|
||||
};
|
||||
});
|
||||
95
src/modules/user/handlers/payment/createOrder.ts
Normal file
95
src/modules/user/handlers/payment/createOrder.ts
Normal file
@@ -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 { PaymentService } from '../../services/payment.service';
|
||||
|
||||
const paymentService = new PaymentService(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 amount = Number(body.amount);
|
||||
if (!Number.isFinite(amount) || amount <= 0) {
|
||||
throw new ApiError(400, 'amount is required and must be greater than 0.');
|
||||
}
|
||||
|
||||
const currency =
|
||||
typeof body.currency === 'string' && body.currency.trim()
|
||||
? body.currency.trim().toUpperCase()
|
||||
: 'INR';
|
||||
|
||||
const receipt =
|
||||
typeof body.receipt === 'string' && body.receipt.trim()
|
||||
? body.receipt.trim()
|
||||
: undefined;
|
||||
|
||||
const notes =
|
||||
body.notes && typeof body.notes === 'object' && !Array.isArray(body.notes)
|
||||
? body.notes
|
||||
: undefined;
|
||||
|
||||
const itineraryHeaderXid =
|
||||
body.itineraryHeaderXid !== undefined && body.itineraryHeaderXid !== null
|
||||
? Number(body.itineraryHeaderXid)
|
||||
: undefined;
|
||||
|
||||
if (
|
||||
itineraryHeaderXid !== undefined &&
|
||||
(!Number.isInteger(itineraryHeaderXid) || itineraryHeaderXid <= 0)
|
||||
) {
|
||||
throw new ApiError(400, 'Invalid itineraryHeaderXid.');
|
||||
}
|
||||
|
||||
const result = await paymentService.createOrder(userId, {
|
||||
amount,
|
||||
currency,
|
||||
receipt,
|
||||
notes: {
|
||||
userId,
|
||||
...(itineraryHeaderXid !== undefined ? { itineraryHeaderXid } : {}),
|
||||
...(notes ?? {}),
|
||||
},
|
||||
itineraryHeaderXid,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Order created successfully',
|
||||
data: result,
|
||||
}),
|
||||
};
|
||||
});
|
||||
71
src/modules/user/handlers/payment/verifyPayment.ts
Normal file
71
src/modules/user/handlers/payment/verifyPayment.ts
Normal file
@@ -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 { PaymentService } from '../../services/payment.service';
|
||||
|
||||
const paymentService = new PaymentService(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 paymentId =
|
||||
typeof body.paymentId === 'string' ? body.paymentId.trim() : '';
|
||||
const orderId =
|
||||
typeof body.orderId === 'string' ? body.orderId.trim() : '';
|
||||
const signature =
|
||||
typeof body.signature === 'string' ? body.signature.trim() : '';
|
||||
|
||||
if (!paymentId || !orderId || !signature) {
|
||||
throw new ApiError(
|
||||
400,
|
||||
'paymentId, orderId and signature are required.',
|
||||
);
|
||||
}
|
||||
|
||||
const result = await paymentService.verifyPayment(userId, {
|
||||
paymentId,
|
||||
orderId,
|
||||
signature,
|
||||
});
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
success: true,
|
||||
message: 'Payment verified',
|
||||
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;
|
||||
}
|
||||
}
|
||||
2607
src/modules/user/services/itinerary.service.ts
Normal file
2607
src/modules/user/services/itinerary.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
177
src/modules/user/services/payment.service.ts
Normal file
177
src/modules/user/services/payment.service.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Prisma, PrismaClient } from '@prisma/client';
|
||||
import crypto from 'crypto';
|
||||
import Razorpay from 'razorpay';
|
||||
|
||||
import ApiError from '../../../common/utils/helper/ApiError';
|
||||
|
||||
const razorpay = new Razorpay({
|
||||
key_id: process.env.RAZORPAY_KEY_ID!,
|
||||
key_secret: process.env.RAZORPAY_KEY_SECRET!,
|
||||
});
|
||||
|
||||
@Injectable()
|
||||
export class PaymentService {
|
||||
constructor(private prisma: PrismaClient) {}
|
||||
|
||||
async createOrder(
|
||||
userXid: number,
|
||||
payload: {
|
||||
amount: number;
|
||||
currency?: string;
|
||||
receipt?: string;
|
||||
notes?: Record<string, string | number | boolean | null>;
|
||||
itineraryHeaderXid?: number;
|
||||
},
|
||||
) {
|
||||
if (!process.env.RAZORPAY_KEY_ID || !process.env.RAZORPAY_KEY_SECRET) {
|
||||
throw new ApiError(500, 'Razorpay credentials are not configured.');
|
||||
}
|
||||
|
||||
if (!Number.isFinite(payload.amount) || payload.amount <= 0) {
|
||||
throw new ApiError(400, 'amount must be a positive number.');
|
||||
}
|
||||
|
||||
if (
|
||||
payload.itineraryHeaderXid !== undefined &&
|
||||
payload.itineraryHeaderXid !== null
|
||||
) {
|
||||
const itineraryHeader = await this.prisma.itineraryHeader.findFirst({
|
||||
where: {
|
||||
id: payload.itineraryHeaderXid,
|
||||
ownerXid: userXid,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!itineraryHeader) {
|
||||
throw new ApiError(
|
||||
404,
|
||||
'Itinerary not found for the logged-in user.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const amountInPaise = Math.round(payload.amount * 100);
|
||||
const receipt = payload.receipt ?? `receipt_${Date.now()}`;
|
||||
const currency = payload.currency ?? 'INR';
|
||||
|
||||
const order = (await razorpay.orders.create({
|
||||
amount: amountInPaise,
|
||||
currency,
|
||||
receipt,
|
||||
notes: payload.notes ?? undefined,
|
||||
} as any)) as any;
|
||||
|
||||
const paymentOrder = await this.prisma.paymentOrders.create({
|
||||
data: {
|
||||
userXid,
|
||||
itineraryHeaderXid: payload.itineraryHeaderXid ?? null,
|
||||
razorpayOrderId: order.id,
|
||||
receipt: order.receipt ?? receipt,
|
||||
amount: order.amount ?? amountInPaise,
|
||||
currency: order.currency ?? currency,
|
||||
paymentStatus: order.status ?? 'created',
|
||||
notes: payload.notes
|
||||
? (payload.notes as Prisma.InputJsonValue)
|
||||
: null,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
paymentOrderId: paymentOrder.id,
|
||||
orderId: order.id,
|
||||
amount: paymentOrder.amount,
|
||||
currency: paymentOrder.currency,
|
||||
receipt: paymentOrder.receipt,
|
||||
status: paymentOrder.paymentStatus,
|
||||
};
|
||||
}
|
||||
|
||||
async verifyPayment(
|
||||
userXid: number,
|
||||
payload: {
|
||||
paymentId: string;
|
||||
orderId: string;
|
||||
signature: string;
|
||||
},
|
||||
) {
|
||||
if (!process.env.RAZORPAY_KEY_SECRET) {
|
||||
throw new ApiError(500, 'Razorpay credentials are not configured.');
|
||||
}
|
||||
|
||||
const paymentId = payload.paymentId?.trim();
|
||||
const orderId = payload.orderId?.trim();
|
||||
const signature = payload.signature?.trim();
|
||||
|
||||
if (!paymentId || !orderId || !signature) {
|
||||
throw new ApiError(
|
||||
400,
|
||||
'paymentId, orderId and signature are required.',
|
||||
);
|
||||
}
|
||||
|
||||
const generatedSignature = crypto
|
||||
.createHmac('sha256', process.env.RAZORPAY_KEY_SECRET)
|
||||
.update(`${orderId}|${paymentId}`)
|
||||
.digest('hex');
|
||||
|
||||
if (generatedSignature !== signature) {
|
||||
throw new ApiError(400, 'Invalid signature.');
|
||||
}
|
||||
|
||||
const paymentOrder = await this.prisma.paymentOrders.findFirst({
|
||||
where: {
|
||||
razorpayOrderId: orderId,
|
||||
userXid,
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
});
|
||||
|
||||
if (!paymentOrder) {
|
||||
throw new ApiError(404, 'Payment order not found.');
|
||||
}
|
||||
|
||||
if (
|
||||
paymentOrder.paymentStatus === 'paid' &&
|
||||
paymentOrder.razorpayPaymentId === paymentId
|
||||
) {
|
||||
return {
|
||||
paymentOrderId: paymentOrder.id,
|
||||
orderId: paymentOrder.razorpayOrderId,
|
||||
paymentId: paymentOrder.razorpayPaymentId,
|
||||
status: paymentOrder.paymentStatus,
|
||||
verifiedAt: paymentOrder.verifiedAt,
|
||||
paidAt: paymentOrder.paidAt,
|
||||
};
|
||||
}
|
||||
|
||||
const updatedPaymentOrder = await this.prisma.paymentOrders.update({
|
||||
where: {
|
||||
id: paymentOrder.id,
|
||||
},
|
||||
data: {
|
||||
razorpayPaymentId: paymentId,
|
||||
razorpaySignature: signature,
|
||||
paymentStatus: 'paid',
|
||||
verifiedAt: new Date(),
|
||||
paidAt: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
paymentOrderId: updatedPaymentOrder.id,
|
||||
orderId: updatedPaymentOrder.razorpayOrderId,
|
||||
paymentId: updatedPaymentOrder.razorpayPaymentId,
|
||||
status: updatedPaymentOrder.paymentStatus,
|
||||
verifiedAt: updatedPaymentOrder.verifiedAt,
|
||||
paidAt: updatedPaymentOrder.paidAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user