Compare commits
50 Commits
Split-lamb
...
daff265584
| Author | SHA1 | Date | |
|---|---|---|---|
| daff265584 | |||
| 0f5061a129 | |||
| 5e87ab84d1 | |||
| 54a4f22d2f | |||
| bb87e0ac05 | |||
|
|
01569670b4 | ||
| 8fccc62f33 | |||
| fcdb813fb7 | |||
| 5239e04621 | |||
| fe0a2eb95b | |||
|
|
16d075b5f7 | ||
| 9df8c5443c | |||
| be0667d8e9 | |||
| a44321044f | |||
| fcac64e0a9 | |||
| 6ea2ebe5e1 | |||
| 388f3079a1 | |||
| 6a11c15f39 | |||
| ea461b6056 | |||
| 6703dc784d | |||
| e22c37bc65 | |||
| d32915c865 | |||
| 1c7ad52d0e | |||
| f1f1f199e8 | |||
| 1c32c18e03 | |||
| 0d3c71ab5a | |||
| 50f93bbeae | |||
| 3ce9d1d180 | |||
| cb819088a0 | |||
| 8f5f01c287 | |||
| e4a2a04045 | |||
| 50ce8e39c5 | |||
| 19e57f0e7f | |||
| ad5e343b66 | |||
| 8c3ece6ebd | |||
| 092f425bb3 | |||
| b1a3afd3a1 | |||
| 97f431260d | |||
| bf6d9ae00b | |||
| 518ec4eb21 | |||
| 95b061b400 | |||
| 92992797ab | |||
| c96e3b0c1a | |||
| f23b93801c | |||
| f1801a3210 | |||
| 2588ca4317 | |||
| e809ba4480 | |||
| 678be7c905 | |||
| 08b4231e5f | |||
| a3ab9db5a2 |
@@ -70,6 +70,7 @@
|
|||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pizzip": "^3.2.0",
|
"pizzip": "^3.2.0",
|
||||||
"prisma": "^7.0.1",
|
"prisma": "^7.0.1",
|
||||||
|
"razorpay": "^2.9.6",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"serverless": "4.24.0",
|
"serverless": "4.24.0",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ generator client {
|
|||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
binaryTargets = ["native", "rhel-openssl-3.0.x"] // Lambda Node 18/20 (Amazon Linux) target
|
binaryTargets = ["native", "rhel-openssl-3.0.x"] // Lambda Node 18/20 (Amazon Linux) target
|
||||||
previewFeatures = ["multiSchema"]
|
previewFeatures = ["multiSchema"]
|
||||||
engineType = "library"
|
engineType = "library"
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
@@ -12,30 +12,32 @@ datasource db {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
firstName String? @map("first_name") @db.VarChar(50)
|
firstName String? @map("first_name") @db.VarChar(50)
|
||||||
lastName String? @map("last_name") @db.VarChar(50)
|
lastName String? @map("last_name") @db.VarChar(50)
|
||||||
roleXid Int? @map("role_xid")
|
roleXid Int? @map("role_xid")
|
||||||
dateOfBirth DateTime? @map("date_of_birth")
|
dateOfBirth DateTime? @map("date_of_birth")
|
||||||
genderName String? @map("gender_name") @db.VarChar(20)
|
genderName String? @map("gender_name") @db.VarChar(20)
|
||||||
role Roles? @relation(fields: [roleXid], references: [id], onDelete: Restrict)
|
role Roles? @relation(fields: [roleXid], references: [id], onDelete: Restrict)
|
||||||
emailAddress String? @unique @map("email_address") @db.VarChar(150)
|
emailAddress String? @unique @map("email_address") @db.VarChar(150)
|
||||||
isdCode String? @map("isd_code") @db.VarChar(6) // +91, +1, +971 etc.
|
isdCode String? @map("isd_code") @db.VarChar(6) // +91, +1, +971 etc.
|
||||||
mobileNumber String? @unique @map("mobile_number") @db.VarChar(15) // international safe limit
|
mobileNumber String? @unique @map("mobile_number") @db.VarChar(15) // international safe limit
|
||||||
userPassword String? @map("user_password") @db.VarChar(255) // hashed passwords
|
userPassword String? @map("user_password") @db.VarChar(255) // hashed passwords
|
||||||
userPasscode String? @map("user_passcode") @db.VarChar(255) // 4–6 digit passcode
|
userPasscode String? @map("user_passcode") @db.VarChar(255) // 4–6 digit passcode
|
||||||
profileImage String? @map("profile_image") @db.VarChar(500) // S3 key or URL
|
profileImage String? @map("profile_image") @db.VarChar(500) // S3 key or URL
|
||||||
userLat String? @map("user_lat") @db.VarChar(20) // "-23.44444"
|
userLat String? @map("user_lat") @db.VarChar(20) // "-23.44444"
|
||||||
userLong String? @map("user_long") @db.VarChar(20)
|
userLong String? @map("user_long") @db.VarChar(20)
|
||||||
userStatus String? @default("pending") @map("user_status") @db.VarChar(20)
|
userStatus String? @default("pending") @map("user_status") @db.VarChar(20)
|
||||||
isEmailVerfied Boolean? @default(false) @map("is_email_verified")
|
isEmailVerfied Boolean? @default(false) @map("is_email_verified")
|
||||||
isMobileVerfied Boolean? @default(false) @map("is_mobile_verified")
|
isMobileVerfied Boolean? @default(false) @map("is_mobile_verified")
|
||||||
isProfileUpdated Boolean? @default(false) @map("is_profile_updated")
|
isProfileUpdated Boolean? @default(false) @map("is_profile_updated")
|
||||||
userRefNumber String? @unique @map("user_ref_number") @db.VarChar(20)
|
userRefNumber String? @unique @map("user_ref_number") @db.VarChar(20)
|
||||||
isActive Boolean? @default(true) @map("is_active")
|
dataConsentAccepted Boolean? @default(false) @map("data_consent_accepted")
|
||||||
createdAt DateTime? @default(now()) @map("created_at")
|
dataConsentAcceptedOn DateTime? @map("data_consent_accepted_on")
|
||||||
updatedAt DateTime? @updatedAt @map("updated_at")
|
isActive Boolean? @default(true) @map("is_active")
|
||||||
deletedAt DateTime? @map("deleted_at")
|
createdAt DateTime? @default(now()) @map("created_at")
|
||||||
|
updatedAt DateTime? @updatedAt @map("updated_at")
|
||||||
|
deletedAt DateTime? @map("deleted_at")
|
||||||
|
|
||||||
// Relations
|
// Relations
|
||||||
UserOtp UserOtp[]
|
UserOtp UserOtp[]
|
||||||
@@ -59,6 +61,7 @@ model User {
|
|||||||
ActivitySOSDetails ActivitySOSDetails[]
|
ActivitySOSDetails ActivitySOSDetails[]
|
||||||
ActivityFeedbacks ActivityFeedbacks[]
|
ActivityFeedbacks ActivityFeedbacks[]
|
||||||
ItineraryDetails ItineraryDetails[]
|
ItineraryDetails ItineraryDetails[]
|
||||||
|
paymentOrders PaymentOrders[]
|
||||||
inviteDetails InviteDetails[] @relation("InvitedUser")
|
inviteDetails InviteDetails[] @relation("InvitedUser")
|
||||||
invitedInviteDetails InviteDetails[] @relation("InviterUser")
|
invitedInviteDetails InviteDetails[] @relation("InviterUser")
|
||||||
userRevenues UserRevenue[]
|
userRevenues UserRevenue[]
|
||||||
@@ -874,7 +877,7 @@ model HostParent {
|
|||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
hostXid Int @map("host_xid")
|
hostXid Int @map("host_xid")
|
||||||
host HostHeader @relation(fields: [hostXid], references: [id], onDelete: Cascade)
|
host HostHeader @relation(fields: [hostXid], references: [id], onDelete: Cascade)
|
||||||
companyName String @map("company_name") @db.VarChar(100)
|
companyName String? @map("company_name") @db.VarChar(100)
|
||||||
address1 String? @map("address_1") @db.VarChar(150)
|
address1 String? @map("address_1") @db.VarChar(150)
|
||||||
address2 String? @map("address_2") @db.VarChar(150)
|
address2 String? @map("address_2") @db.VarChar(150)
|
||||||
cityXid Int? @map("city_xid")
|
cityXid Int? @map("city_xid")
|
||||||
@@ -1358,15 +1361,16 @@ model ActivityFoodCost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model ActivityFoodTypes {
|
model ActivityFoodTypes {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
activityXid Int @map("activity_xid")
|
activityXid Int @map("activity_xid")
|
||||||
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
|
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
|
||||||
foodTypeXid Int @map("food_type_xid")
|
foodTypeXid Int @map("food_type_xid")
|
||||||
foodType FoodTypes @relation(fields: [foodTypeXid], references: [id], onDelete: Restrict)
|
foodType FoodTypes @relation(fields: [foodTypeXid], references: [id], onDelete: Restrict)
|
||||||
isActive Boolean @default(true) @map("is_active")
|
isActive Boolean @default(true) @map("is_active")
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
deletedAt DateTime? @map("deleted_at")
|
deletedAt DateTime? @map("deleted_at")
|
||||||
|
itineraryActivitySelectionFoodTypes ItineraryActivitySelectionFoodType[]
|
||||||
|
|
||||||
@@map("activity_food_types")
|
@@map("activity_food_types")
|
||||||
@@schema("act")
|
@@schema("act")
|
||||||
@@ -1405,18 +1409,19 @@ model ActivityFoodTaxes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model ActivityEquipments {
|
model ActivityEquipments {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
activityXid Int @map("activity_xid")
|
activityXid Int @map("activity_xid")
|
||||||
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
|
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
|
||||||
equipmentName String @map("equipment_name") @db.VarChar(30)
|
equipmentName String @map("equipment_name") @db.VarChar(30)
|
||||||
isEquipmentChargeable Boolean @default(false) @map("is_equipment_chargeable")
|
isEquipmentChargeable Boolean @default(false) @map("is_equipment_chargeable")
|
||||||
equipmentBasePrice Int @map("equipment_base_price")
|
equipmentBasePrice Int @map("equipment_base_price")
|
||||||
equipmentTotalPrice Int @map("equipment_total_price")
|
equipmentTotalPrice Int @map("equipment_total_price")
|
||||||
isActive Boolean @default(true) @map("is_active")
|
isActive Boolean @default(true) @map("is_active")
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
deletedAt DateTime? @map("deleted_at")
|
deletedAt DateTime? @map("deleted_at")
|
||||||
ActivityEquipmentTaxes ActivityEquipmentTaxes[]
|
ActivityEquipmentTaxes ActivityEquipmentTaxes[]
|
||||||
|
itineraryActivitySelectionEquipments ItineraryActivitySelectionEquipment[]
|
||||||
|
|
||||||
@@map("activity_equipments")
|
@@map("activity_equipments")
|
||||||
@@schema("act")
|
@@schema("act")
|
||||||
@@ -1452,6 +1457,7 @@ model ActivityNavigationModes {
|
|||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
deletedAt DateTime? @map("deleted_at")
|
deletedAt DateTime? @map("deleted_at")
|
||||||
ActivityNavigationModesTaxes ActivityNavigationModesTaxes[]
|
ActivityNavigationModesTaxes ActivityNavigationModesTaxes[]
|
||||||
|
ItineraryActivitySelections ItineraryActivitySelection[]
|
||||||
|
|
||||||
@@map("activity_navigation_modes")
|
@@map("activity_navigation_modes")
|
||||||
@@schema("act")
|
@@schema("act")
|
||||||
@@ -1655,29 +1661,31 @@ model ItineraryHeader {
|
|||||||
ItineraryMembers ItineraryMembers[]
|
ItineraryMembers ItineraryMembers[]
|
||||||
ItineraryStartStopDetails ItineraryStartStopDetails[]
|
ItineraryStartStopDetails ItineraryStartStopDetails[]
|
||||||
ItineraryActivities ItineraryActivities[]
|
ItineraryActivities ItineraryActivities[]
|
||||||
|
paymentOrders PaymentOrders[]
|
||||||
|
|
||||||
@@map("itinerary_header")
|
@@map("itinerary_header")
|
||||||
@@schema("itn")
|
@@schema("itn")
|
||||||
}
|
}
|
||||||
|
|
||||||
model ItineraryMembers {
|
model ItineraryMembers {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
itineraryHeaderXid Int @map("itinerary_header_xid")
|
itineraryHeaderXid Int @map("itinerary_header_xid")
|
||||||
itineraryHeader ItineraryHeader @relation(fields: [itineraryHeaderXid], references: [id], onDelete: Cascade)
|
itineraryHeader ItineraryHeader @relation(fields: [itineraryHeaderXid], references: [id], onDelete: Cascade)
|
||||||
memberXid Int @map("member_xid")
|
memberXid Int @map("member_xid")
|
||||||
member User @relation("MemberUser", fields: [memberXid], references: [id], onDelete: Restrict)
|
member User @relation("MemberUser", fields: [memberXid], references: [id], onDelete: Restrict)
|
||||||
memberRole String @map("member_role") @db.VarChar(30)
|
memberRole String @map("member_role") @db.VarChar(30)
|
||||||
memberStatus String @default("pending") @map("member_status") @db.VarChar(30)
|
memberStatus String @default("pending") @map("member_status") @db.VarChar(30)
|
||||||
invitedByXid Int @map("invited_by_xid")
|
invitedByXid Int @map("invited_by_xid")
|
||||||
invitedBy User @relation("InvitedByUser", fields: [invitedByXid], references: [id], onDelete: Restrict)
|
invitedBy User @relation("InvitedByUser", fields: [invitedByXid], references: [id], onDelete: Restrict)
|
||||||
isActive Boolean @default(true) @map("is_active")
|
isActive Boolean @default(true) @map("is_active")
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
deletedAt DateTime? @map("deleted_at")
|
deletedAt DateTime? @map("deleted_at")
|
||||||
ItineraryStartStopDetails ItineraryStartStopDetails[]
|
ItineraryStartStopDetails ItineraryStartStopDetails[]
|
||||||
User User? @relation(fields: [userId], references: [id])
|
User User? @relation(fields: [userId], references: [id])
|
||||||
userId Int?
|
userId Int?
|
||||||
ItineraryDetails ItineraryDetails[]
|
ItineraryDetails ItineraryDetails[]
|
||||||
|
itineraryActivitySelections ItineraryActivitySelection[]
|
||||||
|
|
||||||
@@map("itinerary_members")
|
@@map("itinerary_members")
|
||||||
@@schema("itn")
|
@@schema("itn")
|
||||||
@@ -1708,41 +1716,124 @@ model ItineraryStartStopDetails {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model ItineraryActivities {
|
model ItineraryActivities {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
itineraryHeaderXid Int @map("itinerary_header_xid")
|
itineraryHeaderXid Int @map("itinerary_header_xid")
|
||||||
itineraryHeader ItineraryHeader @relation(fields: [itineraryHeaderXid], references: [id], onDelete: Cascade)
|
itineraryHeader ItineraryHeader @relation(fields: [itineraryHeaderXid], references: [id], onDelete: Cascade)
|
||||||
itineraryType String @map("itinerary_type") @db.VarChar(30)
|
displayOrder Int @default(0) @map("display_order")
|
||||||
activityXid Int @map("activity_xid")
|
itineraryType String @map("itinerary_type") @db.VarChar(30)
|
||||||
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Restrict)
|
activityXid Int? @map("activity_xid")
|
||||||
scheduledHeaderXid Int @map("scheduled_header_xid")
|
activity Activities? @relation(fields: [activityXid], references: [id], onDelete: Restrict)
|
||||||
scheduledHeader ScheduleHeader @relation(fields: [scheduledHeaderXid], references: [id], onDelete: Restrict)
|
scheduledHeaderXid Int? @map("scheduled_header_xid")
|
||||||
occurenceDate DateTime @map("occurence_date")
|
scheduledHeader ScheduleHeader? @relation(fields: [scheduledHeaderXid], references: [id], onDelete: Restrict)
|
||||||
startTime String @map("start_time") @db.VarChar(30)
|
occurenceDate DateTime @map("occurence_date")
|
||||||
endTime String @map("end_time") @db.VarChar(30)
|
startTime String @map("start_time") @db.VarChar(30)
|
||||||
endDate DateTime @map("end_date")
|
endTime String @map("end_time") @db.VarChar(30)
|
||||||
venueXid Int @map("venue_xid")
|
endDate DateTime @map("end_date")
|
||||||
venue ActivityVenues @relation(fields: [venueXid], references: [id], onDelete: Restrict)
|
venueXid Int? @map("venue_xid")
|
||||||
locationLat Float? @map("location_lat")
|
venue ActivityVenues? @relation(fields: [venueXid], references: [id], onDelete: Restrict)
|
||||||
locationLong Float? @map("location_long")
|
locationLat Float? @map("location_lat")
|
||||||
locationAddress Json? @map("location_address")
|
locationLong Float? @map("location_long")
|
||||||
travelMode String? @map("travel_mode") @db.VarChar(30)
|
locationAddress Json? @map("location_address")
|
||||||
kmForNextPoint Float? @map("km_for_next_point")
|
travelMode String? @map("travel_mode") @db.VarChar(30)
|
||||||
timeForNextPointMins Int? @map("time_for_next_point_mins")
|
kmForNextPoint Float? @map("km_for_next_point")
|
||||||
paxCount Int @map("pax_count")
|
timeForNextPointMins Int? @map("time_for_next_point_mins")
|
||||||
totalAmount Int @map("total_amount")
|
paxCount Int? @map("pax_count")
|
||||||
bookingStatus String @default("pending") @map("booking_status") @db.VarChar(30)
|
totalAmount Int? @map("total_amount")
|
||||||
isActive Boolean @default(true) @map("is_active")
|
bookingStatus String @default("pending") @map("booking_status") @db.VarChar(30)
|
||||||
createdAt DateTime @default(now()) @map("created_at")
|
isActive Boolean @default(true) @map("is_active")
|
||||||
updatedAt DateTime @updatedAt @map("updated_at")
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
deletedAt DateTime? @map("deleted_at")
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
ActivitySOSDetails ActivitySOSDetails[]
|
deletedAt DateTime? @map("deleted_at")
|
||||||
ActivityFeedbacks ActivityFeedbacks[]
|
ActivitySOSDetails ActivitySOSDetails[]
|
||||||
ItineraryDetails ItineraryDetails[]
|
ActivityFeedbacks ActivityFeedbacks[]
|
||||||
|
ItineraryDetails ItineraryDetails[]
|
||||||
|
itineraryActivitySelections ItineraryActivitySelection[]
|
||||||
|
|
||||||
@@map("itinerary_activities")
|
@@map("itinerary_activities")
|
||||||
@@schema("itn")
|
@@schema("itn")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model PaymentOrders {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
userXid Int @map("user_xid")
|
||||||
|
user User @relation(fields: [userXid], references: [id], onDelete: Cascade)
|
||||||
|
itineraryHeaderXid Int? @map("itinerary_header_xid")
|
||||||
|
itineraryHeader ItineraryHeader? @relation(fields: [itineraryHeaderXid], references: [id], onDelete: Cascade)
|
||||||
|
razorpayOrderId String? @unique @map("razorpay_order_id") @db.VarChar(100)
|
||||||
|
razorpayPaymentId String? @unique @map("razorpay_payment_id") @db.VarChar(100)
|
||||||
|
razorpaySignature String? @map("razorpay_signature") @db.VarChar(255)
|
||||||
|
receipt String @unique @map("receipt") @db.VarChar(100)
|
||||||
|
amount Int @map("amount")
|
||||||
|
currency String @default("INR") @map("currency") @db.VarChar(10)
|
||||||
|
paymentStatus String @default("created") @map("payment_status") @db.VarChar(30)
|
||||||
|
notes Json? @map("notes")
|
||||||
|
verifiedAt DateTime? @map("verified_at")
|
||||||
|
paidAt DateTime? @map("paid_at")
|
||||||
|
isActive Boolean @default(true) @map("is_active")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
deletedAt DateTime? @map("deleted_at")
|
||||||
|
|
||||||
|
@@index([userXid, itineraryHeaderXid])
|
||||||
|
@@map("payment_orders")
|
||||||
|
@@schema("itn")
|
||||||
|
}
|
||||||
|
|
||||||
|
model ItineraryActivitySelection {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
itineraryActivityXid Int @map("itinerary_activity_xid")
|
||||||
|
itineraryActivity ItineraryActivities @relation(fields: [itineraryActivityXid], references: [id], onDelete: Cascade)
|
||||||
|
itineraryMemberXid Int @map("itinerary_member_xid")
|
||||||
|
itineraryMember ItineraryMembers @relation(fields: [itineraryMemberXid], references: [id], onDelete: Cascade)
|
||||||
|
isFoodOpted Boolean @default(false) @map("is_food_opted")
|
||||||
|
isTrainerOpted Boolean @default(false) @map("is_trainer_opted")
|
||||||
|
isInActivityNavigationOpted Boolean @default(false) @map("is_in_activity_navigation_opted")
|
||||||
|
activityNavigationModeXid Int? @map("activity_navigation_mode_xid")
|
||||||
|
activityNavigationMode ActivityNavigationModes? @relation(fields: [activityNavigationModeXid], references: [id], onDelete: Restrict)
|
||||||
|
isActive Boolean @default(true) @map("is_active")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
deletedAt DateTime? @map("deleted_at")
|
||||||
|
selectedFoodTypes ItineraryActivitySelectionFoodType[]
|
||||||
|
selectedEquipments ItineraryActivitySelectionEquipment[]
|
||||||
|
|
||||||
|
@@unique([itineraryActivityXid, itineraryMemberXid])
|
||||||
|
@@map("itinerary_activity_selection")
|
||||||
|
@@schema("itn")
|
||||||
|
}
|
||||||
|
|
||||||
|
model ItineraryActivitySelectionFoodType {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
itineraryActivitySelectionXid Int @map("itinerary_activity_selection_xid")
|
||||||
|
itineraryActivitySelection ItineraryActivitySelection @relation(fields: [itineraryActivitySelectionXid], references: [id], onDelete: Cascade)
|
||||||
|
activityFoodTypeXid Int @map("activity_food_type_xid")
|
||||||
|
activityFoodType ActivityFoodTypes @relation(fields: [activityFoodTypeXid], references: [id], onDelete: Cascade)
|
||||||
|
isActive Boolean @default(true) @map("is_active")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
deletedAt DateTime? @map("deleted_at")
|
||||||
|
|
||||||
|
@@unique([itineraryActivitySelectionXid, activityFoodTypeXid])
|
||||||
|
@@map("itinerary_activity_selection_food_type")
|
||||||
|
@@schema("itn")
|
||||||
|
}
|
||||||
|
|
||||||
|
model ItineraryActivitySelectionEquipment {
|
||||||
|
id Int @id @default(autoincrement())
|
||||||
|
itineraryActivitySelectionXid Int @map("itinerary_activity_selection_xid")
|
||||||
|
itineraryActivitySelection ItineraryActivitySelection @relation(fields: [itineraryActivitySelectionXid], references: [id], onDelete: Cascade)
|
||||||
|
activityEquipmentXid Int @map("activity_equipment_xid")
|
||||||
|
activityEquipment ActivityEquipments @relation(fields: [activityEquipmentXid], references: [id], onDelete: Cascade)
|
||||||
|
isActive Boolean @default(true) @map("is_active")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
deletedAt DateTime? @map("deleted_at")
|
||||||
|
|
||||||
|
@@unique([itineraryActivitySelectionXid, activityEquipmentXid])
|
||||||
|
@@map("itinerary_activity_selection_equipment")
|
||||||
|
@@schema("itn")
|
||||||
|
}
|
||||||
|
|
||||||
model ActivitySOSDetails {
|
model ActivitySOSDetails {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
itineraryActivityXid Int @map("itinerary_activity_xid")
|
itineraryActivityXid Int @map("itinerary_activity_xid")
|
||||||
@@ -1795,8 +1886,8 @@ model ItineraryDetails {
|
|||||||
activityStatus String @map("activity_status") @db.VarChar(30)
|
activityStatus String @map("activity_status") @db.VarChar(30)
|
||||||
isChargeable Boolean @default(false) @map("is_chargeable")
|
isChargeable Boolean @default(false) @map("is_chargeable")
|
||||||
baseAmount Int @map("base_amount")
|
baseAmount Int @map("base_amount")
|
||||||
totalAmount Int @map("total_amount")
|
totalAmount Int? @map("total_amount")
|
||||||
itineraryStatus String @map("itinerary_status") @db.VarChar(30)
|
itineraryStatus String? @map("itinerary_status") @db.VarChar(30)
|
||||||
isPaid Boolean @default(false) @map("is_paid")
|
isPaid Boolean @default(false) @map("is_paid")
|
||||||
paidByXid Int? @map("paid_by_xid")
|
paidByXid Int? @map("paid_by_xid")
|
||||||
paidBy User? @relation(fields: [paidByXid], references: [id], onDelete: Restrict)
|
paidBy User? @relation(fields: [paidByXid], references: [id], onDelete: Restrict)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Legacy monolith config. For new deployments use serverless.*.yml files.
|
# Legacy monolith config. For new deployments use serverless.*.yml files.
|
||||||
service: minglarDev
|
service: minglar
|
||||||
|
|
||||||
|
|
||||||
useDotenv: true
|
useDotenv: true
|
||||||
@@ -65,6 +65,9 @@ provider:
|
|||||||
AM_INVITATION_LINK: ${env:AM_INVITATION_LINK}
|
AM_INVITATION_LINK: ${env:AM_INVITATION_LINK}
|
||||||
HOST_LINK: ${env:HOST_LINK}
|
HOST_LINK: ${env:HOST_LINK}
|
||||||
HOST_LINK_PQ: ${env:HOST_LINK_PQ}
|
HOST_LINK_PQ: ${env:HOST_LINK_PQ}
|
||||||
|
RAZORPAY_KEY_ID: ${env:RAZORPAY_KEY_ID}
|
||||||
|
RAZORPAY_KEY_SECRET: ${env:RAZORPAY_KEY_SECRET}
|
||||||
|
RAZORPAY_WEBHOOK_SECRET: ${env:RAZORPAY_WEBHOOK_SECRET}
|
||||||
|
|
||||||
iam:
|
iam:
|
||||||
role:
|
role:
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ provider:
|
|||||||
AM_INVITATION_LINK: ${env:AM_INVITATION_LINK}
|
AM_INVITATION_LINK: ${env:AM_INVITATION_LINK}
|
||||||
HOST_LINK: ${env:HOST_LINK}
|
HOST_LINK: ${env:HOST_LINK}
|
||||||
HOST_LINK_PQ: ${env:HOST_LINK_PQ}
|
HOST_LINK_PQ: ${env:HOST_LINK_PQ}
|
||||||
|
RAZORPAY_KEY_ID: ${env:RAZORPAY_KEY_ID}
|
||||||
|
RAZORPAY_KEY_SECRET: ${env:RAZORPAY_KEY_SECRET}
|
||||||
|
RAZORPAY_WEBHOOK_SECRET: ${env:RAZORPAY_WEBHOOK_SECRET}
|
||||||
|
|
||||||
iam:
|
iam:
|
||||||
role:
|
role:
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ minglarRegistration:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/registration
|
path: /registration
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
minglarLoginForAdmin:
|
minglarLoginForAdmin:
|
||||||
@@ -28,7 +28,7 @@ minglarLoginForAdmin:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/login
|
path: /login
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
minglarCreatePassword:
|
minglarCreatePassword:
|
||||||
@@ -43,7 +43,7 @@ minglarCreatePassword:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/create-password
|
path: /create-password
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
updateMinglarProfile:
|
updateMinglarProfile:
|
||||||
@@ -60,7 +60,7 @@ updateMinglarProfile:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/update-profile
|
path: /update-profile
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
prepopulateRole:
|
prepopulateRole:
|
||||||
@@ -75,7 +75,7 @@ prepopulateRole:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/prepopulate-Roles
|
path: /prepopulate-Roles
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getHostDetailsById:
|
getHostDetailsById:
|
||||||
@@ -90,7 +90,7 @@ getHostDetailsById:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/hosts/get-host-details/{host_xid}
|
path: /hosthub/hosts/get-host-details/{host_xid}
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
inviteTeammate:
|
inviteTeammate:
|
||||||
@@ -105,7 +105,7 @@ inviteTeammate:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/settings/teammates/invite-teammate
|
path: /settings/teammates/invite-teammate
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
getAllHostApplication:
|
getAllHostApplication:
|
||||||
@@ -121,7 +121,7 @@ getAllHostApplication:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/hosts/get-all-host-applications-am
|
path: /hosthub/hosts/get-all-host-applications-am
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getAllHostActivityForAdmin:
|
getAllHostActivityForAdmin:
|
||||||
@@ -137,7 +137,7 @@ getAllHostActivityForAdmin:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/get-all-activity-of-host/{id}
|
path: /get-all-activity-of-host/{id}
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getAllOnboardingHostApplications:
|
getAllOnboardingHostApplications:
|
||||||
@@ -153,7 +153,7 @@ getAllOnboardingHostApplications:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/onboarding/get-all-host-applications-admin
|
path: /hosthub/onboarding/get-all-host-applications-admin
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getAllOnboardingHostApplications_New:
|
getAllOnboardingHostApplications_New:
|
||||||
@@ -169,7 +169,7 @@ getAllOnboardingHostApplications_New:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/onboarding/get-all-host-applications-admin-new
|
path: /hosthub/onboarding/get-all-host-applications-admin-new
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getAllInvitationDetails:
|
getAllInvitationDetails:
|
||||||
@@ -184,7 +184,7 @@ getAllInvitationDetails:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/settings/teammates/get-all-invitation-details
|
path: /settings/teammates/get-all-invitation-details
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
addSuggestion:
|
addSuggestion:
|
||||||
@@ -200,7 +200,7 @@ addSuggestion:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/hosts/add-suggestion
|
path: /hosthub/hosts/add-suggestion
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
getAllCoadminAndAMDetails:
|
getAllCoadminAndAMDetails:
|
||||||
@@ -215,7 +215,7 @@ getAllCoadminAndAMDetails:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/settings/teammates/get-all-coadmin-am
|
path: /settings/teammates/get-all-coadmin-am
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getAllInvitedCoadminAndAMDetails:
|
getAllInvitedCoadminAndAMDetails:
|
||||||
@@ -230,7 +230,7 @@ getAllInvitedCoadminAndAMDetails:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/settings/teammates/get-all-invited-coadmin-am
|
path: /settings/teammates/get-all-invited-coadmin-am
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getAmDetailsbyId:
|
getAmDetailsbyId:
|
||||||
@@ -245,7 +245,7 @@ getAmDetailsbyId:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/settings/teammates/get-am-details-by-id/{amXid}
|
path: /settings/teammates/get-am-details-by-id/{amXid}
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
assignAMToHost:
|
assignAMToHost:
|
||||||
@@ -261,7 +261,7 @@ assignAMToHost:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/onboarding/assign-am
|
path: /hosthub/onboarding/assign-am
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
editAgreementDetailsAndAccept:
|
editAgreementDetailsAndAccept:
|
||||||
@@ -277,7 +277,7 @@ editAgreementDetailsAndAccept:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/onboarding/edit-agreement-accept-host
|
path: /hosthub/onboarding/edit-agreement-accept-host
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
getAllPqqQuesAnsForAM:
|
getAllPqqQuesAnsForAM:
|
||||||
@@ -292,7 +292,7 @@ getAllPqqQuesAnsForAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/onboarding/get-all-pqq-ques-ans-for-am
|
path: /hosthub/onboarding/get-all-pqq-ques-ans-for-am
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
acceptHostApplication:
|
acceptHostApplication:
|
||||||
@@ -308,7 +308,7 @@ acceptHostApplication:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/hosts/accept-host-application
|
path: /hosthub/hosts/accept-host-application
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
RejectPQQByAM:
|
RejectPQQByAM:
|
||||||
@@ -324,7 +324,7 @@ RejectPQQByAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/hosts/reject-pq-by-am
|
path: /hosthub/hosts/reject-pq-by-am
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
rejectActivityDetailsApplicationByAM:
|
rejectActivityDetailsApplicationByAM:
|
||||||
@@ -340,7 +340,7 @@ rejectActivityDetailsApplicationByAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/hosts/reject-activity-application-by-am
|
path: /hosthub/hosts/reject-activity-application-by-am
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
acceptPQByAM:
|
acceptPQByAM:
|
||||||
@@ -356,7 +356,7 @@ acceptPQByAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/hosts/accept-pq-by-am
|
path: /hosthub/hosts/accept-pq-by-am
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
acceptActivityDetailsApplicationByAM:
|
acceptActivityDetailsApplicationByAM:
|
||||||
@@ -372,7 +372,7 @@ acceptActivityDetailsApplicationByAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/hosts/accept-activity-application-by-am
|
path: /hosthub/hosts/accept-activity-application-by-am
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
rejectHostApplication:
|
rejectHostApplication:
|
||||||
@@ -388,7 +388,7 @@ rejectHostApplication:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/onboarding/reject-host-application
|
path: /hosthub/onboarding/reject-host-application
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
rejectHostApplicationAM:
|
rejectHostApplicationAM:
|
||||||
@@ -404,7 +404,7 @@ rejectHostApplicationAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/hosts/reject-host-application-am
|
path: /hosthub/hosts/reject-host-application-am
|
||||||
method: patch
|
method: patch
|
||||||
|
|
||||||
addPQQSuggestion:
|
addPQQSuggestion:
|
||||||
@@ -420,7 +420,7 @@ addPQQSuggestion:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/hosts/add-Pqq-suggestion
|
path: /hosthub/hosts/add-Pqq-suggestion
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
addActivitySuggestion:
|
addActivitySuggestion:
|
||||||
@@ -436,7 +436,7 @@ addActivitySuggestion:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/hosts/add-Activity-suggestion
|
path: /hosthub/hosts/add-Activity-suggestion
|
||||||
method: post
|
method: post
|
||||||
|
|
||||||
getAllPQPDetailsForAM:
|
getAllPQPDetailsForAM:
|
||||||
@@ -452,7 +452,7 @@ getAllPQPDetailsForAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/pqp/pqp-details-for-am/{activityXid}
|
path: /hosthub/pqp/pqp-details-for-am/{activityXid}
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
getSuggestionsForAM:
|
getSuggestionsForAM:
|
||||||
@@ -468,5 +468,5 @@ getSuggestionsForAM:
|
|||||||
- ${file(./serverless/patterns/base.yml):pattern4}
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /minglaradmin/hosthub/onboarding/show-suggestion-to-am/{hostXid}
|
path: /hosthub/onboarding/show-suggestion-to-am/{hostXid}
|
||||||
method: get
|
method: get
|
||||||
|
|||||||
@@ -288,6 +288,21 @@ getActivityFromConnectionsInterest:
|
|||||||
path: /connections/get-activity-from-connections-interest
|
path: /connections/get-activity-from-connections-interest
|
||||||
method: get
|
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:
|
viewMoreActivitiesByInterest:
|
||||||
handler: src/modules/user/handlers/activities/viewMoreActivities.handler
|
handler: src/modules/user/handlers/activities/viewMoreActivities.handler
|
||||||
memorySize: 384
|
memorySize: 384
|
||||||
@@ -406,4 +421,124 @@ getAllBucketActivities:
|
|||||||
events:
|
events:
|
||||||
- httpApi:
|
- httpApi:
|
||||||
path: /activities/get-all-bucket-activities
|
path: /activities/get-all-bucket-activities
|
||||||
method: get
|
method: get
|
||||||
|
|
||||||
|
getUserItineraryDetails:
|
||||||
|
handler: src/modules/user/handlers/itinerary/getUserItineraryDetails.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/user/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /itinerary/get-user-itinerary-details
|
||||||
|
method: get
|
||||||
|
|
||||||
|
saveUserItinerary:
|
||||||
|
handler: src/modules/user/handlers/itinerary/saveUserItinerary.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/user/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /itinerary/save-user-itinerary
|
||||||
|
method: post
|
||||||
|
|
||||||
|
saveItineraryActivitySelections:
|
||||||
|
handler: src/modules/user/handlers/itinerary/saveItineraryActivitySelections.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/user/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /itinerary/save-itinerary-activity-selections
|
||||||
|
method: post
|
||||||
|
|
||||||
|
getAllUserSavedItineraries:
|
||||||
|
handler: src/modules/user/handlers/itinerary/getAllUserSavedItineraries.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/user/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /itinerary/get-all-user-saved-itineraries
|
||||||
|
method: get
|
||||||
|
|
||||||
|
createRazorpayOrder:
|
||||||
|
handler: src/modules/user/handlers/payment/createOrder.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/user/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /payment/create-order
|
||||||
|
method: post
|
||||||
|
|
||||||
|
verifyRazorpayPayment:
|
||||||
|
handler: src/modules/user/handlers/payment/verifyPayment.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/user/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /payment/verify-payment
|
||||||
|
method: post
|
||||||
|
|
||||||
|
razorpayWebhook:
|
||||||
|
handler: src/modules/user/handlers/payment/razorpayWebhook.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/user/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /payment/webhook/razorpay
|
||||||
|
method: post
|
||||||
|
|
||||||
|
getMatchingBucketInterestedActivities:
|
||||||
|
handler: src/modules/user/handlers/itinerary/getMatchingBucketInterestedActivities.handler
|
||||||
|
memorySize: 512
|
||||||
|
package:
|
||||||
|
patterns:
|
||||||
|
- 'src/modules/user/**'
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern1}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern2}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern3}
|
||||||
|
- ${file(./serverless/patterns/base.yml):pattern4}
|
||||||
|
events:
|
||||||
|
- httpApi:
|
||||||
|
path: /itinerary/get-matching-bucket-interested-activities
|
||||||
|
method: post
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export const ACTIVITY_DISPLAY_STATUS = {
|
|||||||
PQ_IN_REVIEW: 'PQ In Review',
|
PQ_IN_REVIEW: 'PQ In Review',
|
||||||
PQ_APPROVED: 'PQ Approved',
|
PQ_APPROVED: 'PQ Approved',
|
||||||
|
|
||||||
ACTIVITY_DRAFT: 'Draft - Activity',
|
ACTIVITY_DRAFT: 'Draft',
|
||||||
ACTIVITY_IN_REVIEW: 'In Review',
|
ACTIVITY_IN_REVIEW: 'In Review',
|
||||||
ACTIVITY_TO_REVIEW: 'Re-submitted',
|
ACTIVITY_TO_REVIEW: 'Re-submitted',
|
||||||
NOT_LISTED: 'Not Listed',
|
NOT_LISTED: 'Not Listed',
|
||||||
@@ -94,7 +94,7 @@ export const ACTIVITY_AM_DISPLAY_STATUS = {
|
|||||||
PQ_APPROVED: 'PQ Approved',
|
PQ_APPROVED: 'PQ Approved',
|
||||||
REVISED: 'Revised',
|
REVISED: 'Revised',
|
||||||
|
|
||||||
ACTIVITY_DRAFT: 'Draft - Activity',
|
ACTIVITY_DRAFT: 'Draft',
|
||||||
ACTIVITY_NEW: 'New',
|
ACTIVITY_NEW: 'New',
|
||||||
ACTIVITY_TO_REVIEW: 'Activity To Review',
|
ACTIVITY_TO_REVIEW: 'Activity To Review',
|
||||||
ACTIVITY_ENHANCING: 'Enhancing',
|
ACTIVITY_ENHANCING: 'Enhancing',
|
||||||
|
|||||||
@@ -84,7 +84,10 @@ const envVarsSchema = yup
|
|||||||
// Email links
|
// Email links
|
||||||
AM_INVITATION_LINK: yup.string().required('Link to send in AM invitation mail is required'),
|
AM_INVITATION_LINK: yup.string().required('Link to send in AM invitation mail is required'),
|
||||||
HOST_LINK: yup.string().required('Link to host panel is required'),
|
HOST_LINK: yup.string().required('Link to host panel is required'),
|
||||||
HOST_LINK_PQ: yup.string().required('Link to host panel pqp is required')
|
HOST_LINK_PQ: yup.string().required('Link to host panel pqp is required'),
|
||||||
|
RAZORPAY_KEY_SECRET: yup.string().required('Razorpay key secret is required'),
|
||||||
|
RAZORPAY_KEY_ID: yup.string().required('Razorpay key id is required'),
|
||||||
|
RAZORPAY_WEBHOOK_SECRET: yup.string().required('Razorpay webhook secret is required'),
|
||||||
})
|
})
|
||||||
.noUnknown(true);
|
.noUnknown(true);
|
||||||
|
|
||||||
@@ -165,6 +168,10 @@ function getConfig() {
|
|||||||
AM_INVITATION_LINK: envVars.AM_INVITATION_LINK,
|
AM_INVITATION_LINK: envVars.AM_INVITATION_LINK,
|
||||||
HOST_LINK: envVars.HOST_LINK,
|
HOST_LINK: envVars.HOST_LINK,
|
||||||
HOST_LINK_PQ: envVars.HOST_LINK_PQ,
|
HOST_LINK_PQ: envVars.HOST_LINK_PQ,
|
||||||
|
RAZORPAY_KEY_ID: envVars.RAZORPAY_KEY_ID,
|
||||||
|
RAZORPAY_KEY_SECRET: envVars.RAZORPAY_KEY_SECRET,
|
||||||
|
RAZORPAY_WEBHOOK_SECRET: envVars.RAZORPAY_WEBHOOK_SECRET,
|
||||||
|
|
||||||
// oneSignal: {
|
// oneSignal: {
|
||||||
// appID: envVars.ONESIGNAL_APPID,
|
// appID: envVars.ONESIGNAL_APPID,
|
||||||
// restApiKey: envVars.ONESIGNAL_REST_APIKEY,
|
// restApiKey: envVars.ONESIGNAL_REST_APIKEY,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHo
|
|||||||
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
import ApiError from '../../../../../common/utils/helper/ApiError';
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
import { HostService } from '../../../services/host.service';
|
import { HostService } from '../../../services/host.service';
|
||||||
|
import { sendPQPEmailToAM } from '../../../services/sendHostResubmitEmailToAM.service';
|
||||||
|
|
||||||
const hostService = new HostService(prismaClient);
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
@@ -177,6 +178,15 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
|
|
||||||
const getAllUpdatedQuestionResponse = await hostService.getAllPQUpdatedResponse(activityXid)
|
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
|
// CASE 2 — NO deletion & NO new files => DO NOTHING to existing files
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -13,6 +13,40 @@ const hostService = new HostService(prismaClient);
|
|||||||
|
|
||||||
const s3 = new AWS.S3({ region: config.aws.region });
|
const s3 = new AWS.S3({ region: config.aws.region });
|
||||||
|
|
||||||
|
function parseMultipartFieldValue(val: string) {
|
||||||
|
if (val === '' || val === 'null' || val === 'undefined') return null;
|
||||||
|
|
||||||
|
const cleaned = val.trim();
|
||||||
|
const looksLikeJson =
|
||||||
|
(cleaned.startsWith('{') && cleaned.endsWith('}')) ||
|
||||||
|
(cleaned.startsWith('[') && cleaned.endsWith(']')) ||
|
||||||
|
(cleaned.startsWith('"') && cleaned.endsWith('"'));
|
||||||
|
|
||||||
|
if (!looksLikeJson) return val;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(cleaned);
|
||||||
|
} catch {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeComments(comments: unknown): string | null {
|
||||||
|
if (comments === null || comments === undefined || comments === '') return null;
|
||||||
|
|
||||||
|
const value = String(comments).trim();
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
|
if (
|
||||||
|
(value.startsWith('"') && value.endsWith('"')) ||
|
||||||
|
(value.startsWith("'") && value.endsWith("'"))
|
||||||
|
) {
|
||||||
|
return value.slice(1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
// Function to extract S3 key from URL
|
// Function to extract S3 key from URL
|
||||||
function getS3KeyFromUrl(url: string): string {
|
function getS3KeyFromUrl(url: string): string {
|
||||||
const bucketBaseUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
|
const bucketBaseUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
|
||||||
@@ -122,22 +156,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
|
|
||||||
bb.on("field", (fieldname, val) => {
|
bb.on("field", (fieldname, val) => {
|
||||||
console.log(`FIELD RAW: ${fieldname} =`, val);
|
console.log(`FIELD RAW: ${fieldname} =`, val);
|
||||||
if (val === '' || val === 'null' || val === 'undefined') fields[fieldname] = null;
|
fields[fieldname] = parseMultipartFieldValue(val);
|
||||||
else {
|
|
||||||
try {
|
|
||||||
const cleaned = val.trim();
|
|
||||||
|
|
||||||
// If it starts and ends with quotes, remove them
|
|
||||||
const withoutQuotes =
|
|
||||||
(cleaned.startsWith('"') && cleaned.endsWith('"'))
|
|
||||||
? cleaned.slice(1, -1)
|
|
||||||
: cleaned;
|
|
||||||
|
|
||||||
fields[fieldname] = JSON.parse(withoutQuotes);
|
|
||||||
} catch {
|
|
||||||
fields[fieldname] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
bb.on("close", () => resolve());
|
bb.on("close", () => resolve());
|
||||||
@@ -154,7 +173,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
const activityXid = Number(fields.activityXid);
|
const activityXid = Number(fields.activityXid);
|
||||||
const pqqQuestionXid = Number(fields.pqqQuestionXid);
|
const pqqQuestionXid = Number(fields.pqqQuestionXid);
|
||||||
const pqqAnswerXid = Number(fields.pqqAnswerXid);
|
const pqqAnswerXid = Number(fields.pqqAnswerXid);
|
||||||
const comments = fields.comments || null;
|
const comments = normalizeComments(fields.comments);
|
||||||
|
|
||||||
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Please provide a valid activity");
|
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Please provide a valid activity");
|
||||||
if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Please select a valid question");
|
if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Please select a valid question");
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { prismaClient } from '../../../../../common/database/prisma.lambda.servi
|
|||||||
import { HostService } from '../../../services/host.service';
|
import { HostService } from '../../../services/host.service';
|
||||||
import ApiError from '../../../../../common/utils/helper/ApiError';
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
|
||||||
|
import { sendWelcomeEmailToHost } from '../../../services/sendOTPEmail.service';
|
||||||
|
|
||||||
const hostService = new HostService(prismaClient);
|
const hostService = new HostService(prismaClient);
|
||||||
|
|
||||||
@@ -46,7 +47,8 @@ export const handler = safeHandler(async (
|
|||||||
throw new ApiError(400, 'Password must be at least 8 characters long');
|
throw new ApiError(400, 'Password must be at least 8 characters long');
|
||||||
}
|
}
|
||||||
|
|
||||||
await hostService.createPassword(user_xid, password);
|
const result = await hostService.createPassword(user_xid, password);
|
||||||
|
await sendWelcomeEmailToHost(result.emailAddress);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
|||||||
@@ -4,13 +4,9 @@ import { prismaClient } from '../../../../../common/database/prisma.lambda.servi
|
|||||||
import { ROLE } from '../../../../../common/utils/constants/common.constant';
|
import { ROLE } from '../../../../../common/utils/constants/common.constant';
|
||||||
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
|
||||||
import ApiError from '../../../../../common/utils/helper/ApiError';
|
import ApiError from '../../../../../common/utils/helper/ApiError';
|
||||||
import { encryptUserId } from '../../../../../common/utils/helper/CodeGenerator';
|
|
||||||
import { OtpGeneratorSixDigit } from '../../../../../common/utils/helper/OtpGenerator';
|
import { OtpGeneratorSixDigit } from '../../../../../common/utils/helper/OtpGenerator';
|
||||||
import { HostService } from '../../../services/host.service';
|
|
||||||
import { sendOtpEmailForHost } from '@/modules/host/services/sendOTPEmail.service';
|
import { sendOtpEmailForHost } from '@/modules/host/services/sendOTPEmail.service';
|
||||||
|
|
||||||
const hostService = new HostService(prismaClient);
|
|
||||||
|
|
||||||
export async function generateHostRefNumber(tx: any) {
|
export async function generateHostRefNumber(tx: any) {
|
||||||
const lastrecord = await tx.user.findFirst({
|
const lastrecord = await tx.user.findFirst({
|
||||||
orderBy: {
|
orderBy: {
|
||||||
@@ -45,13 +41,23 @@ export const handler = safeHandler(async (
|
|||||||
throw new ApiError(400, 'Email is required');
|
throw new ApiError(400, 'Email is required');
|
||||||
}
|
}
|
||||||
|
|
||||||
const emailToLowerCase = email.toLowerCase()
|
const emailToLowerCase = email.trim().toLowerCase();
|
||||||
|
|
||||||
|
if (!emailToLowerCase) {
|
||||||
|
throw new ApiError(400, 'Email is required');
|
||||||
|
}
|
||||||
|
|
||||||
// Use a single transaction for user creation/lookup and OTP storage
|
// Use a single transaction for user creation/lookup and OTP storage
|
||||||
const transactionResult = await prismaClient.$transaction(async (tx) => {
|
const transactionResult = await prismaClient.$transaction(async (tx) => {
|
||||||
const user = await tx.user.findUnique({
|
const user = await tx.user.findUnique({
|
||||||
where: { emailAddress: emailToLowerCase },
|
where: { emailAddress: emailToLowerCase },
|
||||||
select: { emailAddress: true, id: true, userPassword: true },
|
select: {
|
||||||
|
emailAddress: true,
|
||||||
|
id: true,
|
||||||
|
userPassword: true,
|
||||||
|
dataConsentAccepted: true,
|
||||||
|
dataConsentAcceptedOn: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user && user.userPassword) {
|
if (user && user.userPassword) {
|
||||||
@@ -93,9 +99,18 @@ export const handler = safeHandler(async (
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const encryptedId = encryptUserId(String(newUserLocal.id));
|
await tx.user.update({
|
||||||
|
where: { id: Number(newUserLocal.id) },
|
||||||
|
data: {
|
||||||
|
dataConsentAccepted: true,
|
||||||
|
dataConsentAcceptedOn:
|
||||||
|
user?.dataConsentAccepted && user?.dataConsentAcceptedOn
|
||||||
|
? user.dataConsentAcceptedOn
|
||||||
|
: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return { newUser: newUserLocal, otp, encryptedId };
|
return { newUser: newUserLocal, otp };
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!transactionResult || !transactionResult.otp) {
|
if (!transactionResult || !transactionResult.otp) {
|
||||||
|
|||||||
@@ -142,6 +142,10 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
|
|
||||||
const deletedFiles = normalizeJsonField(fields, "deletedFiles") || [];
|
const deletedFiles = normalizeJsonField(fields, "deletedFiles") || [];
|
||||||
const parentDeletedFiles = normalizeJsonField(fields, "parentDeletedFiles") || [];
|
const parentDeletedFiles = normalizeJsonField(fields, "parentDeletedFiles") || [];
|
||||||
|
const deleteCompanyLogo =
|
||||||
|
fields.deleteCompanyLogo === 'true' || fields.deleteCompanyLogo === true;
|
||||||
|
const deleteParentCompanyLogo =
|
||||||
|
fields.deleteParentCompanyLogo === 'true' || fields.deleteParentCompanyLogo === true;
|
||||||
|
|
||||||
/** 4) Extract and clean isDraft flag */
|
/** 4) Extract and clean isDraft flag */
|
||||||
const isDraft = fields.isDraft === 'true' || fields.isDraft === true;
|
const isDraft = fields.isDraft === 'true' || fields.isDraft === true;
|
||||||
@@ -172,14 +176,46 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
if (fields.userProfile) {
|
if (fields.userProfile) {
|
||||||
const userProfileRaw = normalizeJsonField(fields, 'userProfile');
|
const userProfileRaw = normalizeJsonField(fields, 'userProfile');
|
||||||
if (userProfileRaw) {
|
if (userProfileRaw) {
|
||||||
const { firstName, lastName, mobileNumber } = userProfileRaw;
|
const firstName =
|
||||||
|
typeof userProfileRaw.firstName === 'string'
|
||||||
|
? userProfileRaw.firstName.trim()
|
||||||
|
: undefined;
|
||||||
|
const lastName =
|
||||||
|
typeof userProfileRaw.lastName === 'string'
|
||||||
|
? userProfileRaw.lastName.trim()
|
||||||
|
: undefined;
|
||||||
|
const mobileNumber =
|
||||||
|
typeof userProfileRaw.mobileNumber === 'string'
|
||||||
|
? userProfileRaw.mobileNumber.trim()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (mobileNumber) {
|
||||||
|
const existingUser = await prismaClient.user.findFirst({
|
||||||
|
where: {
|
||||||
|
mobileNumber,
|
||||||
|
id: {
|
||||||
|
not: Number(userInfo.id),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingUser) {
|
||||||
|
throw new ApiError(
|
||||||
|
409,
|
||||||
|
'Mobile number already exists for another user. Please use a different mobile number.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await prismaClient.user.update({
|
await prismaClient.user.update({
|
||||||
where: { id: userInfo.id },
|
where: { id: userInfo.id },
|
||||||
data: {
|
data: {
|
||||||
...(firstName && { firstName }),
|
...(firstName !== undefined && { firstName }),
|
||||||
...(lastName && { lastName }),
|
...(lastName !== undefined && { lastName }),
|
||||||
...(mobileNumber && { mobileNumber }),
|
...(mobileNumber !== undefined && { mobileNumber }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -379,6 +415,63 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** DELETE EXISTING LOGO IF REQUESTED */
|
||||||
|
if (deleteCompanyLogo) {
|
||||||
|
const existingHost = await prismaClient.hostHeader.findFirst({
|
||||||
|
where: { userXid: userInfo.id },
|
||||||
|
select: { logoPath: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingHost?.logoPath) {
|
||||||
|
try {
|
||||||
|
const s3Key = getS3KeyFromUrl(existingHost.logoPath);
|
||||||
|
await deleteFromS3(s3Key);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('S3 delete failed for company logo:', existingHost.logoPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedCompany.logoPath = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** DELETE EXISTING PARENT COMPANY LOGO IF REQUESTED */
|
||||||
|
if (deleteParentCompanyLogo && parsedCompany.isSubsidairy) {
|
||||||
|
const existingHost = await prismaClient.hostHeader.findFirst({
|
||||||
|
where: { userXid: userInfo.id },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
hostParent: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
logoPath: true,
|
||||||
|
},
|
||||||
|
take: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingParent = Array.isArray(existingHost?.hostParent)
|
||||||
|
? existingHost.hostParent[0]
|
||||||
|
: existingHost?.hostParent;
|
||||||
|
|
||||||
|
if (existingParent?.logoPath) {
|
||||||
|
try {
|
||||||
|
const s3Key = getS3KeyFromUrl(existingParent.logoPath);
|
||||||
|
await deleteFromS3(s3Key);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('S3 delete failed for parent company logo:', existingParent.logoPath, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsedParentCompany) {
|
||||||
|
parsedParentCompany.logoPath = null;
|
||||||
|
} else {
|
||||||
|
parsedParentCompany = {
|
||||||
|
logoPath: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** UPLOAD LOGO (if provided) */
|
/** UPLOAD LOGO (if provided) */
|
||||||
const logoFile = files.find(
|
const logoFile = files.find(
|
||||||
(f) => f.fieldName === 'companyLogo' || f.fieldName === 'companyLogoFile'
|
(f) => f.fieldName === 'companyLogo' || f.fieldName === 'companyLogoFile'
|
||||||
@@ -449,6 +542,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
|||||||
parsedParentCompany,
|
parsedParentCompany,
|
||||||
uploadedParentDocs,
|
uploadedParentDocs,
|
||||||
isDraft,
|
isDraft,
|
||||||
|
{ deleteCompanyLogo, deleteParentCompanyLogo },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!createdOrUpdated) throw new ApiError(400, 'Failed to add/update company details.');
|
if (!createdOrUpdated) throw new ApiError(400, 'Failed to add/update company details.');
|
||||||
|
|||||||
@@ -395,6 +395,17 @@ const s3 = new AWS.S3({
|
|||||||
region: config.aws.region,
|
region: config.aws.region,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getS3KeyFromStoredPath(path?: string | null) {
|
||||||
|
if (!path) return null;
|
||||||
|
return path.startsWith('http') ? path.split('.com/')[1] || null : path;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveIncomingLogoPath(path?: string | null) {
|
||||||
|
if (typeof path !== 'string') return null;
|
||||||
|
const trimmed = path.trim();
|
||||||
|
return trimmed.length ? trimmed : null;
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateHostProfileInput = {
|
type UpdateHostProfileInput = {
|
||||||
firstName?: string;
|
firstName?: string;
|
||||||
lastName?: string | null;
|
lastName?: string | null;
|
||||||
@@ -415,6 +426,43 @@ type UpdateHostProfileInput = {
|
|||||||
export class HostService {
|
export class HostService {
|
||||||
constructor(private prisma: PrismaClient) { }
|
constructor(private prisma: PrismaClient) { }
|
||||||
|
|
||||||
|
private async getValidLogoUrl(
|
||||||
|
model: 'hostHeader' | 'hostParent',
|
||||||
|
recordId: number,
|
||||||
|
logoPath?: string | null,
|
||||||
|
) {
|
||||||
|
const key = getS3KeyFromStoredPath(logoPath);
|
||||||
|
if (!key) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await s3
|
||||||
|
.headObject({
|
||||||
|
Bucket: bucket,
|
||||||
|
Key: key,
|
||||||
|
})
|
||||||
|
.promise();
|
||||||
|
|
||||||
|
return await getPresignedUrl(bucket, key);
|
||||||
|
} catch (error: any) {
|
||||||
|
const statusCode = error?.statusCode;
|
||||||
|
const errorCode = error?.code;
|
||||||
|
const isMissingObject =
|
||||||
|
statusCode === 404 ||
|
||||||
|
errorCode === 'NotFound' ||
|
||||||
|
errorCode === 'NoSuchKey';
|
||||||
|
|
||||||
|
if (isMissingObject) {
|
||||||
|
await (this.prisma as any)[model].update({
|
||||||
|
where: { id: recordId },
|
||||||
|
data: { logoPath: null },
|
||||||
|
});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async createHost(data: CreateHostDto) {
|
async createHost(data: CreateHostDto) {
|
||||||
return this.prisma.user.create({ data });
|
return this.prisma.user.create({ data });
|
||||||
}
|
}
|
||||||
@@ -444,9 +492,83 @@ export class HostService {
|
|||||||
async getHostById(id: number) {
|
async getHostById(id: number) {
|
||||||
const host = await this.prisma.hostHeader.findFirst({
|
const host = await this.prisma.hostHeader.findFirst({
|
||||||
where: { userXid: id },
|
where: { userXid: id },
|
||||||
include: {
|
select: {
|
||||||
|
id: true,
|
||||||
|
logoPath: true,
|
||||||
|
companyName: true,
|
||||||
|
address1: true,
|
||||||
|
address2: true,
|
||||||
|
pinCode: true,
|
||||||
|
isSubsidairy: true,
|
||||||
|
registrationNumber: true,
|
||||||
|
panNumber: true,
|
||||||
|
gstNumber: true,
|
||||||
|
formationDate: true,
|
||||||
|
companyTypeXid: true,
|
||||||
|
websiteUrl: true,
|
||||||
|
instagramUrl: true,
|
||||||
|
facebookUrl: true,
|
||||||
|
linkedinUrl: true,
|
||||||
|
twitterUrl: true,
|
||||||
|
stepper: true,
|
||||||
|
hostStatusInternal: true,
|
||||||
|
hostStatusDisplay: true,
|
||||||
|
adminStatusInternal: true,
|
||||||
|
adminStatusDisplay: true,
|
||||||
|
amStatus: true,
|
||||||
|
agreementAccepted: true,
|
||||||
|
assignedOn: true,
|
||||||
|
agreementStartDate: true,
|
||||||
|
isApproved: true,
|
||||||
|
durationNumber: true,
|
||||||
|
durationFrequency: true,
|
||||||
|
isCommisionBase: true,
|
||||||
|
commisionPer: true,
|
||||||
|
amountPerBooking: true,
|
||||||
|
payoutDurationNum: true,
|
||||||
|
payoutDurationFrequency: true,
|
||||||
|
referencedBy: true,
|
||||||
hostParent: {
|
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: {
|
HostParenetDocuments: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -479,6 +601,7 @@ export class HostService {
|
|||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
emailAddress: true,
|
emailAddress: true,
|
||||||
|
dateOfBirth: true,
|
||||||
firstName: true,
|
firstName: true,
|
||||||
lastName: true,
|
lastName: true,
|
||||||
mobileNumber: true,
|
mobileNumber: true,
|
||||||
@@ -575,11 +698,15 @@ export class HostService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (host?.logoPath) {
|
if (host?.logoPath) {
|
||||||
const key = host.logoPath.startsWith('http')
|
const resolvedLogoUrl = await this.getValidLogoUrl(
|
||||||
? host.logoPath.split('.com/')[1]
|
'hostHeader',
|
||||||
: host.logoPath;
|
host.id,
|
||||||
|
host.logoPath,
|
||||||
host.logoPath = await getPresignedUrl(bucket, key);
|
);
|
||||||
|
if (!resolvedLogoUrl) {
|
||||||
|
host.logoPath = null;
|
||||||
|
}
|
||||||
|
(host as any).logoPresignedUrl = resolvedLogoUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (host.accountManager?.profileImage) {
|
if (host.accountManager?.profileImage) {
|
||||||
@@ -595,11 +722,15 @@ export class HostService {
|
|||||||
|
|
||||||
// Parent company logo
|
// Parent company logo
|
||||||
if (parent.logoPath) {
|
if (parent.logoPath) {
|
||||||
const key = parent.logoPath.startsWith('http')
|
const resolvedParentLogoUrl = await this.getValidLogoUrl(
|
||||||
? parent.logoPath.split('.com/')[1]
|
'hostParent',
|
||||||
: parent.logoPath;
|
parent.id,
|
||||||
|
parent.logoPath,
|
||||||
parent.logoPath = await getPresignedUrl(bucket, key);
|
);
|
||||||
|
if (!resolvedParentLogoUrl) {
|
||||||
|
parent.logoPath = null;
|
||||||
|
}
|
||||||
|
(parent as any).logoPresignedUrl = resolvedParentLogoUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parent documents
|
// Parent documents
|
||||||
@@ -837,10 +968,10 @@ export class HostService {
|
|||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createPassword(user_xid: number, password: string): Promise<boolean> {
|
async createPassword(user_xid: number, password: string): Promise<Partial<User>> {
|
||||||
// Find user by id
|
// Find user by id
|
||||||
const user = await this.prisma.user.findUnique({
|
const user = await this.prisma.user.findUnique({
|
||||||
where: { id: user_xid },
|
where: { id: user_xid, isActive: true },
|
||||||
select: { id: true, emailAddress: true, userPassword: true },
|
select: { id: true, emailAddress: true, userPassword: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -870,7 +1001,7 @@ export class HostService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBankBranchById(bankBranchXid: number) {
|
async getBankBranchById(bankBranchXid: number) {
|
||||||
@@ -1351,6 +1482,10 @@ export class HostService {
|
|||||||
parentCompanyData?: any | null,
|
parentCompanyData?: any | null,
|
||||||
parentDocuments?: HostDocumentInput[],
|
parentDocuments?: HostDocumentInput[],
|
||||||
isDraft: boolean = false,
|
isDraft: boolean = false,
|
||||||
|
options?: {
|
||||||
|
deleteCompanyLogo?: boolean;
|
||||||
|
deleteParentCompanyLogo?: boolean;
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
return await this.prisma.$transaction(async (tx) => {
|
return await this.prisma.$transaction(async (tx) => {
|
||||||
// Check if host already has a company
|
// Check if host already has a company
|
||||||
@@ -1395,7 +1530,7 @@ export class HostService {
|
|||||||
hostStatusDisplay = HOST_STATUS_DISPLAY.UNDER_REVIEW;
|
hostStatusDisplay = HOST_STATUS_DISPLAY.UNDER_REVIEW;
|
||||||
|
|
||||||
minglarStatusInternal = MINGLAR_STATUS_INTERNAL.AM_TO_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
|
// CASE 2: Admin has rejected but host can resubmit
|
||||||
else if (
|
else if (
|
||||||
@@ -1611,7 +1746,11 @@ export class HostService {
|
|||||||
? { connect: { id: Number(companyData.countryXid) } }
|
? { connect: { id: Number(companyData.countryXid) } }
|
||||||
: undefined,
|
: undefined,
|
||||||
pinCode: companyData.pinCode,
|
pinCode: companyData.pinCode,
|
||||||
logoPath: companyData.logoPath || existingHostCompany.logoPath,
|
logoPath: options?.deleteCompanyLogo
|
||||||
|
? null
|
||||||
|
: resolveIncomingLogoPath(companyData.logoPath) ??
|
||||||
|
existingHostCompany.logoPath ??
|
||||||
|
null,
|
||||||
isSubsidairy: companyData.isSubsidairy,
|
isSubsidairy: companyData.isSubsidairy,
|
||||||
registrationNumber: companyData.registrationNumber,
|
registrationNumber: companyData.registrationNumber,
|
||||||
panNumber: companyData.panNumber,
|
panNumber: companyData.panNumber,
|
||||||
@@ -1752,9 +1891,10 @@ export class HostService {
|
|||||||
? { connect: { id: Number(parentCompanyData.countryXid) } }
|
? { connect: { id: Number(parentCompanyData.countryXid) } }
|
||||||
: undefined,
|
: undefined,
|
||||||
pinCode: parentCompanyData.pinCode || null,
|
pinCode: parentCompanyData.pinCode || null,
|
||||||
logoPath:
|
logoPath: options?.deleteParentCompanyLogo
|
||||||
parentCompanyData?.logoPath ||
|
? null
|
||||||
existingParentCompany?.logoPath ||
|
: resolveIncomingLogoPath(parentCompanyData?.logoPath) ??
|
||||||
|
existingParentCompany?.logoPath ??
|
||||||
null,
|
null,
|
||||||
registrationNumber: parentCompanyData.registrationNumber || null,
|
registrationNumber: parentCompanyData.registrationNumber || null,
|
||||||
panNumber: parentCompanyData.panNumber || null,
|
panNumber: parentCompanyData.panNumber || null,
|
||||||
@@ -1810,9 +1950,10 @@ export class HostService {
|
|||||||
? { connect: { id: Number(parentCompanyData.countryXid) } }
|
? { connect: { id: Number(parentCompanyData.countryXid) } }
|
||||||
: undefined,
|
: undefined,
|
||||||
pinCode: parentCompanyData.pinCode || null,
|
pinCode: parentCompanyData.pinCode || null,
|
||||||
logoPath:
|
logoPath: options?.deleteParentCompanyLogo
|
||||||
parentCompanyData?.logoPath ||
|
? null
|
||||||
existingParentCompany?.logoPath ||
|
: resolveIncomingLogoPath(parentCompanyData?.logoPath) ??
|
||||||
|
existingParentCompany?.logoPath ??
|
||||||
null,
|
null,
|
||||||
registrationNumber: parentCompanyData.registrationNumber || null,
|
registrationNumber: parentCompanyData.registrationNumber || null,
|
||||||
panNumber: parentCompanyData.panNumber || null,
|
panNumber: parentCompanyData.panNumber || null,
|
||||||
@@ -3337,6 +3478,34 @@ export class HostService {
|
|||||||
throw new ApiError(404, 'Activity not found');
|
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
|
* 3️⃣ STATUS DECISION
|
||||||
* -------------------------------- */
|
* -------------------------------- */
|
||||||
|
|||||||
@@ -10,13 +10,16 @@ export async function resendOtpEmail(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "New OTP from Minglar Team";
|
const subject = "Your Minglar Verification Code";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear ${role},</p>
|
<p>Hi ${role},</p>
|
||||||
<p>Your new OTP is: <strong>${otp}</strong></p>
|
<p>Here's your verification code to get started:</p>
|
||||||
<p>This code will be valid for the next 5 minutes.</p>
|
<p><strong>${otp}</strong></p>
|
||||||
<p>Warm regards,<br/>Minglar Team</p>
|
<p>This code is valid for the next 5 minutes.</p>
|
||||||
|
<p>Once verified, you can continue setting up your Minglar account. If you didn't request this, you can safely ignore this email.</p>
|
||||||
|
<p>Need help? Reach out to us at info@minglargroup.com.</p>
|
||||||
|
<p>Warm regards,<br/>Team Minglar</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -37,3 +40,5 @@ export async function resendOtpEmail(
|
|||||||
throw new ApiError(500, "Failed to send OTP to host via email.");
|
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ export async function sendEmailToAM(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = `Host Application Re-Submited : ${hostCompanyName}`;
|
const subject = `${hostCompanyName} Has Resubmitted Their Application`;
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear ${amName},</p>
|
<p>Hello ${amName},</p>
|
||||||
<p>Host ${hostCompanyName} with reference number: <strong>${hostRefNumber}</strong> has re-submited the application with implimented suggestions.</p>
|
<p>${hostCompanyName} has updated and re-submitted their application for your review.</p>
|
||||||
<p>Please review their appliaction and take the necessary action.</p>
|
<p>Reference number: <strong>${hostRefNumber}</strong></p>
|
||||||
<p>Best regards,<br/>Minglar Team</p>
|
<p>Please log in to your dashboard to review the revised submission and proceed with the necessary action.</p>
|
||||||
|
<p>Thank you,<br/>Minglar Team</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -49,13 +50,14 @@ export async function sendEmailToMinglarAdmin(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = `New Host Application Recieved : ${hostCompanyName}`;
|
const subject = "New Host Application Submitted for Review";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear ${minglarAdminName},</p>
|
<p>Hi ${minglarAdminName},</p>
|
||||||
<p>Host ${hostCompanyName} with reference number: <strong>${hostRefNumber}</strong> has submited their application.</p>
|
<p>${hostCompanyName} has submitted their application and is awaiting your review.</p>
|
||||||
<p>Please review their appliaction and take the necessary action.</p>
|
<p>Reference number: <strong>${hostRefNumber}</strong></p>
|
||||||
<p>Best regards,<br/>Minglar Team</p>
|
<p>Please log in to your dashboard to review the submission and take the necessary action.</p>
|
||||||
|
<p>Thank you,<br/>Minglar Team</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -76,3 +78,43 @@ export async function sendEmailToMinglarAdmin(
|
|||||||
throw new ApiError(500, "Failed to send OTP to host via email.");
|
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 Host Application Submitted for Review";
|
||||||
|
|
||||||
|
const htmlContent = `
|
||||||
|
<p>Hi ${minglarAdminName},</p>
|
||||||
|
<p>${hostCompanyName} has submitted the pre-qualification details and is awaiting your review.</p>
|
||||||
|
<p>Reference number: <strong>${hostRefNumber}</strong></p>
|
||||||
|
<p>Please log in to your dashboard to review the submission and take the necessary action.</p>
|
||||||
|
<p>Thank you,<br/>Minglar Team</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await brevoService.sendEmail({
|
||||||
|
recipients: [{ email: emailAddress }],
|
||||||
|
subject,
|
||||||
|
htmlContent,
|
||||||
|
});
|
||||||
|
|
||||||
|
// console.log("📧 Email sent successfully:", result);
|
||||||
|
|
||||||
|
return {
|
||||||
|
sent: true,
|
||||||
|
// messageId: result.messageId
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Brevo email send failed:", err);
|
||||||
|
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { brevoService } from "@/common/email/brevoApi";
|
import { brevoService } from "../../../common/email/brevoApi";
|
||||||
import ApiError from "@/common/utils/helper/ApiError";
|
import ApiError from "../../../common/utils/helper/ApiError";
|
||||||
|
import config from "../../../config/config";
|
||||||
|
|
||||||
export async function sendOtpEmailForHost(
|
export async function sendOtpEmailForHost(
|
||||||
emailAddress: string,
|
emailAddress: string,
|
||||||
@@ -9,14 +10,16 @@ export async function sendOtpEmailForHost(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "OTP for Host Registration";
|
const subject = "Your Minglar Verification Code";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear Host,</p>
|
<p>Hi there,</p>
|
||||||
<p>You’re almost all set! 🎉</p>
|
<p>Here's your verification code to get started:</p>
|
||||||
<p>Enter <strong>${otp}</strong> to wrap your registration.</p>
|
<p><strong>${otp}</strong></p>
|
||||||
<p>This code will be valid for the next 5 minutes.</p>
|
<p>This code is valid for the next 5 minutes.</p>
|
||||||
<p>Warm regards,<br/>Minglar Team</p>
|
<p>Once verified, you can continue setting up your Minglar account. If you didn't request this, you can safely ignore this email.</p>
|
||||||
|
<p>Need help? Reach out to us at info@minglargroup.com.</p>
|
||||||
|
<p>Warm regards,<br/>Team Minglar</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -37,3 +40,49 @@ export async function sendOtpEmailForHost(
|
|||||||
throw new ApiError(500, "Failed to send OTP to host via email.");
|
throw new ApiError(500, "Failed to send OTP to host via email.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function sendWelcomeEmailToHost(
|
||||||
|
emailAddress: string,
|
||||||
|
): Promise<{
|
||||||
|
sent: boolean;
|
||||||
|
// messageId: string
|
||||||
|
}> {
|
||||||
|
|
||||||
|
const subject = "Get Started as a Minglar Host";
|
||||||
|
|
||||||
|
const htmlContent = `
|
||||||
|
<p>Hi ${emailAddress},</p>
|
||||||
|
<p>We're excited to have you join Minglar as a host. Welcome aboard!</p>
|
||||||
|
<p>To get started and bring your activities live, here's what comes next:</p>
|
||||||
|
<p><strong>Your next steps:</strong></p>
|
||||||
|
<p>1. Complete your host profile</p>
|
||||||
|
<p>2. Complete the pre-qualification process for all your activities</p>
|
||||||
|
<p>3. Submit your activity details for review</p>
|
||||||
|
<p>4. Go live and start receiving bookings</p>
|
||||||
|
<p><strong>Access your Host Portal:</strong><br/>
|
||||||
|
<a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a>
|
||||||
|
</p>
|
||||||
|
<p>If you need any support along the way, our team is always here to help. You can reach us anytime at info@minglargroup.com.</p>
|
||||||
|
<p>We're looking forward to seeing your experiences come to life on Minglar.</p>
|
||||||
|
<p>Warm regards,<br/>Team Minglar</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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const handler = safeHandler(async (
|
|||||||
if (!hostDetails?.emailAddress) {
|
if (!hostDetails?.emailAddress) {
|
||||||
throw new ApiError(404, 'Host details or email address not found');
|
throw new ApiError(404, 'Host details or email address not found');
|
||||||
}
|
}
|
||||||
await sendEmailToHostForRejectedApplication(hostDetails.emailAddress)
|
await sendEmailToHostForRejectedApplication(hostDetails.emailAddress, hostDetails.firstName || 'Host');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
|
|||||||
@@ -2,18 +2,19 @@
|
|||||||
import { brevoService } from '@/common/email/brevoApi';
|
import { brevoService } from '@/common/email/brevoApi';
|
||||||
import ApiError from '@/common/utils/helper/ApiError';
|
import ApiError from '@/common/utils/helper/ApiError';
|
||||||
|
|
||||||
export async function sendAMEmailForHostAssign(emailAddress: string): Promise<{
|
export async function sendAMEmailForHostAssign(emailAddress: string, accountManagerName?: string): Promise<{
|
||||||
sent: boolean;
|
sent: boolean;
|
||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
const subject = 'Minglar Admin: Host Assignment Notification';
|
const subject = "You've Been Assigned a New Host";
|
||||||
|
|
||||||
|
const displayName = accountManagerName?.trim() || "there";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Hi,</p>
|
<p>Hi ${displayName},</p>
|
||||||
|
<p>A new host has been assigned to you by the Minglar team.</p>
|
||||||
<p>You’ve been assigned the <strong>Host</strong> role by Minglar Admin.</p>
|
<p>You can now manage and support this host through your admin dashboard. Log in to review the host's details, connect with them, and take the next steps as needed.</p>
|
||||||
|
<p>Warm regards,<br/>Minglar Team</p>
|
||||||
<p>Best regards,<br/>Minglar Admin Team</p>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -35,3 +36,5 @@ export async function sendAMEmailForHostAssign(emailAddress: string): Promise<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ...existing code...
|
// ...existing code...
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ export class AMNotificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sendAMEmailForHostAssign(amUser.emailAddress);
|
const amName = [amUser.firstName, amUser.lastName].filter(Boolean).join(' ').trim();
|
||||||
|
await sendAMEmailForHostAssign(amUser.emailAddress, amName);
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error sending AM assignment email', err);
|
console.error('Error sending AM assignment email', err);
|
||||||
|
|||||||
@@ -10,15 +10,16 @@ export async function sendEmailToHostForApprovedApplication(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Approval for your application";
|
const subject = "Host Onboarding Application Approved";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear ${name},</p>
|
<p>Hi ${name},</p>
|
||||||
<p>Congratulations, Your application to minglar admin has been approved.</p>
|
<p>We're pleased to inform you that your host onboarding application has been approved by our team.</p>
|
||||||
<p>You can start onboarding your activities through the host panel.</p>
|
<p>You can now proceed with completing your activity pre-qualification process.</p>
|
||||||
<p> You can login to your account using the link below:<br/>
|
<p>Please click the link below to log in to your account and continue:</p>
|
||||||
<strong>Link:</strong> ${config.HOST_LINK} </p>
|
<p><a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a></p>
|
||||||
<p>Best regards,<br/>Minglar Team</p>
|
<p>If you have any questions or need assistance, feel free to reach out to us at info@minglargroup.com.</p>
|
||||||
|
<p>Warm regards,<br/>Minglar Team</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -47,13 +48,16 @@ export async function sendEmailToHostForMinglarApproval(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Approval for your application";
|
const subject = "Host Onboarding Application Approved";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear Host,</p>
|
<p>Hi there,</p>
|
||||||
<p>Congratulations, Your application to minglar admin has been approved by minglar admin.</p>
|
<p>We're pleased to inform you that your host onboarding application has been approved by our team.</p>
|
||||||
<p>Minglar admin will assign account manager to your application.</p>
|
<p>You can now proceed with completing your activity pre-qualification process.</p>
|
||||||
<p>Best regards,<br/>Minglar Team</p>
|
<p>Please click the link below to log in to your account and continue:</p>
|
||||||
|
<p><a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a></p>
|
||||||
|
<p>If you have any questions or need assistance, feel free to reach out to us at info@minglargroup.com.</p>
|
||||||
|
<p>Warm regards,<br/>Minglar Team</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -83,15 +87,16 @@ export async function sendAMPQQAcceptanceMailtoHost(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Approval for your activity onboarding application";
|
const subject = "Your Activity Has Been Qualified for Onboarding";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear ${name},</p>
|
<p>Hi ${name},</p>
|
||||||
<p>Congratulations, Your activity onboarding application to minglar admin has been approved.</p>
|
<p>We're pleased to inform you that your activity has been qualified on the Minglar platform.</p>
|
||||||
<p>You can start adding other details of your activity through the host panel.</p>
|
<p>You can now proceed to complete the details of your activity through the Host portal.</p>
|
||||||
<p> You can login to your account using the link below:<br/>
|
<p>Please click the link below to log in to your account and continue:</p>
|
||||||
<strong>Link:</strong> ${config.HOST_LINK_PQ} </p>
|
<p><a href="${config.HOST_LINK_PQ}" target="_blank">${config.HOST_LINK_PQ}</a></p>
|
||||||
<p>Best regards,<br/>Minglar Team</p>
|
<p>If you have any questions or need assistance, feel free to reach out at info@minglargroup.com.</p>
|
||||||
|
<p>Warm regards,<br/>Minglar Team</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -121,15 +126,19 @@ export async function sendActivityAcceptanceMailtoHost(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Approval for your activity details application";
|
const subject = "Onboarding Completed | You Can Now Set Up Your Activity Schedule and Listing";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear ${name},</p>
|
<p>Hi ${name},</p>
|
||||||
<p>Congratulations, Your activity details application to minglar admin has been approved.</p>
|
<p>Great news!</p>
|
||||||
<p>You can start getting orders for your activity.</p>
|
<p>You have successfully completed the onboarding process for your activity on Minglar.</p>
|
||||||
<p>You can login to your account using the link below:<br/>
|
<p>You can now move on to the next step by setting up your activity's schedule. Once this is done, your activity will be ready to be listed on the Minglar app.</p>
|
||||||
<strong>Link:</strong> ${config.HOST_LINK} </p>
|
<p><strong>Access your Host Portal:</strong><br/>
|
||||||
<p>Best regards,<br/>Minglar Team</p>
|
<a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a>
|
||||||
|
</p>
|
||||||
|
<p>If you have any questions or need assistance while setting things up, our team is here to help at info@minglargroup.com.</p>
|
||||||
|
<p>We're excited to see your activity take shape and look forward to having it live on Minglar soon.</p>
|
||||||
|
<p>Warm regards,<br/>Team Minglar</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -150,3 +159,5 @@ export async function sendActivityAcceptanceMailtoHost(
|
|||||||
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,22 +9,16 @@ export async function sendInvitationEmailForMinglarAdmin(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Minglar Admin: Your Team Invitation";
|
const subject = "Team Invitation: Account Manager at Minglar";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Hi there,</p>
|
<p>Hi ${emailAddress},</p>
|
||||||
<p>We're excited to invite you to join the <strong>Minglar Admin Team</strong>!<br/>
|
<p>We're happy to invite you to join the Minglar team as an Account Manager.</p>
|
||||||
Please use the link below to set up your account and get started.</p>
|
<p>To get started, please set up your account using the link below:</p>
|
||||||
|
<p><a href="${link}" target="_blank">${link}</a></p>
|
||||||
<p><strong>Access Your Invitation:</strong><br/>
|
<p>If you have any questions or need help during the setup process, feel free to reach out.</p>
|
||||||
<a href="${link}">${link}</a></p>
|
<p>We look forward to working with you.</p>
|
||||||
|
<p>Warm regards,<br/>Minglar Team</p>
|
||||||
<p>If you have any questions or need assistance, feel free to reach out — we’re here to help.<br/>
|
|
||||||
We look forward to having you on board!</p>
|
|
||||||
|
|
||||||
<p>Welcome aboard!<br/>
|
|
||||||
<strong>The Minglar Admin Team</strong></p>
|
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -45,3 +39,4 @@ We look forward to having you on board!</p>
|
|||||||
throw new ApiError(500, "Failed to send invitation via email.");
|
throw new ApiError(500, "Failed to send invitation via email.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ const bucket = config.aws.bucketName;
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MinglarService {
|
export class MinglarService {
|
||||||
constructor(private prisma: PrismaService | PrismaClient) {}
|
constructor(private prisma: PrismaService | PrismaClient) { }
|
||||||
|
|
||||||
async createPassword(user_xid: number, password: string): Promise<boolean> {
|
async createPassword(user_xid: number, password: string): Promise<boolean> {
|
||||||
// Find user by id
|
// Find user by id
|
||||||
@@ -144,10 +144,10 @@ export class MinglarService {
|
|||||||
|
|
||||||
async getUserDetails(id: number) {
|
async getUserDetails(id: number) {
|
||||||
const hostDetail = await this.prisma.hostHeader.findFirst({
|
const hostDetail = await this.prisma.hostHeader.findFirst({
|
||||||
where: { id: id },
|
where: { id: id, isActive: true },
|
||||||
});
|
});
|
||||||
const userDetails = await this.prisma.user.findUnique({
|
const userDetails = await this.prisma.user.findUnique({
|
||||||
where: { id: hostDetail.userXid },
|
where: { id: hostDetail.userXid, isActive: true },
|
||||||
});
|
});
|
||||||
return userDetails;
|
return userDetails;
|
||||||
}
|
}
|
||||||
@@ -314,6 +314,8 @@ export class MinglarService {
|
|||||||
companyName: true,
|
companyName: true,
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
userRefNumber: true,
|
userRefNumber: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -375,11 +377,52 @@ export class MinglarService {
|
|||||||
const {
|
const {
|
||||||
paginationService,
|
paginationService,
|
||||||
} = require('@/common/utils/pagination/pagination.service');
|
} = 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,
|
hostActivities,
|
||||||
totalCount,
|
totalCount,
|
||||||
paginationOptions || { page: 1, limit: 10, skip: 0 },
|
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(
|
async createUserRevenue(
|
||||||
@@ -818,7 +861,7 @@ export class MinglarService {
|
|||||||
if (
|
if (
|
||||||
userStatus &&
|
userStatus &&
|
||||||
userStatus.trim().toLowerCase() ===
|
userStatus.trim().toLowerCase() ===
|
||||||
MINGLAR_STATUS_DISPLAY.NEW.toLowerCase()
|
MINGLAR_STATUS_DISPLAY.NEW.toLowerCase()
|
||||||
) {
|
) {
|
||||||
filters.adminStatusInternal = MINGLAR_STATUS_INTERNAL.ADMIN_TO_REVIEW;
|
filters.adminStatusInternal = MINGLAR_STATUS_INTERNAL.ADMIN_TO_REVIEW;
|
||||||
}
|
}
|
||||||
@@ -832,9 +875,9 @@ export class MinglarService {
|
|||||||
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
|
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
|
||||||
display: MINGLAR_STATUS_DISPLAY.NEW,
|
display: MINGLAR_STATUS_DISPLAY.NEW,
|
||||||
},
|
},
|
||||||
To_Review: {
|
Re_Submitted: {
|
||||||
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
|
internal: MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW,
|
||||||
display: MINGLAR_STATUS_DISPLAY.TO_REVIEW,
|
display: MINGLAR_STATUS_DISPLAY.RE_SUBMITTED,
|
||||||
},
|
},
|
||||||
Enhancing: {
|
Enhancing: {
|
||||||
internal: MINGLAR_STATUS_INTERNAL.AM_REJECTED,
|
internal: MINGLAR_STATUS_INTERNAL.AM_REJECTED,
|
||||||
@@ -945,6 +988,7 @@ export class MinglarService {
|
|||||||
const where: any = {
|
const where: any = {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
hostStatusInternal: { notIn: [HOST_STATUS_INTERNAL.DRAFT] },
|
hostStatusInternal: { notIn: [HOST_STATUS_INTERNAL.DRAFT] },
|
||||||
|
adminStatusInternal: { notIn: [MINGLAR_STATUS_INTERNAL.AM_TO_REVIEW] },
|
||||||
};
|
};
|
||||||
|
|
||||||
if (search?.trim()) {
|
if (search?.trim()) {
|
||||||
@@ -1187,15 +1231,15 @@ export class MinglarService {
|
|||||||
// Build search filter if search term is provided
|
// Build search filter if search term is provided
|
||||||
const searchFilter = search
|
const searchFilter = search
|
||||||
? {
|
? {
|
||||||
OR: [
|
OR: [
|
||||||
{ email: { contains: search, mode: 'insensitive' as const } },
|
{ email: { contains: search, mode: 'insensitive' as const } },
|
||||||
{ firstName: { contains: search, mode: 'insensitive' as const } },
|
{ firstName: { contains: search, mode: 'insensitive' as const } },
|
||||||
{ lastName: { contains: search, mode: 'insensitive' as const } },
|
{ lastName: { contains: search, mode: 'insensitive' as const } },
|
||||||
{
|
{
|
||||||
userRefNumber: { contains: search, mode: 'insensitive' as const },
|
userRefNumber: { contains: search, mode: 'insensitive' as const },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
// 1. Fetch all required users (Admin, Co-Admin, AM)
|
// 1. Fetch all required users (Admin, Co-Admin, AM)
|
||||||
@@ -1367,7 +1411,7 @@ export class MinglarService {
|
|||||||
|
|
||||||
const amUser = await this.prisma.user.findUnique({
|
const amUser = await this.prisma.user.findUnique({
|
||||||
where: { id: accountManagerXid, isActive: true },
|
where: { id: accountManagerXid, isActive: true },
|
||||||
select: { emailAddress: true },
|
select: { emailAddress: true, firstName: true, lastName: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!amUser || !amUser.emailAddress) {
|
if (!amUser || !amUser.emailAddress) {
|
||||||
@@ -1378,7 +1422,8 @@ export class MinglarService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sendAMEmailForHostAssign(amUser.emailAddress);
|
const amName = [amUser.firstName, amUser.lastName].filter(Boolean).join(' ').trim();
|
||||||
|
await sendAMEmailForHostAssign(amUser.emailAddress, amName);
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error sending AM assignment email', err);
|
console.error('Error sending AM assignment email', err);
|
||||||
@@ -1711,6 +1756,7 @@ export class MinglarService {
|
|||||||
isEmailVerfied: true,
|
isEmailVerfied: true,
|
||||||
isMobileVerfied: true,
|
isMobileVerfied: true,
|
||||||
isBiometric: true,
|
isBiometric: true,
|
||||||
|
createdAt: true,
|
||||||
userAddressDetails: {
|
userAddressDetails: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -1826,8 +1872,8 @@ export class MinglarService {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async rejectActivityApplicationByAM(activityId: number, user_xid: number) {
|
async rejectActivityApplicationByAM(activityId: number, user_xid: number) {
|
||||||
return await this.prisma.$transaction(async (tx) => {
|
return await this.prisma.$transaction(async (tx) => {
|
||||||
await tx.activities.update({
|
await tx.activities.update({
|
||||||
|
|||||||
@@ -4,19 +4,22 @@ import config from "../../../config/config";
|
|||||||
|
|
||||||
export async function sendEmailToHostForRejectedApplication(
|
export async function sendEmailToHostForRejectedApplication(
|
||||||
emailAddress: string,
|
emailAddress: string,
|
||||||
|
firstName: string
|
||||||
): Promise<{
|
): Promise<{
|
||||||
sent: boolean;
|
sent: boolean;
|
||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Rejection for your application";
|
const subject = "Action Needed: Host Onboarding Application";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear Host,</p>
|
<p>Hi ${firstName},</p>
|
||||||
<p>Sorry to say that, But your application to minglar admin has been rejected.</p>
|
<p>After reviewing your submission, we're unable to proceed at this stage, as some details require further updates. We encourage you to log in to your Host portal to review the feedback provided and make the necessary changes.</p>
|
||||||
<p>Please update your application and resubmit it.</p>
|
<p><strong>Host portal login:</strong><br/>
|
||||||
<p>If you have any questions please contact to minglar admin.</p>
|
<a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a>
|
||||||
<p>Best regards,<br/>Minglar Team</p>
|
</p>
|
||||||
|
<p>We appreciate your interest in Minglar and look forward to reviewing your updated application.</p>
|
||||||
|
<p>Warm regards,<br/>Team Minglar</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -47,21 +50,14 @@ export async function sendAMRejectionMailtoHost(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Improvement of your application";
|
const subject = "Action Needed: Host Onboarding Application";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear ${name},</p>
|
<p>Hi ${name},</p>
|
||||||
<p> Your account manager has reviewed your application and provided some suggestions. <br/>
|
<p>After reviewing your submission, we're unable to proceed at this stage, as some details require further updates. We encourage you to log in to your Host portal to review the feedback provided and make the necessary changes.</p>
|
||||||
Please make the necessary improvements and re-submit your application to proceed with the onboarding process on Minglar.</p>
|
<p><a href="${link}" target="_blank">${link}</a></p>
|
||||||
<p> You may access your application using the link below:<br/>
|
<p>We appreciate your interest in Minglar and look forward to reviewing your updated application.</p>
|
||||||
<strong>Link:</strong>
|
<p>Warm regards,<br/>Team Minglar</p>
|
||||||
<a href="${link}" target="_blank">
|
|
||||||
${link}
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<p> If you have any questions, please feel free to contact the Minglar Support Team. </p>
|
|
||||||
<p> Best regards,<br/>
|
|
||||||
<strong>Minglar Team</strong> </p>
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -92,22 +88,17 @@ export async function sendAMPQQRejectionMailtoHost(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Improvement of your activity onboarding application";
|
const subject = "Action Needed: Activity Pre-qualification";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear ${name},</p>
|
<p>Hi ${name},</p>
|
||||||
|
<p>Thank you for taking the time to submit your activity pre-qualification details on the Minglar platform.</p>
|
||||||
<p>Your account manager has reviewed your activity application and provided some suggestions.<br/>
|
<p>After reviewing your submission, we're unable to approve the application at this stage. However, we encourage you to make the suggested updates and refinements, as many applications are successfully approved after revision.</p>
|
||||||
Please make the necessary improvements and re-submit your activity application along with the pre-qualification answers to proceed with the onboarding process on Minglar.</p>
|
<p>You can log in to the Host portal to review the feedback and continue updating your application:</p>
|
||||||
|
<p><a href="${config.HOST_LINK_PQ}" target="_blank">${config.HOST_LINK_PQ}</a></p>
|
||||||
<p>You may access your activity onboarding application using the link below:<br/>
|
<p>If you need any guidance, feel free to reach out to us at info@minglargroup.com.</p>
|
||||||
<strong>Link:</strong> ${config.HOST_LINK}</p>
|
<p>We appreciate your interest in partnering with Minglar and look forward to reviewing your updated submission.</p>
|
||||||
|
<p>Thank you,<br/>Minglar Team</p>
|
||||||
<p>If you have any questions, please feel free to contact the Minglar Support Team.</p>
|
|
||||||
|
|
||||||
<p>Best regards,<br/>
|
|
||||||
<strong>Minglar Team</strong></p>
|
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -138,22 +129,19 @@ export async function sendActivityRejectionMailtoHost(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "Improvement of your activity onboarding application";
|
const subject = "Action Needed: Activity Onboarding";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear ${name},</p>
|
<p>Hi ${name},</p>
|
||||||
|
<p>Thank you for submitting your activity for review.</p>
|
||||||
<p>Your account manager has reviewed your activity application and provided some suggestions.<br/>
|
<p>After evaluating the details provided, we're unable to approve the listing at this stage. A few updates are required before we can proceed.</p>
|
||||||
Please make the necessary improvements and re-submit your activity application along with the pre-qualification answers to proceed with the onboarding process on Minglar.</p>
|
<p>Please log in to your Host Portal to review the feedback and make the necessary revisions.</p>
|
||||||
|
<p><strong>Access your Host Portal:</strong><br/>
|
||||||
<p>You may access your activity onboarding application using the link below:<br/>
|
<a href="${config.HOST_LINK}" target="_blank">${config.HOST_LINK}</a>
|
||||||
<strong>Link:</strong> ${config.HOST_LINK}</p>
|
</p>
|
||||||
|
<p>Once the updates have been submitted, our team will re-evaluate your activity promptly.</p>
|
||||||
<p>If you have any questions, please feel free to contact the Minglar Support Team.</p>
|
<p>If you have any questions or need clarification on the feedback, feel free to reach out to us at info@minglargroup.com. We're happy to assist.</p>
|
||||||
|
<p>Warm regards,<br/>The Minglar Team</p>
|
||||||
<p>Best regards,<br/>
|
|
||||||
<strong>Minglar Team</strong></p>
|
|
||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -174,3 +162,4 @@ export async function sendActivityRejectionMailtoHost(
|
|||||||
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,13 +9,16 @@ export async function sendOtpEmailForMinglarAdmin(
|
|||||||
// messageId: string
|
// messageId: string
|
||||||
}> {
|
}> {
|
||||||
|
|
||||||
const subject = "OTP for Minglar Admin Registration";
|
const subject = "Your Minglar Verification Code";
|
||||||
|
|
||||||
const htmlContent = `
|
const htmlContent = `
|
||||||
<p>Dear User,</p>
|
<p>Hi there,</p>
|
||||||
<p>Your OTP for minglar admin registration is: <strong>${otp}</strong></p>
|
<p>To continue your Minglar Admin registration, please use the following One-Time Password (OTP):</p>
|
||||||
<p>This code is valid for 5 minutes. Please do not share it with anyone.</p>
|
<p><strong>${otp}</strong></p>
|
||||||
<p>Best regards,<br/>Minglar Team</p>
|
<p>This code is valid for 5 minutes.</p>
|
||||||
|
<p>For your security, please do not share this code with anyone.</p>
|
||||||
|
<p>If you did not request this OTP, please ignore this email.</p>
|
||||||
|
<p>Warm regards,<br/>Minglar Team</p>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -36,3 +39,4 @@ export async function sendOtpEmailForMinglarAdmin(
|
|||||||
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
throw new ApiError(500, "Failed to send OTP to minglar admin via email.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -140,7 +140,9 @@ export class PrePopulateService {
|
|||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { documentDetails, countryDetails, stateDetails, companyTypeDetails };
|
const adminEmail = config.MinglarAdminEmail;
|
||||||
|
|
||||||
|
return { documentDetails, countryDetails, stateDetails, companyTypeDetails, adminEmail };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllFrequencies() {
|
async getAllFrequencies() {
|
||||||
|
|||||||
@@ -86,9 +86,10 @@ export const handler = safeHandler(
|
|||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
success: true,
|
success: true,
|
||||||
message: 'Access token generated successfully',
|
message: 'Access token generated successfully',
|
||||||
|
data: {
|
||||||
accessToken: newAccessToken.access.token,
|
accessToken: newAccessToken.access.token,
|
||||||
accessTokenExpires: newAccessToken.access.expires,
|
accessTokenExpires: newAccessToken.access.expires,
|
||||||
data: null,
|
},
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -30,16 +30,17 @@ export const handler = safeHandler(async (
|
|||||||
const transactionResult = await prismaClient.$transaction(async (tx) => {
|
const transactionResult = await prismaClient.$transaction(async (tx) => {
|
||||||
const user = await tx.user.findFirst({
|
const user = await tx.user.findFirst({
|
||||||
where: { mobileNumber: mobileNumber, isActive: true, userStatus: USER_STATUS.ACTIVE },
|
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 newUserLocal;
|
||||||
let isNewUser = false;
|
let isNewUser = false;
|
||||||
|
|
||||||
|
|
||||||
if (user && !user.userPasscode) {
|
if (user && (!user.userPasscode || !user.firstName)) {
|
||||||
// reuse existing invited user record
|
// reuse existing invited user record
|
||||||
newUserLocal = user;
|
newUserLocal = user;
|
||||||
|
isNewUser = true;
|
||||||
} else if (user) {
|
} else if (user) {
|
||||||
// Fully registered user already exists
|
// Fully registered user already exists
|
||||||
newUserLocal = user;
|
newUserLocal = user;
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||||
|
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||||
|
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||||
|
import { UserService } from '../../services/user.service';
|
||||||
|
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
|
||||||
|
|
||||||
|
const userService = new UserService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfo = await verifyUserToken(token);
|
||||||
|
const userId = Number(userInfo.id);
|
||||||
|
|
||||||
|
if (!userId || isNaN(userId)) {
|
||||||
|
throw new ApiError(400, 'Invalid user ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchQuery = event.queryStringParameters?.searchQuery ?? '';
|
||||||
|
const result = await userService.searchConnectionPeople(userId, searchQuery);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Connection people retrieved successfully',
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
|
||||||
|
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||||
|
import { ItineraryService } from '../../services/itinerary.service';
|
||||||
|
|
||||||
|
const itineraryService = new ItineraryService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfo = await verifyUserToken(token);
|
||||||
|
const userId = Number(userInfo.id);
|
||||||
|
|
||||||
|
if (!userId || isNaN(userId)) {
|
||||||
|
throw new ApiError(400, 'Invalid user ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const itineraryHeaderXidRaw =
|
||||||
|
event.queryStringParameters?.itineraryHeaderXid ?? null;
|
||||||
|
|
||||||
|
let itineraryHeaderXid: number | undefined;
|
||||||
|
if (
|
||||||
|
itineraryHeaderXidRaw !== null &&
|
||||||
|
itineraryHeaderXidRaw !== undefined &&
|
||||||
|
itineraryHeaderXidRaw !== ''
|
||||||
|
) {
|
||||||
|
itineraryHeaderXid = Number(itineraryHeaderXidRaw);
|
||||||
|
|
||||||
|
if (!Number.isInteger(itineraryHeaderXid) || itineraryHeaderXid <= 0) {
|
||||||
|
throw new ApiError(400, 'Invalid itineraryHeaderXid');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await itineraryService.getAllUserSavedItineraries(
|
||||||
|
userId,
|
||||||
|
itineraryHeaderXid,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Saved itineraries retrieved successfully',
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
|
||||||
|
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||||
|
import { ItineraryService } from '../../services/itinerary.service';
|
||||||
|
|
||||||
|
const itineraryService = new ItineraryService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfo = await verifyUserToken(token);
|
||||||
|
const userId = Number(userInfo.id);
|
||||||
|
|
||||||
|
if (!userId || Number.isNaN(userId)) {
|
||||||
|
throw new ApiError(400, 'Invalid user ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: Record<string, any> = {};
|
||||||
|
if (event.body) {
|
||||||
|
try {
|
||||||
|
body = JSON.parse(event.body);
|
||||||
|
} catch {
|
||||||
|
throw new ApiError(400, 'Invalid JSON body');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
userLat: Number(body.userLat),
|
||||||
|
userLong: Number(body.userLong),
|
||||||
|
startDate: body.startDate,
|
||||||
|
endDate: body.endDate,
|
||||||
|
startTime: body.startTime,
|
||||||
|
endTime: body.endTime,
|
||||||
|
energyLevelXid:
|
||||||
|
body.energyLevelXid !== undefined && body.energyLevelXid !== null
|
||||||
|
? Number(body.energyLevelXid)
|
||||||
|
: undefined,
|
||||||
|
entryTypeXid: Number(body.entryTypeXid),
|
||||||
|
groupCount:
|
||||||
|
body.groupCount !== undefined && body.groupCount !== null
|
||||||
|
? Number(body.groupCount)
|
||||||
|
: undefined,
|
||||||
|
page: body.page !== undefined ? Number(body.page) : 1,
|
||||||
|
limit: body.limit !== undefined ? Number(body.limit) : 20,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
Number.isNaN(payload.userLat) ||
|
||||||
|
Number.isNaN(payload.userLong) ||
|
||||||
|
!payload.startDate ||
|
||||||
|
!payload.endDate ||
|
||||||
|
!payload.startTime ||
|
||||||
|
!payload.endTime ||
|
||||||
|
(payload.energyLevelXid !== undefined &&
|
||||||
|
Number.isNaN(payload.energyLevelXid)) ||
|
||||||
|
Number.isNaN(payload.entryTypeXid) ||
|
||||||
|
(payload.groupCount !== undefined && Number.isNaN(payload.groupCount)) ||
|
||||||
|
Number.isNaN(payload.page) ||
|
||||||
|
Number.isNaN(payload.limit)
|
||||||
|
) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'userLat, userLong, startDate, endDate, startTime, endTime, entryTypeXid, page and limit are required. energyLevelXid is optional.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await itineraryService.getMatchingBucketInterestedActivities(
|
||||||
|
userId,
|
||||||
|
payload,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Matching itinerary activities retrieved successfully',
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
|
||||||
|
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||||
|
import { ItineraryService } from '../../services/itinerary.service';
|
||||||
|
|
||||||
|
const itineraryService = new ItineraryService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfo = await verifyUserToken(token);
|
||||||
|
const userId = Number(userInfo.id);
|
||||||
|
|
||||||
|
if (!userId || isNaN(userId)) {
|
||||||
|
throw new ApiError(400, 'Invalid user ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await itineraryService.getUserItineraryDetails(userId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Itinerary details retrieved successfully',
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
|
||||||
|
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||||
|
import { ItineraryService } from '../../services/itinerary.service';
|
||||||
|
|
||||||
|
const itineraryService = new ItineraryService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfo = await verifyUserToken(token);
|
||||||
|
const userId = Number(userInfo.id);
|
||||||
|
|
||||||
|
if (!userId || Number.isNaN(userId)) {
|
||||||
|
throw new ApiError(400, 'Invalid user ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: Record<string, any> = {};
|
||||||
|
if (event.body) {
|
||||||
|
try {
|
||||||
|
body = JSON.parse(event.body);
|
||||||
|
} catch {
|
||||||
|
throw new ApiError(400, 'Invalid JSON body');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const itineraryActivityXid = Number(body.itineraryActivityXid);
|
||||||
|
if (!Number.isInteger(itineraryActivityXid) || itineraryActivityXid <= 0) {
|
||||||
|
throw new ApiError(400, 'itineraryActivityXid is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedEquipmentIds = Array.isArray(body.selectedEquipmentIds)
|
||||||
|
? body.selectedEquipmentIds.map((id: unknown) => Number(id))
|
||||||
|
: [];
|
||||||
|
const selectedFoodTypeIds = Array.isArray(body.selectedFoodTypeIds)
|
||||||
|
? body.selectedFoodTypeIds.map((id: unknown) => Number(id))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (selectedEquipmentIds.some((id) => !Number.isInteger(id) || id <= 0)) {
|
||||||
|
throw new ApiError(400, 'selectedEquipmentIds must contain valid ids.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedFoodTypeIds.some((id) => !Number.isInteger(id) || id <= 0)) {
|
||||||
|
throw new ApiError(400, 'selectedFoodTypeIds must contain valid ids.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const toOptionalId = (value: unknown) => {
|
||||||
|
if (value === undefined || value === null || value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = Number(value);
|
||||||
|
if (!Number.isInteger(parsed) || parsed <= 0) {
|
||||||
|
throw new ApiError(400, 'One or more selected option ids are invalid.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await itineraryService.saveItineraryActivitySelections(userId, {
|
||||||
|
itineraryActivityXid,
|
||||||
|
isFoodOpted:
|
||||||
|
body.isFoodOpted === undefined ? false : Boolean(body.isFoodOpted),
|
||||||
|
selectedFoodTypeIds,
|
||||||
|
isTrainerOpted:
|
||||||
|
body.isTrainerOpted === undefined ? false : Boolean(body.isTrainerOpted),
|
||||||
|
isInActivityNavigationOpted:
|
||||||
|
body.isInActivityNavigationOpted === undefined
|
||||||
|
? false
|
||||||
|
: Boolean(body.isInActivityNavigationOpted),
|
||||||
|
selectedNavigationModeXid: toOptionalId(body.selectedNavigationModeXid),
|
||||||
|
selectedEquipmentIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Itinerary activity selections saved successfully.',
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
261
src/modules/user/handlers/itinerary/saveUserItinerary.ts
Normal file
261
src/modules/user/handlers/itinerary/saveUserItinerary.ts
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
|
||||||
|
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||||
|
import { ItineraryService } from '../../services/itinerary.service';
|
||||||
|
|
||||||
|
const itineraryService = new ItineraryService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfo = await verifyUserToken(token);
|
||||||
|
const userId = Number(userInfo.id);
|
||||||
|
|
||||||
|
if (!userId || Number.isNaN(userId)) {
|
||||||
|
throw new ApiError(400, 'Invalid user ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: Record<string, any> = {};
|
||||||
|
if (event.body) {
|
||||||
|
try {
|
||||||
|
body = JSON.parse(event.body);
|
||||||
|
} catch {
|
||||||
|
throw new ApiError(400, 'Invalid JSON body');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const activities = Array.isArray(body.activities) ? body.activities : [];
|
||||||
|
|
||||||
|
if (!body.startDate || !body.endDate || !body.startTime || !body.endTime) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'startDate, endDate, startTime and endTime are required.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
body.startLocationAddress === undefined ||
|
||||||
|
body.startLocationAddress === null ||
|
||||||
|
body.startLocationLat === undefined ||
|
||||||
|
body.startLocationLat === null ||
|
||||||
|
body.startLocationLong === undefined ||
|
||||||
|
body.startLocationLong === null ||
|
||||||
|
body.endLocationAddress === undefined ||
|
||||||
|
body.endLocationAddress === null ||
|
||||||
|
body.endLocationLat === undefined ||
|
||||||
|
body.endLocationLat === null ||
|
||||||
|
body.endLocationLong === undefined ||
|
||||||
|
body.endLocationLong === null
|
||||||
|
) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'startLocationAddress, startLocationLat, startLocationLong, endLocationAddress, endLocationLat and endLocationLong are required.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(typeof body.startLocationAddress === 'string' &&
|
||||||
|
!body.startLocationAddress.trim()) ||
|
||||||
|
(typeof body.endLocationAddress === 'string' &&
|
||||||
|
!body.endLocationAddress.trim())
|
||||||
|
) {
|
||||||
|
throw new ApiError(400, 'Location addresses cannot be empty.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activities.length) {
|
||||||
|
throw new ApiError(400, 'At least one activity is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const activity of activities) {
|
||||||
|
const itineraryType =
|
||||||
|
typeof activity.itineraryType === 'string'
|
||||||
|
? activity.itineraryType.trim().toUpperCase().replace(/\s+/g, '_')
|
||||||
|
: 'ACTIVITY';
|
||||||
|
const isCustomItineraryType =
|
||||||
|
itineraryType === 'STAY' || itineraryType === 'FREE_TIME';
|
||||||
|
|
||||||
|
if (
|
||||||
|
!activity.modeOfTravel ||
|
||||||
|
activity.travelTimeBetweenPointsMins === undefined ||
|
||||||
|
activity.travelTimeBetweenPointsMins === null
|
||||||
|
) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'Each itinerary item must include modeOfTravel and travelTimeBetweenPointsMins.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCustomItineraryType) {
|
||||||
|
const customStartDate = activity.startDate || activity.occurenceDate;
|
||||||
|
const customEndDate = activity.endDate || activity.occurenceDate;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!customStartDate ||
|
||||||
|
!customEndDate ||
|
||||||
|
!activity.selectedStartTime ||
|
||||||
|
!activity.selectedEndTime
|
||||||
|
) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
`${itineraryType} items must include startDate, endDate, selectedStartTime and selectedEndTime.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itineraryType === 'STAY' && !activity.locationAddress) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'STAY items must include locationAddress.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activity.activityXid || !activity.venueXid || !activity.scheduleHeaderXid) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'ACTIVITY items must include activityXid, venueXid and scheduleHeaderXid.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
title: body.title,
|
||||||
|
startDate: body.startDate,
|
||||||
|
endDate: body.endDate,
|
||||||
|
startTime: body.startTime,
|
||||||
|
endTime: body.endTime,
|
||||||
|
startLocationAddress: body.startLocationAddress,
|
||||||
|
startLocationLat:
|
||||||
|
body.startLocationLat !== null && body.startLocationLat !== undefined
|
||||||
|
? Number(body.startLocationLat)
|
||||||
|
: undefined,
|
||||||
|
startLocationLong:
|
||||||
|
body.startLocationLong !== null && body.startLocationLong !== undefined
|
||||||
|
? Number(body.startLocationLong)
|
||||||
|
: undefined,
|
||||||
|
endLocationAddress: body.endLocationAddress,
|
||||||
|
endLocationLat:
|
||||||
|
body.endLocationLat !== null && body.endLocationLat !== undefined
|
||||||
|
? Number(body.endLocationLat)
|
||||||
|
: undefined,
|
||||||
|
endLocationLong:
|
||||||
|
body.endLocationLong !== null && body.endLocationLong !== undefined
|
||||||
|
? Number(body.endLocationLong)
|
||||||
|
: undefined,
|
||||||
|
activities: activities.map((activity: any) => {
|
||||||
|
const itineraryType =
|
||||||
|
typeof activity.itineraryType === 'string'
|
||||||
|
? activity.itineraryType.trim().toUpperCase().replace(/\s+/g, '_')
|
||||||
|
: 'ACTIVITY';
|
||||||
|
const isCustomItineraryType =
|
||||||
|
itineraryType === 'STAY' || itineraryType === 'FREE_TIME';
|
||||||
|
|
||||||
|
return {
|
||||||
|
activityXid:
|
||||||
|
!isCustomItineraryType &&
|
||||||
|
activity.activityXid !== undefined &&
|
||||||
|
activity.activityXid !== null
|
||||||
|
? Number(activity.activityXid)
|
||||||
|
: undefined,
|
||||||
|
venueXid:
|
||||||
|
!isCustomItineraryType &&
|
||||||
|
activity.venueXid !== undefined &&
|
||||||
|
activity.venueXid !== null
|
||||||
|
? Number(activity.venueXid)
|
||||||
|
: undefined,
|
||||||
|
scheduleHeaderXid:
|
||||||
|
!isCustomItineraryType &&
|
||||||
|
activity.scheduleHeaderXid !== undefined &&
|
||||||
|
activity.scheduleHeaderXid !== null
|
||||||
|
? Number(activity.scheduleHeaderXid)
|
||||||
|
: undefined,
|
||||||
|
modeOfTravel: activity.modeOfTravel,
|
||||||
|
travelTimeBetweenPointsMins: Number(
|
||||||
|
activity.travelTimeBetweenPointsMins,
|
||||||
|
),
|
||||||
|
kmForNextPoint:
|
||||||
|
activity.kmForNextPoint !== undefined &&
|
||||||
|
activity.kmForNextPoint !== null
|
||||||
|
? Number(activity.kmForNextPoint)
|
||||||
|
: undefined,
|
||||||
|
startDate: activity.startDate ?? activity.occurenceDate,
|
||||||
|
endDate: activity.endDate ?? activity.occurenceDate,
|
||||||
|
occurenceDate: activity.occurenceDate,
|
||||||
|
selectedStartTime: activity.selectedStartTime,
|
||||||
|
selectedEndTime: activity.selectedEndTime,
|
||||||
|
itineraryType,
|
||||||
|
paxCount:
|
||||||
|
activity.paxCount !== undefined && activity.paxCount !== null
|
||||||
|
? Number(activity.paxCount)
|
||||||
|
: undefined,
|
||||||
|
totalAmount:
|
||||||
|
activity.totalAmount !== undefined && activity.totalAmount !== null
|
||||||
|
? Number(activity.totalAmount)
|
||||||
|
: undefined,
|
||||||
|
locationLat:
|
||||||
|
activity.locationLat !== undefined && activity.locationLat !== null
|
||||||
|
? Number(activity.locationLat)
|
||||||
|
: undefined,
|
||||||
|
locationLong:
|
||||||
|
activity.locationLong !== undefined && activity.locationLong !== null
|
||||||
|
? Number(activity.locationLong)
|
||||||
|
: undefined,
|
||||||
|
locationAddress: activity.locationAddress,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
payload.activities.some(
|
||||||
|
(activity) =>
|
||||||
|
(activity.activityXid !== undefined &&
|
||||||
|
Number.isNaN(activity.activityXid)) ||
|
||||||
|
(activity.venueXid !== undefined && Number.isNaN(activity.venueXid)) ||
|
||||||
|
(activity.scheduleHeaderXid !== undefined &&
|
||||||
|
Number.isNaN(activity.scheduleHeaderXid)) ||
|
||||||
|
Number.isNaN(activity.travelTimeBetweenPointsMins) ||
|
||||||
|
(activity.kmForNextPoint !== undefined &&
|
||||||
|
Number.isNaN(activity.kmForNextPoint)) ||
|
||||||
|
(activity.paxCount !== undefined && Number.isNaN(activity.paxCount)) ||
|
||||||
|
(activity.totalAmount !== undefined &&
|
||||||
|
Number.isNaN(activity.totalAmount)) ||
|
||||||
|
(activity.locationLat !== undefined &&
|
||||||
|
Number.isNaN(activity.locationLat)) ||
|
||||||
|
(activity.locationLong !== undefined &&
|
||||||
|
Number.isNaN(activity.locationLong)),
|
||||||
|
Number.isNaN(payload.startLocationLat) ||
|
||||||
|
Number.isNaN(payload.startLocationLong) ||
|
||||||
|
Number.isNaN(payload.endLocationLat) ||
|
||||||
|
Number.isNaN(payload.endLocationLong),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
throw new ApiError(400, 'One or more numeric itinerary values are invalid.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await itineraryService.saveUserItinerary(userId, payload);
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 201,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Itinerary saved successfully',
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
95
src/modules/user/handlers/payment/createOrder.ts
Normal file
95
src/modules/user/handlers/payment/createOrder.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
|
||||||
|
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
|
||||||
|
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||||
|
import { PaymentService } from '../../services/payment.service';
|
||||||
|
|
||||||
|
const paymentService = new PaymentService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfo = await verifyUserToken(token);
|
||||||
|
const userId = Number(userInfo.id);
|
||||||
|
|
||||||
|
if (!userId || Number.isNaN(userId)) {
|
||||||
|
throw new ApiError(400, 'Invalid user ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: Record<string, any> = {};
|
||||||
|
if (event.body) {
|
||||||
|
try {
|
||||||
|
body = JSON.parse(event.body);
|
||||||
|
} catch {
|
||||||
|
throw new ApiError(400, 'Invalid JSON body');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const amount = Number(body.amount);
|
||||||
|
if (!Number.isFinite(amount) || amount <= 0) {
|
||||||
|
throw new ApiError(400, 'amount is required and must be greater than 0.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const currency =
|
||||||
|
typeof body.currency === 'string' && body.currency.trim()
|
||||||
|
? body.currency.trim().toUpperCase()
|
||||||
|
: 'INR';
|
||||||
|
|
||||||
|
const receipt =
|
||||||
|
typeof body.receipt === 'string' && body.receipt.trim()
|
||||||
|
? body.receipt.trim()
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const notes =
|
||||||
|
body.notes && typeof body.notes === 'object' && !Array.isArray(body.notes)
|
||||||
|
? body.notes
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
const itineraryHeaderXid =
|
||||||
|
body.itineraryHeaderXid !== undefined && body.itineraryHeaderXid !== null
|
||||||
|
? Number(body.itineraryHeaderXid)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (
|
||||||
|
itineraryHeaderXid !== undefined &&
|
||||||
|
(!Number.isInteger(itineraryHeaderXid) || itineraryHeaderXid <= 0)
|
||||||
|
) {
|
||||||
|
throw new ApiError(400, 'Invalid itineraryHeaderXid.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paymentService.createOrder(userId, {
|
||||||
|
amount,
|
||||||
|
currency,
|
||||||
|
receipt,
|
||||||
|
notes: {
|
||||||
|
userId,
|
||||||
|
...(itineraryHeaderXid !== undefined ? { itineraryHeaderXid } : {}),
|
||||||
|
...(notes ?? {}),
|
||||||
|
},
|
||||||
|
itineraryHeaderXid,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Order created successfully',
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
63
src/modules/user/handlers/payment/razorpayWebhook.ts
Normal file
63
src/modules/user/handlers/payment/razorpayWebhook.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import {
|
||||||
|
APIGatewayProxyEvent,
|
||||||
|
APIGatewayProxyResult,
|
||||||
|
Context,
|
||||||
|
} from 'aws-lambda';
|
||||||
|
|
||||||
|
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||||
|
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||||
|
import { PaymentService } from '../../services/payment.service';
|
||||||
|
|
||||||
|
const paymentService = new PaymentService(prismaClient);
|
||||||
|
|
||||||
|
function getHeaderValue(
|
||||||
|
headers: APIGatewayProxyEvent['headers'],
|
||||||
|
name: string,
|
||||||
|
): string | undefined {
|
||||||
|
const targetName = name.toLowerCase();
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(headers ?? {})) {
|
||||||
|
if (key.toLowerCase() === targetName) {
|
||||||
|
return typeof value === 'string' ? value : undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const signature =
|
||||||
|
getHeaderValue(event.headers, 'x-razorpay-signature')?.trim() ?? '';
|
||||||
|
|
||||||
|
if (!signature) {
|
||||||
|
throw new ApiError(400, 'Missing Razorpay webhook signature.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawBody = event.body
|
||||||
|
? event.isBase64Encoded
|
||||||
|
? Buffer.from(event.body, 'base64').toString('utf8')
|
||||||
|
: event.body
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const result = await paymentService.handleWebhook({
|
||||||
|
rawBody,
|
||||||
|
signature,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Webhook received',
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
71
src/modules/user/handlers/payment/verifyPayment.ts
Normal file
71
src/modules/user/handlers/payment/verifyPayment.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
|
||||||
|
|
||||||
|
import { prismaClient } from '../../../../common/database/prisma.lambda.service';
|
||||||
|
import { verifyUserToken } from '../../../../common/middlewares/jwt/authForUser';
|
||||||
|
import { safeHandler } from '../../../../common/utils/handlers/safeHandler';
|
||||||
|
import ApiError from '../../../../common/utils/helper/ApiError';
|
||||||
|
import { PaymentService } from '../../services/payment.service';
|
||||||
|
|
||||||
|
const paymentService = new PaymentService(prismaClient);
|
||||||
|
|
||||||
|
export const handler = safeHandler(async (
|
||||||
|
event: APIGatewayProxyEvent,
|
||||||
|
context?: Context,
|
||||||
|
): Promise<APIGatewayProxyResult> => {
|
||||||
|
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
|
||||||
|
if (!token) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'This is a protected route. Please provide a valid token.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userInfo = await verifyUserToken(token);
|
||||||
|
const userId = Number(userInfo.id);
|
||||||
|
|
||||||
|
if (!userId || Number.isNaN(userId)) {
|
||||||
|
throw new ApiError(400, 'Invalid user ID');
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: Record<string, any> = {};
|
||||||
|
if (event.body) {
|
||||||
|
try {
|
||||||
|
body = JSON.parse(event.body);
|
||||||
|
} catch {
|
||||||
|
throw new ApiError(400, 'Invalid JSON body');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentId =
|
||||||
|
typeof body.paymentId === 'string' ? body.paymentId.trim() : '';
|
||||||
|
const orderId =
|
||||||
|
typeof body.orderId === 'string' ? body.orderId.trim() : '';
|
||||||
|
const signature =
|
||||||
|
typeof body.signature === 'string' ? body.signature.trim() : '';
|
||||||
|
|
||||||
|
if (!paymentId || !orderId || !signature) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'paymentId, orderId and signature are required.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await paymentService.verifyPayment(userId, {
|
||||||
|
paymentId,
|
||||||
|
orderId,
|
||||||
|
signature,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
statusCode: 200,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
message: 'Payment verified',
|
||||||
|
data: result,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
2732
src/modules/user/services/itinerary.service.ts
Normal file
2732
src/modules/user/services/itinerary.service.ts
Normal file
File diff suppressed because it is too large
Load Diff
362
src/modules/user/services/payment.service.ts
Normal file
362
src/modules/user/services/payment.service.ts
Normal file
@@ -0,0 +1,362 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Prisma, PrismaClient } from '@prisma/client';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import Razorpay from 'razorpay';
|
||||||
|
|
||||||
|
import ApiError from '../../../common/utils/helper/ApiError';
|
||||||
|
import config from '../../../config/config';
|
||||||
|
|
||||||
|
const razorpay = new Razorpay({
|
||||||
|
key_id: config.RAZORPAY_KEY_ID,
|
||||||
|
key_secret: config.RAZORPAY_KEY_SECRET,
|
||||||
|
});
|
||||||
|
|
||||||
|
type RazorpayWebhookPayload = {
|
||||||
|
event?: string;
|
||||||
|
payload?: {
|
||||||
|
payment?: {
|
||||||
|
entity?: {
|
||||||
|
id?: string;
|
||||||
|
order_id?: string;
|
||||||
|
status?: string;
|
||||||
|
amount?: number;
|
||||||
|
currency?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
order?: {
|
||||||
|
entity?: {
|
||||||
|
id?: string;
|
||||||
|
status?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PaymentService {
|
||||||
|
constructor(private prisma: PrismaClient) {}
|
||||||
|
|
||||||
|
private signaturesMatch(expectedSignature: string, receivedSignature: string) {
|
||||||
|
const expectedBuffer = Buffer.from(expectedSignature, 'utf8');
|
||||||
|
const receivedBuffer = Buffer.from(receivedSignature, 'utf8');
|
||||||
|
|
||||||
|
if (expectedBuffer.length !== receivedBuffer.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOrder(
|
||||||
|
userXid: number,
|
||||||
|
payload: {
|
||||||
|
amount: number;
|
||||||
|
currency?: string;
|
||||||
|
receipt?: string;
|
||||||
|
notes?: Record<string, string | number | boolean | null>;
|
||||||
|
itineraryHeaderXid?: number;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
if (!process.env.RAZORPAY_KEY_ID || !process.env.RAZORPAY_KEY_SECRET) {
|
||||||
|
throw new ApiError(500, 'Razorpay credentials are not configured.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number.isFinite(payload.amount) || payload.amount <= 0) {
|
||||||
|
throw new ApiError(400, 'amount must be a positive number.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
payload.itineraryHeaderXid !== undefined &&
|
||||||
|
payload.itineraryHeaderXid !== null
|
||||||
|
) {
|
||||||
|
const itineraryHeader = await this.prisma.itineraryHeader.findFirst({
|
||||||
|
where: {
|
||||||
|
id: payload.itineraryHeaderXid,
|
||||||
|
ownerXid: userXid,
|
||||||
|
isActive: true,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!itineraryHeader) {
|
||||||
|
throw new ApiError(
|
||||||
|
404,
|
||||||
|
'Itinerary not found for the logged-in user.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const amountInPaise = Math.round(payload.amount * 100);
|
||||||
|
const receipt = payload.receipt ?? `receipt_${Date.now()}`;
|
||||||
|
const currency = payload.currency ?? 'INR';
|
||||||
|
|
||||||
|
const order = (await razorpay.orders.create({
|
||||||
|
amount: amountInPaise,
|
||||||
|
currency,
|
||||||
|
receipt,
|
||||||
|
notes: payload.notes ?? undefined,
|
||||||
|
} as any)) as any;
|
||||||
|
|
||||||
|
const paymentOrder = await this.prisma.paymentOrders.create({
|
||||||
|
data: {
|
||||||
|
userXid,
|
||||||
|
itineraryHeaderXid: payload.itineraryHeaderXid ?? null,
|
||||||
|
razorpayOrderId: order.id,
|
||||||
|
receipt: order.receipt ?? receipt,
|
||||||
|
amount: order.amount ?? amountInPaise,
|
||||||
|
currency: order.currency ?? currency,
|
||||||
|
paymentStatus: order.status ?? 'created',
|
||||||
|
notes: payload.notes
|
||||||
|
? (payload.notes as Prisma.InputJsonValue)
|
||||||
|
: null,
|
||||||
|
isActive: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
paymentOrderId: paymentOrder.id,
|
||||||
|
orderId: order.id,
|
||||||
|
amount: paymentOrder.amount,
|
||||||
|
currency: paymentOrder.currency,
|
||||||
|
receipt: paymentOrder.receipt,
|
||||||
|
status: paymentOrder.paymentStatus,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyPayment(
|
||||||
|
userXid: number,
|
||||||
|
payload: {
|
||||||
|
paymentId: string;
|
||||||
|
orderId: string;
|
||||||
|
signature: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
if (!process.env.RAZORPAY_KEY_SECRET) {
|
||||||
|
throw new ApiError(500, 'Razorpay credentials are not configured.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentId = payload.paymentId?.trim();
|
||||||
|
const orderId = payload.orderId?.trim();
|
||||||
|
const signature = payload.signature?.trim();
|
||||||
|
|
||||||
|
if (!paymentId || !orderId || !signature) {
|
||||||
|
throw new ApiError(
|
||||||
|
400,
|
||||||
|
'paymentId, orderId and signature are required.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const generatedSignature = crypto
|
||||||
|
.createHmac('sha256', process.env.RAZORPAY_KEY_SECRET)
|
||||||
|
.update(`${orderId}|${paymentId}`)
|
||||||
|
.digest('hex');
|
||||||
|
|
||||||
|
if (generatedSignature !== signature) {
|
||||||
|
throw new ApiError(400, 'Invalid signature.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentOrder = await this.prisma.paymentOrders.findFirst({
|
||||||
|
where: {
|
||||||
|
razorpayOrderId: orderId,
|
||||||
|
userXid,
|
||||||
|
isActive: true,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!paymentOrder) {
|
||||||
|
throw new ApiError(404, 'Payment order not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
paymentOrder.paymentStatus === 'paid' &&
|
||||||
|
paymentOrder.razorpayPaymentId === paymentId
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
paymentOrderId: paymentOrder.id,
|
||||||
|
orderId: paymentOrder.razorpayOrderId,
|
||||||
|
paymentId: paymentOrder.razorpayPaymentId,
|
||||||
|
status: paymentOrder.paymentStatus,
|
||||||
|
verifiedAt: paymentOrder.verifiedAt,
|
||||||
|
paidAt: paymentOrder.paidAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedPaymentOrder = await this.prisma.paymentOrders.update({
|
||||||
|
where: {
|
||||||
|
id: paymentOrder.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
razorpayPaymentId: paymentId,
|
||||||
|
razorpaySignature: signature,
|
||||||
|
paymentStatus: 'paid',
|
||||||
|
verifiedAt: new Date(),
|
||||||
|
paidAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
paymentOrderId: updatedPaymentOrder.id,
|
||||||
|
orderId: updatedPaymentOrder.razorpayOrderId,
|
||||||
|
paymentId: updatedPaymentOrder.razorpayPaymentId,
|
||||||
|
status: updatedPaymentOrder.paymentStatus,
|
||||||
|
verifiedAt: updatedPaymentOrder.verifiedAt,
|
||||||
|
paidAt: updatedPaymentOrder.paidAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleWebhook(payload: {
|
||||||
|
rawBody: string;
|
||||||
|
signature: string;
|
||||||
|
}) {
|
||||||
|
const webhookSecret = process.env.RAZORPAY_WEBHOOK_SECRET?.trim();
|
||||||
|
|
||||||
|
if (!webhookSecret) {
|
||||||
|
throw new ApiError(
|
||||||
|
500,
|
||||||
|
'Razorpay webhook secret is not configured.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawBody = payload.rawBody;
|
||||||
|
const signature = payload.signature?.trim();
|
||||||
|
|
||||||
|
if (!rawBody) {
|
||||||
|
throw new ApiError(400, 'Webhook body is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!signature) {
|
||||||
|
throw new ApiError(400, 'Razorpay webhook signature is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const generatedSignature = crypto
|
||||||
|
.createHmac('sha256', webhookSecret)
|
||||||
|
.update(rawBody, 'utf8')
|
||||||
|
.digest('hex');
|
||||||
|
|
||||||
|
if (!this.signaturesMatch(generatedSignature, signature)) {
|
||||||
|
throw new ApiError(400, 'Invalid webhook signature.');
|
||||||
|
}
|
||||||
|
|
||||||
|
let body: RazorpayWebhookPayload;
|
||||||
|
try {
|
||||||
|
body = JSON.parse(rawBody) as RazorpayWebhookPayload;
|
||||||
|
} catch {
|
||||||
|
throw new ApiError(400, 'Invalid webhook JSON body.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventType = body.event?.trim();
|
||||||
|
if (!eventType) {
|
||||||
|
throw new ApiError(400, 'Webhook event type is missing.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentEntity = body.payload?.payment?.entity;
|
||||||
|
const orderEntity = body.payload?.order?.entity;
|
||||||
|
const razorpayOrderId =
|
||||||
|
paymentEntity?.order_id?.trim() || orderEntity?.id?.trim();
|
||||||
|
const razorpayPaymentId = paymentEntity?.id?.trim();
|
||||||
|
|
||||||
|
if (!razorpayOrderId) {
|
||||||
|
throw new ApiError(400, 'Webhook payload is missing Razorpay order id.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const paymentOrder = await this.prisma.paymentOrders.findFirst({
|
||||||
|
where: {
|
||||||
|
razorpayOrderId,
|
||||||
|
isActive: true,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!paymentOrder) {
|
||||||
|
return {
|
||||||
|
eventType,
|
||||||
|
processed: false,
|
||||||
|
message: 'Payment order not found for webhook payload.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
eventType === 'payment.captured' &&
|
||||||
|
paymentOrder.paymentStatus === 'paid' &&
|
||||||
|
paymentOrder.razorpayPaymentId === razorpayPaymentId
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
paymentOrderId: paymentOrder.id,
|
||||||
|
orderId: paymentOrder.razorpayOrderId,
|
||||||
|
paymentId: paymentOrder.razorpayPaymentId,
|
||||||
|
status: paymentOrder.paymentStatus,
|
||||||
|
verifiedAt: paymentOrder.verifiedAt,
|
||||||
|
paidAt: paymentOrder.paidAt,
|
||||||
|
eventType,
|
||||||
|
processed: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventType === 'payment.captured' || eventType === 'order.paid') {
|
||||||
|
const updatedPaymentOrder = await this.prisma.paymentOrders.update({
|
||||||
|
where: {
|
||||||
|
id: paymentOrder.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...(razorpayPaymentId
|
||||||
|
? { razorpayPaymentId }
|
||||||
|
: {}),
|
||||||
|
paymentStatus: 'paid',
|
||||||
|
verifiedAt: new Date(),
|
||||||
|
paidAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
paymentOrderId: updatedPaymentOrder.id,
|
||||||
|
orderId: updatedPaymentOrder.razorpayOrderId,
|
||||||
|
paymentId: updatedPaymentOrder.razorpayPaymentId,
|
||||||
|
status: updatedPaymentOrder.paymentStatus,
|
||||||
|
verifiedAt: updatedPaymentOrder.verifiedAt,
|
||||||
|
paidAt: updatedPaymentOrder.paidAt,
|
||||||
|
eventType,
|
||||||
|
processed: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventType === 'payment.failed') {
|
||||||
|
const updatedPaymentOrder = await this.prisma.paymentOrders.update({
|
||||||
|
where: {
|
||||||
|
id: paymentOrder.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...(razorpayPaymentId
|
||||||
|
? { razorpayPaymentId }
|
||||||
|
: {}),
|
||||||
|
paymentStatus: 'failed',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
paymentOrderId: updatedPaymentOrder.id,
|
||||||
|
orderId: updatedPaymentOrder.razorpayOrderId,
|
||||||
|
paymentId: updatedPaymentOrder.razorpayPaymentId,
|
||||||
|
status: updatedPaymentOrder.paymentStatus,
|
||||||
|
verifiedAt: updatedPaymentOrder.verifiedAt,
|
||||||
|
paidAt: updatedPaymentOrder.paidAt,
|
||||||
|
eventType,
|
||||||
|
processed: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
paymentOrderId: paymentOrder.id,
|
||||||
|
orderId: paymentOrder.razorpayOrderId,
|
||||||
|
paymentId: paymentOrder.razorpayPaymentId,
|
||||||
|
status: paymentOrder.paymentStatus,
|
||||||
|
verifiedAt: paymentOrder.verifiedAt,
|
||||||
|
paidAt: paymentOrder.paidAt,
|
||||||
|
eventType,
|
||||||
|
processed: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1148,7 +1148,9 @@ export class UserService {
|
|||||||
// 6️⃣ RANDOM ACTIVITIES (5 ONLY - SIMPLE)
|
// 6️⃣ RANDOM ACTIVITIES (5 ONLY - SIMPLE)
|
||||||
// =====================================================
|
// =====================================================
|
||||||
|
|
||||||
const totalActiveCount = await tx.activities.count({
|
let randomActivities: any[] = [];
|
||||||
|
|
||||||
|
const eligibleRandomActivityIds = await tx.activities.findMany({
|
||||||
where: {
|
where: {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
@@ -1157,53 +1159,44 @@ export class UserService {
|
|||||||
id: {
|
id: {
|
||||||
notIn: allUserExcludedActivityIds.length
|
notIn: allUserExcludedActivityIds.length
|
||||||
? allUserExcludedActivityIds
|
? allUserExcludedActivityIds
|
||||||
: [-1], // prevent empty notIn issue
|
: [-1],
|
||||||
},
|
},
|
||||||
|
ActivitiesMedia: {
|
||||||
|
some: {
|
||||||
|
isActive: true,
|
||||||
|
isCoverImage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let randomActivities: any[] = [];
|
if (eligibleRandomActivityIds.length > 0) {
|
||||||
|
const takeCount = Math.min(5, eligibleRandomActivityIds.length);
|
||||||
|
const selectedIds = eligibleRandomActivityIds
|
||||||
|
.sort(() => Math.random() - 0.5)
|
||||||
|
.slice(0, takeCount)
|
||||||
|
.map((activity) => activity.id);
|
||||||
|
|
||||||
if (totalActiveCount > 0) {
|
const randomFetched = await tx.activities.findMany({
|
||||||
const takeCount = Math.min(5, totalActiveCount);
|
where: {
|
||||||
|
id: { in: selectedIds },
|
||||||
const randomOffsets = new Set<number>();
|
},
|
||||||
while (randomOffsets.size < takeCount) {
|
select: {
|
||||||
randomOffsets.add(Math.floor(Math.random() * totalActiveCount));
|
id: true,
|
||||||
}
|
activityTitle: true,
|
||||||
|
ActivitiesMedia: {
|
||||||
const randomFetched = await Promise.all(
|
where: { isActive: true, isCoverImage: true },
|
||||||
Array.from(randomOffsets).map((offset) =>
|
orderBy: { displayOrder: 'asc' },
|
||||||
tx.activities.findFirst({
|
take: 1,
|
||||||
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], // prevent empty notIn issue
|
|
||||||
},
|
|
||||||
},
|
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
mediaFileName: true,
|
||||||
activityTitle: true,
|
|
||||||
ActivitiesMedia: {
|
|
||||||
where: { isActive: true, isCoverImage: true },
|
|
||||||
orderBy: { displayOrder: 'asc' },
|
|
||||||
take: 1,
|
|
||||||
select: {
|
|
||||||
mediaFileName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
),
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
randomActivities = await Promise.all(
|
randomActivities = await Promise.all(
|
||||||
randomFetched
|
randomFetched
|
||||||
@@ -1817,7 +1810,9 @@ export class UserService {
|
|||||||
RANDOM ACTIVITIES (5 COVER IMAGES)
|
RANDOM ACTIVITIES (5 COVER IMAGES)
|
||||||
===================================================== */
|
===================================================== */
|
||||||
|
|
||||||
const totalActiveCount = await tx.activities.count({
|
let randomActivities: any[] = [];
|
||||||
|
|
||||||
|
const eligibleRandomActivityIds = await tx.activities.findMany({
|
||||||
where: {
|
where: {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
@@ -1826,50 +1821,43 @@ export class UserService {
|
|||||||
id: {
|
id: {
|
||||||
notIn: safeExcludedIds,
|
notIn: safeExcludedIds,
|
||||||
},
|
},
|
||||||
|
ActivitiesMedia: {
|
||||||
|
some: {
|
||||||
|
isActive: true,
|
||||||
|
isCoverImage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
...excludeUserInterestCondition,
|
...excludeUserInterestCondition,
|
||||||
},
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let randomActivities: any[] = [];
|
if (eligibleRandomActivityIds.length > 0) {
|
||||||
|
const takeCount = Math.min(5, eligibleRandomActivityIds.length);
|
||||||
|
const selectedIds = eligibleRandomActivityIds
|
||||||
|
.sort(() => Math.random() - 0.5)
|
||||||
|
.slice(0, takeCount)
|
||||||
|
.map((activity) => activity.id);
|
||||||
|
|
||||||
if (totalActiveCount > 0) {
|
const randomFetched = await tx.activities.findMany({
|
||||||
const takeCount = Math.min(5, totalActiveCount);
|
where: {
|
||||||
|
id: { in: selectedIds },
|
||||||
const randomOffsets = new Set<number>();
|
},
|
||||||
|
select: {
|
||||||
while (randomOffsets.size < takeCount) {
|
id: true,
|
||||||
randomOffsets.add(Math.floor(Math.random() * totalActiveCount));
|
activityTitle: true,
|
||||||
}
|
ActivitiesMedia: {
|
||||||
|
where: { isActive: true, isCoverImage: true },
|
||||||
const randomFetched = await Promise.all(
|
orderBy: { displayOrder: 'asc' },
|
||||||
Array.from(randomOffsets).map((offset) =>
|
take: 1,
|
||||||
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: safeExcludedIds,
|
|
||||||
},
|
|
||||||
...excludeUserInterestCondition,
|
|
||||||
},
|
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
mediaFileName: true,
|
||||||
activityTitle: true,
|
|
||||||
ActivitiesMedia: {
|
|
||||||
where: { isActive: true, isCoverImage: true },
|
|
||||||
orderBy: { displayOrder: 'asc' },
|
|
||||||
take: 1,
|
|
||||||
select: {
|
|
||||||
mediaFileName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
),
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
randomActivities = await Promise.all(
|
randomActivities = await Promise.all(
|
||||||
randomFetched
|
randomFetched
|
||||||
@@ -2863,6 +2851,111 @@ export class UserService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async searchConnectionPeople(userXid: number, searchQuery?: string) {
|
||||||
|
const userConnectionDetails = await this.prisma.connectDetails.findMany({
|
||||||
|
where: {
|
||||||
|
userXid,
|
||||||
|
isActive: true,
|
||||||
|
deletedAt: null,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
schoolCompanyXid: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const schoolCompanyXids = [
|
||||||
|
...new Set(userConnectionDetails.map((item) => item.schoolCompanyXid)),
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!schoolCompanyXids.length) {
|
||||||
|
return {
|
||||||
|
count: 0,
|
||||||
|
people: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const trimmedSearchQuery = searchQuery?.trim() ?? '';
|
||||||
|
|
||||||
|
const connectionPeople = await this.prisma.connectDetails.findMany({
|
||||||
|
where: {
|
||||||
|
isActive: true,
|
||||||
|
deletedAt: null,
|
||||||
|
schoolCompanyXid: { in: schoolCompanyXids },
|
||||||
|
userXid: { not: userXid },
|
||||||
|
user: {
|
||||||
|
isActive: true,
|
||||||
|
deletedAt: null,
|
||||||
|
...(trimmedSearchQuery
|
||||||
|
? {
|
||||||
|
OR: [
|
||||||
|
{
|
||||||
|
firstName: {
|
||||||
|
contains: trimmedSearchQuery,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
lastName: {
|
||||||
|
contains: trimmedSearchQuery,
|
||||||
|
mode: 'insensitive',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
distinct: ['userXid'],
|
||||||
|
orderBy: {
|
||||||
|
createdAt: 'desc',
|
||||||
|
},
|
||||||
|
take: 10,
|
||||||
|
select: {
|
||||||
|
userXid: true,
|
||||||
|
schoolCompany: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
schoolCompanyName: true,
|
||||||
|
isSchool: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
profileImage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const people = await Promise.all(
|
||||||
|
connectionPeople.map(async (item) => {
|
||||||
|
const firstName = item.user.firstName?.trim() ?? '';
|
||||||
|
const lastName = item.user.lastName?.trim() ?? '';
|
||||||
|
const fullName = `${firstName} ${lastName}`.trim();
|
||||||
|
|
||||||
|
return {
|
||||||
|
userXid: item.user.id,
|
||||||
|
fullName,
|
||||||
|
firstName: item.user.firstName,
|
||||||
|
lastName: item.user.lastName,
|
||||||
|
profileImage: item.user.profileImage,
|
||||||
|
profileImagePresignedUrl: await attachPresignedUrl(
|
||||||
|
item.user.profileImage,
|
||||||
|
),
|
||||||
|
schoolCompany: item.schoolCompany,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
count: people.length,
|
||||||
|
people,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async searchSchoolsAndCompanies(searchQuery: string, isSchool: boolean) {
|
async searchSchoolsAndCompanies(searchQuery: string, isSchool: boolean) {
|
||||||
if (!searchQuery) {
|
if (!searchQuery) {
|
||||||
throw new ApiError(
|
throw new ApiError(
|
||||||
@@ -3529,7 +3622,9 @@ export class UserService {
|
|||||||
RANDOM ACTIVITIES FROM CONNECTION USERS (5 COVER IMAGES)
|
RANDOM ACTIVITIES FROM CONNECTION USERS (5 COVER IMAGES)
|
||||||
===================================================== */
|
===================================================== */
|
||||||
|
|
||||||
const totalActiveCount = await tx.activities.count({
|
let randomActivities: any[] = [];
|
||||||
|
|
||||||
|
const eligibleRandomActivityIds = await tx.activities.findMany({
|
||||||
where: {
|
where: {
|
||||||
id: { in: connectionActivityIds },
|
id: { in: connectionActivityIds },
|
||||||
isActive: true,
|
isActive: true,
|
||||||
@@ -3537,47 +3632,42 @@ export class UserService {
|
|||||||
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
activityTypeXid: { in: activityTypeIds },
|
activityTypeXid: { in: activityTypeIds },
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
|
ActivitiesMedia: {
|
||||||
|
some: {
|
||||||
|
isActive: true,
|
||||||
|
isCoverImage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let randomActivities: any[] = [];
|
if (eligibleRandomActivityIds.length > 0) {
|
||||||
|
const takeCount = Math.min(5, eligibleRandomActivityIds.length);
|
||||||
|
const selectedIds = eligibleRandomActivityIds
|
||||||
|
.sort(() => Math.random() - 0.5)
|
||||||
|
.slice(0, takeCount)
|
||||||
|
.map((activity) => activity.id);
|
||||||
|
|
||||||
if (totalActiveCount > 0) {
|
const randomFetched = await tx.activities.findMany({
|
||||||
const takeCount = Math.min(5, totalActiveCount);
|
where: {
|
||||||
|
id: { in: selectedIds },
|
||||||
const randomOffsets = new Set<number>();
|
},
|
||||||
|
select: {
|
||||||
while (randomOffsets.size < takeCount) {
|
id: true,
|
||||||
randomOffsets.add(Math.floor(Math.random() * totalActiveCount));
|
activityTitle: true,
|
||||||
}
|
ActivitiesMedia: {
|
||||||
|
where: { isActive: true, isCoverImage: true },
|
||||||
const randomFetched = await Promise.all(
|
orderBy: { displayOrder: 'asc' },
|
||||||
Array.from(randomOffsets).map((offset) =>
|
take: 1,
|
||||||
tx.activities.findFirst({
|
|
||||||
skip: offset,
|
|
||||||
where: {
|
|
||||||
id: { in: connectionActivityIds },
|
|
||||||
isActive: true,
|
|
||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
|
||||||
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
|
||||||
activityTypeXid: { in: activityTypeIds },
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
mediaFileName: true,
|
||||||
activityTitle: true,
|
|
||||||
ActivitiesMedia: {
|
|
||||||
where: { isActive: true, isCoverImage: true },
|
|
||||||
orderBy: { displayOrder: "asc" },
|
|
||||||
take: 1,
|
|
||||||
select: {
|
|
||||||
mediaFileName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}),
|
},
|
||||||
),
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
randomActivities = await Promise.all(
|
randomActivities = await Promise.all(
|
||||||
randomFetched
|
randomFetched
|
||||||
@@ -4046,56 +4136,54 @@ export class UserService {
|
|||||||
async getFiveRandomActivities() {
|
async getFiveRandomActivities() {
|
||||||
return await this.prisma.$transaction(async (tx) => {
|
return await this.prisma.$transaction(async (tx) => {
|
||||||
|
|
||||||
// Step 1: Count eligible activities
|
const eligibleRandomActivityIds = await tx.activities.findMany({
|
||||||
const totalCount = await tx.activities.count({
|
|
||||||
where: {
|
where: {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
|
ActivitiesMedia: {
|
||||||
|
some: {
|
||||||
|
isActive: true,
|
||||||
|
isCoverImage: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (totalCount === 0) return [];
|
if (eligibleRandomActivityIds.length === 0) return [];
|
||||||
|
|
||||||
// Step 2: Generate 5 unique random offsets
|
const takeCount = Math.min(5, eligibleRandomActivityIds.length);
|
||||||
const takeCount = Math.min(5, totalCount);
|
const selectedIds = eligibleRandomActivityIds
|
||||||
const randomOffsets = new Set<number>();
|
.sort(() => Math.random() - 0.5)
|
||||||
|
.slice(0, takeCount)
|
||||||
|
.map((activity) => activity.id);
|
||||||
|
|
||||||
while (randomOffsets.size < takeCount) {
|
const activities = await tx.activities.findMany({
|
||||||
randomOffsets.add(Math.floor(Math.random() * totalCount));
|
where: {
|
||||||
}
|
id: { in: selectedIds },
|
||||||
|
},
|
||||||
// Step 3: Fetch activities using skip (efficient for small limit like 5)
|
select: {
|
||||||
const activities = await Promise.all(
|
id: true,
|
||||||
Array.from(randomOffsets).map((offset) =>
|
activityTitle: true,
|
||||||
tx.activities.findFirst({
|
ActivitiesMedia: {
|
||||||
skip: offset,
|
|
||||||
where: {
|
where: {
|
||||||
isActive: true,
|
isActive: true,
|
||||||
activityInternalStatus: ACTIVITY_INTERNAL_STATUS.ACTIVITY_LISTED,
|
isCoverImage: true,
|
||||||
amInternalStatus: ACTIVITY_AM_INTERNAL_STATUS.ACTIVITY_LISTED,
|
|
||||||
deletedAt: null,
|
|
||||||
},
|
},
|
||||||
|
orderBy: {
|
||||||
|
displayOrder: 'asc',
|
||||||
|
},
|
||||||
|
take: 1,
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
mediaFileName: true,
|
||||||
activityTitle: true,
|
|
||||||
ActivitiesMedia: {
|
|
||||||
where: {
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
displayOrder: 'asc',
|
|
||||||
},
|
|
||||||
take: 1,
|
|
||||||
select: {
|
|
||||||
mediaFileName: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
},
|
||||||
)
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
// Step 4: Attach presigned URLs
|
// Step 4: Attach presigned URLs
|
||||||
const result = await Promise.all(
|
const result = await Promise.all(
|
||||||
@@ -4138,7 +4226,7 @@ export class UserService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const existing = await this.prisma.userBucketInterested.findFirst({
|
const existing = await this.prisma.userBucketInterested.findFirst({
|
||||||
where: { userXid, activityXid },
|
where: { userXid, activityXid, isActive: true },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
@@ -4322,4 +4410,4 @@ export class UserService {
|
|||||||
oneDay,
|
oneDay,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user