64 Commits

Author SHA1 Message Date
e4a2a04045 fixed the coverimage issue for random activities and addbucket issue also resolved and taking the group count 2026-03-25 13:34:12 +05:30
50ce8e39c5 sending checkin lat long in the user itinerary api and city state country details of the parent company in the getbyid api of host 2026-03-25 12:36:47 +05:30
19e57f0e7f fixed the new user condition in the user module 2026-03-25 11:33:57 +05:30
ad5e343b66 changed the draft-activity to draft 2026-03-24 17:38:07 +05:30
8c3ece6ebd checking the activity title should not be same 2026-03-24 14:42:09 +05:30
092f425bb3 changed the to review 2026-03-24 14:41:25 +05:30
b1a3afd3a1 Sending mail on the final submission of the pqp 2026-03-23 19:31:19 +05:30
97f431260d sending the admin email in the prepopulate 2026-03-23 15:04:05 +05:30
bf6d9ae00b sending created at 2026-03-20 15:15:40 +05:30
518ec4eb21 made save itinerary and get itinerary details apis 2026-03-18 19:49:04 +05:30
95b061b400 made searchConnectionPeople to invite in the itinerary API 2026-03-18 13:30:41 +05:30
92992797ab sending the filter details in the getMatchingBucketInterestedActivities API 2026-03-18 13:21:32 +05:30
c96e3b0c1a sending the energyLevel details also in the getUserItineraryDetails service 2026-03-18 12:45:59 +05:30
f23b93801c making the energylevelxid optional 2026-03-18 11:09:57 +05:30
f1801a3210 made getMatchingBucketInterestedActivities api 2026-03-17 16:22:03 +05:30
2588ca4317 fixed the path 2026-03-17 14:28:56 +05:30
e809ba4480 sending mail after host submits pqp questioniare and not sending the applications with new status in the host applications 2026-03-16 15:50:27 +05:30
678be7c905 fixed the issue of path 2026-03-16 15:27:20 +05:30
08b4231e5f Merge branch 'Split-lambda' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayankSprint2 2026-03-16 11:30:53 +05:30
paritosh18
4a61e2c412 prepopulate change 2026-03-16 11:19:54 +05:30
a3ab9db5a2 Made getUserItineraryDetails api for the prepopulate data of itinerary page in mobile 2026-03-15 20:33:45 +05:30
paritosh18
7167eae07e fixed the path in the user yml file 2026-03-13 17:31:29 +05:30
paritosh18
4f3d7fd737 Merge branch 'mayankSprint2' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into Split-lambda 2026-03-13 17:30:27 +05:30
paritosh18
199013b0f5 split lambda 2026-03-13 17:27:51 +05:30
0b81dbf7b1 made a new getAllBucketActivities api 2026-03-13 13:09:25 +05:30
paritosh18
9722e1988c Merge branch 'mayankSprint2' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into Split-lambda 2026-03-13 13:05:14 +05:30
cf2bbbf138 fixed the response structure in the specific api 2026-03-11 15:53:54 +05:30
paritosh18
2ca785248f add serverless-plugin-split-stacks and update serverless configuration; refactor host function definitions and improve updateHostProfile handler 2026-03-11 15:42:41 +05:30
0aa2b9b53e fixed the search api for the specific and sending by default 15 km radius activities in the nearby activity api 2026-03-11 13:41:15 +05:30
b5cdb20c4f sending the latestactivity image in the bucket 2026-03-10 21:30:43 +05:30
00e07113e5 sending the latest added activity image in the add to bucket api and fixed the presignedurl in the getFilteredLandingPageAllDetails api and fixed the logic to send the activities from the selected activityxids 2026-03-10 19:36:44 +05:30
c8f0f93792 sending the duration cheapprice per person and sustanibility score in the surprise me api 2026-03-10 18:12:26 +05:30
87779664d1 sending the activity type xids in the search api of I am specific 2026-03-10 15:26:57 +05:30
5b31e5f2a9 sending the activity count in the submit personal info 2026-03-09 18:49:40 +05:30
2a073c44a2 Merge branch 'paritosh-main1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayankSprint2 2026-03-09 16:09:36 +05:30
paritosh18
220d309087 Added profile image upload functionality to updateHostProfile API, including multipart form data handling and S3 integration for image storage. 2026-03-09 16:09:20 +05:30
f45c33ba83 sending the random activities in the getconnection api 2026-03-09 16:00:31 +05:30
22b3593150 sending 5 random activities in the get surprise me api 2026-03-09 15:57:14 +05:30
d186681ee4 added isActive condition 2026-03-09 15:51:58 +05:30
8f428fc1cb fixed the i am specific new api issue 2026-03-09 15:18:59 +05:30
0b503cf8bb sending the checkIn and checkOut location 2026-03-06 19:59:31 +05:30
7110d0462c Merge branch 'paritosh-main1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayankSprint2 2026-03-06 17:37:56 +05:30
96648fe37e sending the isBukcet and isInterested flag in getbyid api and changed the logic in the getactivitiesfrom connections api 2026-03-06 17:36:14 +05:30
paritosh18
2095f8e124 filtered landing page for specific search api 2026-03-06 17:11:21 +05:30
21c8799502 Merge branch 'paritosh-main1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayankSprint2 2026-03-06 15:49:46 +05:30
paritosh18
ad9e8e1a3f Added remove api from interested 2026-03-06 15:49:25 +05:30
b200e2cb94 sending the distance in the getbyid of activity 2026-03-06 15:49:13 +05:30
cae66237d2 fixed the check availability api and sending the interested and bucket count in the connection api 2026-03-06 15:40:21 +05:30
25be8a5647 sending the distance in connection activities api 2026-03-05 19:21:16 +05:30
7a4aecdd45 Merge branch 'paritosh-main1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayankSprint2 2026-03-05 18:58:34 +05:30
5cced2981a sending the distance in the apis 2026-03-05 18:57:58 +05:30
paritosh18
b9fbab3717 Merge branch 'mayankSprint2' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh-main1 2026-03-05 18:52:32 +05:30
paritosh18
90c897ad48 remove deleteMany call to allow reuse of refresh tokens in generateAuthToken method 2026-03-05 18:44:57 +05:30
4a069cc67a if there is no slot then the venue will not be sent 2026-03-05 18:12:35 +05:30
paritosh18
5d046c4bcf update ActivityOtherDetails model to change string fields to text type for better data handling 2026-03-05 16:54:43 +05:30
accfc4b769 sending the safety instruction and cancellations in the getbyid api of activity in user 2026-03-05 16:15:15 +05:30
e149884f72 sending hostheader data also in the get latest agreement details api response 2026-03-05 13:05:56 +05:30
a31ec97640 Merge branch 'paritosh-main1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayankSprint2 2026-03-05 12:49:44 +05:30
0c97412057 sending pincode in getbyid 2026-03-05 11:25:33 +05:30
paritosh18
b4ff39c0d7 removed logs 2026-03-04 18:59:37 +05:30
bb5da7647b sending the address details in the getbyid host api 2026-03-04 18:57:14 +05:30
3f19bb4087 Merge branch 'paritosh-main1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayankSprint2 2026-03-04 18:45:44 +05:30
be8b9cef7d gbbb 2026-03-04 18:45:33 +05:30
paritosh18
77cef98091 Implement updateHostProfile endpoint and related service logic; remove navigation modes seeding; add logging for user activities 2026-03-04 17:04:14 +05:30
43 changed files with 6014 additions and 577 deletions

533
package-lock.json generated
View File

@@ -13,7 +13,7 @@
"@aws-crypto/sha256-browser": "^5.2.0",
"@aws-crypto/sha256-js": "^5.2.0",
"@aws-sdk/client-s3": "^3.928.0",
"@aws-sdk/s3-request-presigner": "^3.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",

View File

@@ -99,6 +99,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",

View File

@@ -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 {
@@ -1038,13 +1039,13 @@ model ActivityOtherDetails {
id Int @id @default(autoincrement())
activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
exclusiveNotes String? @map("exclusive_notes") @db.VarChar(500)
SafetyInstruction String? @map("safety_instruction") @db.VarChar(400)
Cancellations String? @map("cancellations") @db.VarChar(400)
dosNotes String? @map("dos_notes") @db.VarChar(400)
dontsNotes String? @map("donts_notes") @db.VarChar(400)
tipsNotes String? @map("tips_notes") @db.VarChar(400)
termsAndCondition String? @map("terms_and_condition") @db.VarChar(500)
exclusiveNotes String? @map("exclusive_notes") @db.Text
SafetyInstruction String? @map("safety_instruction") @db.Text
Cancellations String? @map("cancellations") @db.Text
dosNotes String? @map("dos_notes") @db.Text
dontsNotes String? @map("donts_notes") @db.Text
tipsNotes String? @map("tips_notes") @db.Text
termsAndCondition String? @map("terms_and_condition") @db.Text
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@ -1727,8 +1728,8 @@ model ItineraryActivities {
travelMode String? @map("travel_mode") @db.VarChar(30)
kmForNextPoint Float? @map("km_for_next_point")
timeForNextPointMins Int? @map("time_for_next_point_mins")
paxCount Int @map("pax_count")
totalAmount Int @map("total_amount")
paxCount Int? @map("pax_count")
totalAmount Int? @map("total_amount")
bookingStatus String @default("pending") @map("booking_status") @db.VarChar(30)
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")

View File

@@ -268,9 +268,9 @@ async function main() {
create: { interestName: 'Nightlife & Events', displayOrder: 10, interestColor: 'Blue', interestImage: 'https://minglar-dev-bucket.s3.ap-south-1.amazonaws.com/StaticImages/InterestTypes/NightlifeandEvents.png', interestCode: 'NE' },
});
const furfam = await prisma.interests.upsert({
where: { interestName: '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: [

13
serverless.host.yml Normal file
View File

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

View 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)}

View File

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

View File

@@ -0,0 +1,32 @@
service: minglar-prisma-layer
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'}
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
layers:
prisma:
path: layers/prisma
name: ${self:service}-prisma-layer-${sls:stage}
description: Prisma 7 client with pg driver adapter (no binary engines)
compatibleRuntimes:
- nodejs22.x
retain: false

13
serverless.user.yml Normal file
View File

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

View File

@@ -1,3 +1,4 @@
# Legacy monolith config. For new deployments use serverless.*.yml files.
service: minglarDev
@@ -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
@@ -73,10 +79,6 @@ provider:
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}'
- 'arn:aws:s3:::${env:S3_BUCKET_NAME}/*'
custom:
serverless-offline:
reloadHandler: true
build:
esbuild:
bundle: true
@@ -145,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

134
serverless/common.yml Normal file
View File

@@ -0,0 +1,134 @@
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'}
deploymentBucket:
# use a fixed bucket name to prevent Serverless from creating/quashing a resource
name: serverless-framework-deployments-ap-south-1-50264b8e-d2b9
# optionally uncomment below to enable serverless to create if missing
# serverSideEncryption: AES256
versionFunctions: false
memorySize: 512
# Apply Prisma layer to all functions
# Reference the layer defined in the dedicated layer stack
layers:
- ${cf:minglar-prisma-layer-${sls:stage}.PrismaLambdaLayerQualifiedArn}
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}/*'
build:
esbuild:
bundle: true
minify: true
sourcemap: false
target: node20
platform: node
# Mark as external so they're not bundled into the JS
external:
- '@prisma/client'
- '.prisma/client'
- '.prisma'
- '@prisma/adapter-pg'
- 'pg'
- 'zod'
- '@aws-sdk/*'
- '@smithy/*'
- '@aws-crypto/*'
# Exclude prevents npm install of these packages in the zip
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
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/**'
plugins:
- serverless-offline
custom:
serverless-offline:
reloadHandler: true

View File

@@ -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,7 +255,7 @@ 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:
@@ -271,7 +271,7 @@ getLatestAgreement:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /host/Host_Admin/onboarding/get-latest-agreement
path: /Host_Admin/onboarding/get-latest-agreement
method: get
getStepperInfo:
@@ -292,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
@@ -304,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:
@@ -320,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:
@@ -336,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:
@@ -352,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:
@@ -367,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:
@@ -382,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:
@@ -397,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:
@@ -538,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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,7 +360,7 @@ getNearbyActivities:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /user/activities/get-nearby-activities
path: /activities/get-nearby-activities
method: get
addActivityToBucketInterested:
@@ -360,5 +375,110 @@ addActivityToBucketInterested:
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /user/activities/add-to-bucket-interested
method: post
path: /activities/add-to-bucket-interested
method: post
removeActivityFromBucketInterested:
handler: src/modules/user/handlers/activities/removeFromBucketInterested.handler
memorySize: 384
package:
patterns:
- 'src/modules/user/handlers/activities/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/remove-from-bucket-interested
method: post
getFilteredLandingPageAllDetails:
handler: src/modules/user/handlers/activities/filteredLandingPageAllDetails.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/get-filtered-landing-page-details
method: get
getAllBucketActivities:
handler: src/modules/user/handlers/activities/getAllBucketActivities.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /activities/get-all-bucket-activities
method: get
getUserItineraryDetails:
handler: src/modules/user/handlers/itinerary/getUserItineraryDetails.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /itinerary/get-user-itinerary-details
method: get
saveUserItinerary:
handler: src/modules/user/handlers/itinerary/saveUserItinerary.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /itinerary/save-user-itinerary
method: post
getAllUserSavedItineraries:
handler: src/modules/user/handlers/itinerary/getAllUserSavedItineraries.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /itinerary/get-all-user-saved-itineraries
method: get
getMatchingBucketInterestedActivities:
handler: src/modules/user/handlers/itinerary/getMatchingBucketInterestedActivities.handler
memorySize: 512
package:
patterns:
- 'src/modules/user/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /itinerary/get-matching-bucket-interested-activities
method: post

View File

@@ -55,7 +55,7 @@ export const ACTIVITY_DISPLAY_STATUS = {
PQ_IN_REVIEW: 'PQ In Review',
PQ_APPROVED: 'PQ Approved',
ACTIVITY_DRAFT: 'Draft - Activity',
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',

View File

@@ -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 {

View File

@@ -1,6 +1,6 @@
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { prismaClient } from '../../../../../common/database/prisma.lambda.service';
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service';
@@ -25,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,
},
}),
};
});

View 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
}),
};
});

View File

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

View File

@@ -177,8 +177,8 @@ function computeBasePriceAndTaxes(
return { basePrice, taxDetails };
}
const normalize = (v?: string | null) =>
v ? v.trim().toLowerCase() : null;
const normalize = (v?: string | null, maxLength: number = 50) =>
v ? v.trim().toLowerCase().substring(0, maxLength) : null;
async function renderAgreementPdf(vars: {
effectiveDate: string;
@@ -338,9 +338,11 @@ const findOrCreateState = async (
) => {
if (!stateName || !countryXid) return null;
const trimmedStateName = stateName.trim().substring(0, 50);
const state = await tx.states.findFirst({
where: {
stateName: { equals: stateName.trim(), mode: 'insensitive' },
stateName: { equals: trimmedStateName, mode: 'insensitive' },
countryXid,
isActive: true,
},
@@ -350,7 +352,7 @@ const findOrCreateState = async (
const created = await tx.states.create({
data: {
stateName: stateName.trim(),
stateName: trimmedStateName,
countryXid,
},
});
@@ -365,9 +367,11 @@ const findOrCreateCity = async (
) => {
if (!cityName || !stateXid) return null;
const trimmedCityName = cityName.trim().substring(0, 50);
const city = await tx.cities.findFirst({
where: {
cityName: { equals: cityName.trim(), mode: 'insensitive' },
cityName: { equals: trimmedCityName, mode: 'insensitive' },
stateXid,
isActive: true,
},
@@ -377,7 +381,7 @@ const findOrCreateCity = async (
const created = await tx.cities.create({
data: {
cityName: cityName.trim(),
cityName: trimmedCityName,
stateXid,
},
});
@@ -391,6 +395,22 @@ const s3 = new AWS.S3({
region: config.aws.region,
});
type UpdateHostProfileInput = {
firstName?: string;
lastName?: string | null;
isdCode?: string;
mobileNumber?: string;
dateOfBirth?: Date;
address?: {
address1?: string;
address2?: string;
countryXid?: number;
stateXid?: number;
cityXid?: number;
pinCode?: string;
};
};
@Injectable()
export class HostService {
constructor(private prisma: PrismaClient) { }
@@ -426,7 +446,46 @@ export class HostService {
where: { userXid: id },
include: {
hostParent: {
include: {
select: {
id: true,
logoPath: true,
companyName: true,
address1: true,
address2: true,
cities: {
select: {
id: true,
cityName: true
}
},
states: {
select: {
id: true,
stateName: true
}
},
countries: {
select: {
id: true,
countryName: true
}
},
pinCode: true,
registrationNumber: true,
panNumber: true,
gstNumber: true,
formationDate: true,
companyTypes: {
select: {
id: true,
companyTypeName: true
}
},
websiteUrl: true,
instagramUrl: true,
facebookUrl: true,
linkedinUrl: true,
twitterUrl: true,
HostParenetDocuments: {
select: {
id: true,
@@ -465,6 +524,39 @@ export class HostService {
profileImage: true,
userStatus: true,
userRefNumber: true,
userAddressDetails: {
where: { isActive: true },
select: {
id: true,
address1: true,
address2: true,
locationAddress: true,
locationLat: true,
locationLong: true,
pinCode: true,
cityXid: true,
cities: {
select: {
id: true,
cityName: true,
}
},
stateXid: true,
states: {
select: {
id: true,
stateName: true,
}
},
countryXid: true,
country: {
select: {
id: true,
countryName: true
}
}
}
}
},
},
companyTypes: {
@@ -577,6 +669,114 @@ export class HostService {
return this.prisma.user.delete({ where: { id } });
}
/**
* Update the logged-in Host's personal profile details.
* Email is intentionally NOT editable here.
*/
async updateHostProfileDetails(userId: number, input: UpdateHostProfileInput) {
return this.prisma.$transaction(async (tx) => {
const user = await tx.user.findUnique({
where: { id: userId, isActive: true },
select: { id: true, roleXid: true },
});
if (!user) throw new ApiError(404, 'User not found');
if (user.roleXid !== ROLE.HOST) throw new ApiError(403, 'Access denied.');
// 1) Update `User` (whitelist only)
const userUpdateData: any = {};
if (input.firstName !== undefined) userUpdateData.firstName = input.firstName || null;
if (input.lastName !== undefined) userUpdateData.lastName = input.lastName;
if (input.isdCode !== undefined) userUpdateData.isdCode = input.isdCode || null;
if (input.mobileNumber !== undefined) userUpdateData.mobileNumber = input.mobileNumber || null;
if (input.dateOfBirth !== undefined) userUpdateData.dateOfBirth = input.dateOfBirth;
if (Object.keys(userUpdateData).length > 0) {
await tx.user.update({
where: { id: userId },
data: {
...userUpdateData,
isProfileUpdated: true,
},
});
}
// 2) Update/Create `UserAddressDetails` (if any address field sent)
const addressData = input.address || {};
const hasAnyAddressField = Object.values(addressData).some((v) => v !== undefined);
if (hasAnyAddressField) {
const existingAddress = await tx.userAddressDetails.findFirst({
where: { userXid: userId, isActive: true },
select: { id: true },
});
const addressUpdateData: any = {};
if (addressData.address1 !== undefined) addressUpdateData.address1 = addressData.address1;
if (addressData.address2 !== undefined) addressUpdateData.address2 = addressData.address2;
if (addressData.countryXid !== undefined) addressUpdateData.countryXid = addressData.countryXid;
if (addressData.stateXid !== undefined) addressUpdateData.stateXid = addressData.stateXid;
if (addressData.cityXid !== undefined) addressUpdateData.cityXid = addressData.cityXid;
if (addressData.pinCode !== undefined) addressUpdateData.pinCode = addressData.pinCode;
if (existingAddress) {
await tx.userAddressDetails.update({
where: { id: existingAddress.id },
data: addressUpdateData,
});
} else {
const required = ['address1', 'countryXid', 'stateXid', 'cityXid', 'pinCode'] as const;
const missing = required.filter((k) => addressData[k] === undefined);
if (missing.length) {
throw new ApiError(400, `Missing required address fields: ${missing.join(', ')}`);
}
await tx.userAddressDetails.create({
data: {
userXid: userId,
...addressUpdateData,
},
});
}
}
// 3) Return updated profile snapshot (including read-only email)
const updated = await tx.user.findUnique({
where: { id: userId },
select: {
id: true,
firstName: true,
lastName: true,
emailAddress: true,
isdCode: true,
mobileNumber: true,
dateOfBirth: true,
profileImage: true,
isProfileUpdated: true,
userAddressDetails: {
where: { isActive: true },
take: 1,
select: {
id: true,
address1: true,
address2: true,
countryXid: true,
stateXid: true,
cityXid: true,
pinCode: true,
},
},
},
});
return {
user: updated,
address: updated?.userAddressDetails?.[0] ?? null,
};
});
}
async getHostByEmail(email: string): Promise<User> {
return this.prisma.user.findUnique({ where: { emailAddress: email } });
}
@@ -919,55 +1119,79 @@ 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,
};
}
/**
@@ -978,6 +1202,29 @@ export class HostService {
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' },
@@ -991,23 +1238,30 @@ export class HostService {
},
});
if (!agreement) {
// ❌ If both missing
if (!agreement && !hostHeader) {
throw new ApiError(404, 'No active agreement found for this host');
}
const filePath = agreement.filePath;
let presignedUrl = "";
// If full URL is saved, extract only S3 key part
const key = filePath.startsWith('http')
? filePath.split('.com/')[1]
: filePath;
if (agreement?.filePath) {
const key = agreement.filePath.startsWith('http')
? agreement.filePath.split('.com/')[1]
: agreement.filePath;
const bucket = config.aws.bucketName;
const presignedUrl = await getPresignedUrl(bucket, key);
const bucket = config.aws.bucketName;
presignedUrl = await getPresignedUrl(bucket, key);
}
return {
...agreement,
presignedUrl,
hostHeader: hostHeader || null,
agreement: agreement
? {
...agreement,
presignedUrl
}
: null
};
}
@@ -1180,7 +1434,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 (
@@ -3122,6 +3376,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
* -------------------------------- */

View File

@@ -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.");
}
}

View File

@@ -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,

View File

@@ -34,7 +34,7 @@ const bucket = config.aws.bucketName;
@Injectable()
export class MinglarService {
constructor(private prisma: PrismaService | PrismaClient) {}
constructor(private prisma: PrismaService | PrismaClient) { }
async createPassword(user_xid: number, password: string): Promise<boolean> {
// Find user by id
@@ -314,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({

View File

@@ -140,7 +140,9 @@ export class PrePopulateService {
}),
]);
return { documentDetails, countryDetails, stateDetails, companyTypeDetails };
const adminEmail = config.MinglarAdminEmail;
return { documentDetails, countryDetails, stateDetails, companyTypeDetails, adminEmail };
}
async getAllFrequencies() {

View File

@@ -66,6 +66,8 @@ export const handler = safeHandler(async (
data: {
bucketCount: counts.bucketCount,
interestedCount: counts.interestedCount,
coverImage: counts.coverImage,
coverImagePresignedUrl: counts.coverImagePresignedUrl,
}
}),
};

View File

@@ -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 }),
};
});

View File

@@ -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,
}),
};
});

View File

@@ -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,
}),
};
});

View File

@@ -27,16 +27,16 @@ export const handler = safeHandler(async (
const longParam = event.queryStringParameters?.long ?? event.queryStringParameters?.lng ?? event.queryStringParameters?.longitude;
const radiusParam = event.queryStringParameters?.radiusKm ?? event.queryStringParameters?.radius;
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);

View File

@@ -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 {

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

View File

@@ -86,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,
},
}),
};
},

View File

@@ -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;

View File

@@ -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,
}),
};
});

View File

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

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

View File

@@ -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,
}),
};
});

View File

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

View 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;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff