55 Commits

Author SHA1 Message Date
ab9e02972e Refactor hostCompanyDetails validation schema to make address and location fields optional; update login handler to remove password comparison logic; enhance host service to include user status check and return specific user fields; clean up profile completion logic in MinglarService. 2025-12-04 20:01:09 +05:30
33b330a15b Merge branch 'pqq-optimized-logic' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into pqq-optimized-logic 2025-12-04 18:40:44 +05:30
d898dcd8ff Add S3 document deletion functionality in submitCompanyDetails handler; update host service to return activity ID with sorted categories; modify PQP details retrieval to use new admin host token for authentication. 2025-12-04 18:40:41 +05:30
paritosh18
ff18fcbf9f Merge branch 'pqq-optimized-logic' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into pqq-optimized-logic 2025-12-04 11:54:12 +05:30
paritosh18
a872ed89e4 Enhance MinglarService: Add id field to selection in PQQ data retrieval and ensure activityPqqHeaderId is set at the category level for better data integrity. 2025-12-04 11:53:37 +05:30
759eeb298c Update sorting logic for categories and subcategories to ensure proper order in responses. 2025-12-04 08:33:21 +05:30
1b72e65a71 Enhance PQQ data retrieval: Add comments and pqqAnswerXid fields to the selection in HostService and MinglarService. Update PQQAnswers structure to include all answer options for questions, improving data handling in responses. 2025-12-03 19:43:22 +05:30
4a7e5fbb1e Add PQQ functionality: Introduce new endpoints for creating activities and submitting answers, along with updates to the Minglar service for retrieving PQQ details. Update serverless configuration to include new function files. 2025-12-03 19:21:21 +05:30
ca5936d0db Update referencedBy field in HostService to default to null if undefined 2025-12-03 14:56:15 +05:30
c0d607a321 Add optional referencedBy field to hostCompanyDetails validation schema and update HostService to handle referencedBy data 2025-12-03 14:41:00 +05:30
1d684b7de6 Add referencedBy field to HostHeader model and update import paths for various handlers 2025-12-03 14:34:53 +05:30
16b16ac7ca Refactor import path for verifyMinglarAdminHostToken and update selection fields in getAllPQQQuesAndSubmittedAns method 2025-12-03 13:56:39 +05:30
0e0c63e31a path updated for the renamed file 2025-12-03 13:49:56 +05:30
822b425c1d Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayank 2025-12-03 13:43:41 +05:30
42e2d7a579 taking the activity xid in get all submited ques ans 2025-12-03 13:43:30 +05:30
paritosh18
78f49b35dd Update API path for retrieving all PQQ questions and answers for AM 2025-12-03 13:37:01 +05:30
paritosh18
930ae708a1 Merge branch 'paritosh-main1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into sprint1 2025-12-03 13:20:38 +05:30
paritosh18
8fb3f16d18 Add endpoint to retrieve all PQQ questions and answers for AM 2025-12-03 13:20:21 +05:30
3d6226ddac added search 2025-12-03 13:18:28 +05:30
38c616a7af making entries in host track 2025-12-03 12:44:07 +05:30
c6ab3e57c0 fixed the condition of submit pq for review 2025-12-02 20:19:03 +05:30
paritosh18
776c03911e Merge branch 'paritosh-main1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into sprint1 2025-12-02 20:11:19 +05:30
paritosh18
781058c443 Implement pagination for host activity retrieval in HostService and MinglarService 2025-12-02 20:10:10 +05:30
3b723e5d1f made accept pq by am api 2025-12-02 20:09:42 +05:30
paritosh18
56ebf44d37 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh-main1 2025-12-02 19:58:52 +05:30
4f8274adb9 Add pinCode field to location selection in MinglarService 2025-12-02 19:00:30 +05:30
39f182b8e9 sending city state abd country in getbyidAM 2025-12-02 18:59:06 +05:30
d9f7cd9a0f fixed the optional chaining 2025-12-02 18:08:39 +05:30
4772c320ba Enhance HostService and MinglarService to include account manager details and process profile images. Updated getAllHostActivity to retrieve account manager information and generate presigned URLs for media and profile images. Refactored media processing logic for improved clarity and consistency. 2025-12-02 17:53:19 +05:30
paritosh18
f19f5e46c4 Remove commented-out validation for allowed titles in addPQQSuggestion handler 2025-12-02 17:33:30 +05:30
paritosh18
156aed2429 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh-main1 2025-12-02 17:27:57 +05:30
paritosh18
fb77111e34 Refactor payment details handling: remove IFSC code validation, add currencyXid to DTO, and implement bank branch lookup for IFSC code retrieval. 2025-12-02 17:27:36 +05:30
6b673a173d Refactor host functions and constants for improved clarity and functionality. Renamed 'getAllActivityType' to 'prePopulateNewActivity' and updated its path. Added 'submitPQQForReview' handler for submitting PQQ for review. Enhanced error handling and response structure in 'submitPQQ_Answer' and 'submitPQQForReview' methods. Updated constants to standardize PQQ-related statuses. Improved S3 file handling logic in various handlers. 2025-12-02 17:24:01 +05:30
3c4b0db39f Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayank 2025-12-02 13:42:27 +05:30
e72c260b18 Add ActivityTrack model to schema and update User and Activities models to include activityTracks relation. Modify seed data to reflect new interest names and activity types. Implement activity reference number generation in HostService for activity creation. 2025-12-02 13:42:14 +05:30
paritosh18
9a777eb3f9 Refactor addOrUpdateCompanyDetails method in HostService to streamline host status handling and improve document management logic for both new and existing companies. 2025-12-02 13:40:41 +05:30
paritosh18
c3f0a1d82a Merge branch 'paritosh' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-02 13:19:16 +05:30
3f921febe0 Refactor host status determination logic in HostService to handle various submission cases, including updates and drafts. Update host suggestion review process to only mark suggestions as reviewed when not in draft state. 2025-12-01 20:25:23 +05:30
paritosh18
4bc5eb8d4d Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-01 19:56:43 +05:30
f3f0b2a81b Refactor HOST_SUGGESTION_TITLES to comments in minglar.constant.ts, update HostService to filter HostSuggestion by active and reviewed status, and modify addSuggestion handler to remove title validation against HOST_SUGGESTION_TITLES. Enhance MinglarService to include profile image handling and add city, country, and state selections in user queries. 2025-12-01 19:56:21 +05:30
paritosh18
6f0cdb4e0a Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-01 17:58:14 +05:30
ce9c8174d8 Merge branch 'paritosh' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into mayank 2025-12-01 17:57:45 +05:30
c72e757bf3 Refactor Minglar admin functions: removed deprecated acceptHostApplicationMinglar handler, added editAgreementDetailsAndAccept handler, and updated related service methods for improved agreement processing. Cleaned up schema by removing isSubsidairy field and adjusted file handling logic in submitCompanyDetails. Enhanced suggestion handling by including isParent flag in addSuggestion. Updated host retrieval logic in getStepper. 2025-12-01 17:57:08 +05:30
paritosh18
6aaf49bf72 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-01 17:07:59 +05:30
paritosh18
1b31ca4a83 Implement pagination in getAllOnboardingHostApplications handler and update MinglarService to support pagination options. Enhance response structure to include total count of applications. 2025-12-01 17:05:08 +05:30
140f70615c Added adminStatusInternal field to the MinglarService for enhanced data handling. 2025-12-01 15:48:42 +05:30
32f4c7ce42 added filter in get host am api 2025-12-01 15:43:06 +05:30
paritosh18
067d1a1f1b Merge branch 'mayank' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-01 13:45:51 +05:30
paritosh18
470298a3fb Add search functionality to getAllOnboardingHostApplications and getAllOnboardingNewApplications handlers; update MinglarService to support search queries 2025-12-01 13:45:39 +05:30
1d7d0749b3 Refactor authentication: replaced instances of verifyHostToken with verifyMinglarAdminHostToken in prepopulate handlers for improved security and consistency. 2025-12-01 13:42:10 +05:30
paritosh18
d3fb1c85fb Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-01 13:32:29 +05:30
e723e680ab sending the internal admin status to frontend 2025-12-01 13:32:09 +05:30
paritosh18
0e50b8b187 Merge branch 'sprint1' of http://git.wdipl.com/Mayank.Mishra/MinglarBackendNestJS into paritosh 2025-12-01 13:29:46 +05:30
paritosh18
bbe725dd9e Update HTTP methods to PATCH for rejectHostApplication endpoints and implement pagination in getAllInvitationDetails and getAllInvitedCoadminAndAM handlers 2025-12-01 13:29:10 +05:30
e5861654e9 Enhance company types management: updated schema to include display order and relationships, modified validation to use company type XID, and seeded initial company types data. Updated services to reflect new structure and ensure proper data handling. 2025-12-01 13:26:06 +05:30
67 changed files with 3075 additions and 1143 deletions

View File

@@ -66,6 +66,7 @@ model User {
friendOf Friends[] @relation("FriendUser") friendOf Friends[] @relation("FriendUser")
userAddressDetails UserAddressDetails[] userAddressDetails UserAddressDetails[]
userDocuments UserDocuments[] userDocuments UserDocuments[]
activityTracks ActivityTrack[]
@@map("users") @@map("users")
@@schema("usr") @@schema("usr")
@@ -405,12 +406,15 @@ model FoodCuisines {
} }
model CompanyTypes { model CompanyTypes {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
companyTypeName String @unique @map("company_type_name") @db.VarChar(30) companyTypeName String @unique @map("company_type_name") @db.VarChar(100)
isActive Boolean @default(true) @map("is_active") displayOrder Int @map("display_order")
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")
deletedAt DateTime? @map("deleted_at")
hostHeaders HostHeader[]
hostParents HostParent[]
@@map("company_types") @@map("company_types")
@@schema("mst") @@schema("mst")
@@ -664,12 +668,13 @@ model HostHeader {
panNumber String? @map("pan_number") @db.VarChar(30) panNumber String? @map("pan_number") @db.VarChar(30)
gstNumber String? @map("gst_number") @db.VarChar(30) gstNumber String? @map("gst_number") @db.VarChar(30)
formationDate DateTime? @map("formation_date") formationDate DateTime? @map("formation_date")
companyType String? @map("company_type") @db.VarChar(30) companyTypeXid Int? @map("company_type_xid")
websiteUrl String? @map("website_url") @db.VarChar(50) companyTypes CompanyTypes? @relation(fields: [companyTypeXid], references: [id], onDelete: Restrict)
instagramUrl String? @map("instagram_url") @db.VarChar(80) websiteUrl String? @map("website_url") @db.VarChar(250)
facebookUrl String? @map("facebook_url") @db.VarChar(80) instagramUrl String? @map("instagram_url") @db.VarChar(250)
linkedinUrl String? @map("linkedin_url") @db.VarChar(80) facebookUrl String? @map("facebook_url") @db.VarChar(250)
twitterUrl String? @map("twitter_url") @db.VarChar(80) linkedinUrl String? @map("linkedin_url") @db.VarChar(250)
twitterUrl String? @map("twitter_url") @db.VarChar(250)
currencyXid Int? @map("currency_xid") currencyXid Int? @map("currency_xid")
currencies Currencies? @relation(fields: [currencyXid], references: [id], onDelete: Restrict) currencies Currencies? @relation(fields: [currencyXid], references: [id], onDelete: Restrict)
stepper Int? @default(1) @map("stepper") stepper Int? @default(1) @map("stepper")
@@ -691,6 +696,7 @@ model HostHeader {
amountPerBooking Int? @map("amount_per_booking") amountPerBooking Int? @map("amount_per_booking")
payoutDurationNum Int? @map("payout_duration_num") payoutDurationNum Int? @map("payout_duration_num")
payoutDurationFrequency String? @map("payout_duration_frequency") @db.VarChar(20) payoutDurationFrequency String? @map("payout_duration_frequency") @db.VarChar(20)
referencedBy String? @default("null") @map("referenced_by") @db.VarChar(100)
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")
@@ -780,17 +786,17 @@ model HostParent {
countries Countries? @relation(fields: [countryXid], references: [id], onDelete: Restrict) countries Countries? @relation(fields: [countryXid], references: [id], onDelete: Restrict)
pinCode String? @map("pin_code") @db.VarChar(30) pinCode String? @map("pin_code") @db.VarChar(30)
logoPath String? @map("logo_path") @db.VarChar(400) logoPath String? @map("logo_path") @db.VarChar(400)
isSubsidairy Boolean @default(false) @map("is_subsidairy")
registrationNumber String? @map("registration_number") @db.VarChar(30) registrationNumber String? @map("registration_number") @db.VarChar(30)
panNumber String? @map("pan_number") @db.VarChar(30) panNumber String? @map("pan_number") @db.VarChar(30)
gstNumber String? @map("gst_number") @db.VarChar(30) gstNumber String? @map("gst_number") @db.VarChar(30)
formationDate DateTime? @map("formation_date") formationDate DateTime? @map("formation_date")
companyType String? @map("company_type") @db.VarChar(30) companyTypeXid Int? @map("company_type_xid")
websiteUrl String? @map("website_url") @db.VarChar(80) companyTypes CompanyTypes? @relation(fields: [companyTypeXid], references: [id], onDelete: Restrict)
instagramUrl String? @map("instagram_url") @db.VarChar(80) websiteUrl String? @map("website_url") @db.VarChar(250)
facebookUrl String? @map("facebook_url") @db.VarChar(80) instagramUrl String? @map("instagram_url") @db.VarChar(250)
linkedinUrl String? @map("linkedin_url") @db.VarChar(80) facebookUrl String? @map("facebook_url") @db.VarChar(250)
twitterUrl String? @map("twitter_url") @db.VarChar(80) linkedinUrl String? @map("linkedin_url") @db.VarChar(250)
twitterUrl String? @map("twitter_url") @db.VarChar(250)
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")
@@ -904,6 +910,7 @@ model Activities {
ActivityEquipmentTaxes ActivityEquipmentTaxes[] ActivityEquipmentTaxes ActivityEquipmentTaxes[]
ScheduleHeader ScheduleHeader[] ScheduleHeader ScheduleHeader[]
ItineraryActivities ItineraryActivities[] ItineraryActivities ItineraryActivities[]
activityTracks ActivityTrack[]
@@map("activities") @@map("activities")
@@schema("act") @@schema("act")
@@ -928,6 +935,25 @@ model ActivityOtherDetails {
@@schema("act") @@schema("act")
} }
model ActivityTrack {
id Int @id @default(autoincrement())
activityXid Int @map("activity_xid")
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
trackType String? @default("PQQ") @map("track_type")
updatedByRole String? @map("updated_by_role")
trackStatus String? @map("track_status")
updatedByXid Int? @map("updated_by_xid")
user User? @relation(fields: [updatedByXid], references: [id], onDelete: Cascade)
updatedOn DateTime? @map("updated_on")
isActive Boolean @default(true) @map("is_active")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
deletedAt DateTime? @map("deleted_at")
@@map("activity_track")
@@schema("act")
}
model ActivitiesMedia { model ActivitiesMedia {
id Int @id @default(autoincrement()) id Int @id @default(autoincrement())
activityXid Int @map("activity_xid") activityXid Int @map("activity_xid")
@@ -1115,8 +1141,8 @@ model ActivityPQQheader {
activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade) activity Activities @relation(fields: [activityXid], references: [id], onDelete: Cascade)
pqqQuestionXid Int @map("pqq_question_xid") pqqQuestionXid Int @map("pqq_question_xid")
pqqQuestions PQQQuestions @relation(fields: [pqqQuestionXid], references: [id], onDelete: Restrict) pqqQuestions PQQQuestions @relation(fields: [pqqQuestionXid], references: [id], onDelete: Restrict)
pqqAnswerXid Int @map("pqq_answer_xid") pqqAnswerXid Int? @map("pqq_answer_xid")
pqqAnswers PQQAnswers @relation(fields: [pqqAnswerXid], references: [id], onDelete: Restrict) pqqAnswers PQQAnswers? @relation(fields: [pqqAnswerXid], references: [id], onDelete: Restrict)
comments String? @map("comments") @db.VarChar(200) comments String? @map("comments") @db.VarChar(200)
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")

View File

@@ -1,6 +1,10 @@
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { PrismaPg } from '@prisma/adapter-pg';
import 'dotenv/config';
const prisma = new PrismaClient(); const prisma = new PrismaClient({
adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
});
async function main() { async function main() {
// ✅ Countries // ✅ Countries
@@ -74,35 +78,51 @@ async function main() {
create: { interestName: 'Chill and Zen', displayOrder: 1 }, create: { interestName: 'Chill and Zen', displayOrder: 1 },
}); });
const sweatmode = await prisma.interests.upsert({ const sweatmode = await prisma.interests.upsert({
where: { interestName: 'Sweat Mode' }, where: { interestName: 'Sweat Mode On' },
update: {}, update: {},
create: { interestName: 'Sweat Mode', displayOrder: 2 }, create: { interestName: 'Sweat Mode On', displayOrder: 2 },
}); });
const gameon = await prisma.interests.upsert({ const trackracer = await prisma.interests.upsert({
where: { interestName: 'Game On' }, where: { interestName: 'Track Racer' },
update: {}, update: {},
create: { interestName: 'Game On', displayOrder: 3 }, create: { interestName: 'Track Racer', displayOrder: 3 },
});
const circuitracer = await prisma.interests.upsert({
where: { interestName: 'Circuit Racer' },
update: {},
create: { interestName: 'Circuit Racer', displayOrder: 4 },
});
const thermalGliding = await prisma.interests.upsert({
where: { interestName: 'Thermal Gliding' },
update: {},
create: { interestName: 'Thermal Gliding', displayOrder: 5 },
}); });
const partycentral = await prisma.interests.upsert({ const partycentral = await prisma.interests.upsert({
where: { interestName: 'Party Central' }, where: { interestName: 'Party Central' },
update: {}, update: {},
create: { interestName: 'Party Central', displayOrder: 4 }, create: { interestName: 'Party Central', displayOrder: 6 },
}); });
const artsy = await prisma.interests.upsert({ const aqua = await prisma.interests.upsert({
where: { interestName: 'Artsy' }, where: { interestName: 'Aqua' },
update: {}, update: {},
create: { interestName: 'Artsy', displayOrder: 5 }, create: { interestName: 'Aqua', displayOrder: 7 },
}); });
const foodiediaries = await prisma.interests.upsert({ const foodie = await prisma.interests.upsert({
where: { interestName: 'Foodie Diaries' }, where: { interestName: 'Foodie' },
update: {}, update: {},
create: { interestName: 'Foodie Diaries', displayOrder: 6 }, create: { interestName: 'Foodie', displayOrder: 8 },
}); });
await prisma.activityTypes.createMany({ await prisma.activityTypes.createMany({
data: [ data: [
{ interestXid: chillandzen.id, activityTypeName: 'Cricket' }, { interestXid: aqua.id, activityTypeName: 'Scuba-Diving' },
{ interestXid: chillandzen.id, activityTypeName: 'Football' }, { interestXid: sweatmode.id, activityTypeName: 'Cloudboarding' },
{ interestXid: partycentral.id, activityTypeName: 'Soaring Glider' },
{ interestXid: sweatmode.id, activityTypeName: 'Speedway Racer' },
{ interestXid: aqua.id, activityTypeName: 'Aerial Surfing' },
{ interestXid: foodie.id, activityTypeName: 'Wine Tasting' },
{ interestXid: trackracer.id, activityTypeName: 'Track Racer' },
{ interestXid: thermalGliding.id, activityTypeName: 'Thermal Gliding' },
], ],
skipDuplicates: true, skipDuplicates: true,
}); });
@@ -133,6 +153,43 @@ async function main() {
skipDuplicates: true, // prevents error if already seeded skipDuplicates: true, // prevents error if already seeded
}); });
// ✅ Company types data
await prisma.companyTypes.upsert({
where: { companyTypeName: 'Proprietory' },
update: {},
create: { companyTypeName: 'Proprietory', displayOrder: 1 },
});
await prisma.companyTypes.upsert({
where: { companyTypeName: 'One Person Company' },
update: {},
create: { companyTypeName: 'One Person Company', displayOrder: 2 },
});
await prisma.companyTypes.upsert({
where: { companyTypeName: 'Limited Liability Partnership' },
update: {},
create: { companyTypeName: 'Limited Liability Partnership', displayOrder: 3 },
});
await prisma.companyTypes.upsert({
where: { companyTypeName: 'Partnership Firm' },
update: {},
create: { companyTypeName: 'Partnership Firm', displayOrder: 4 },
});
await prisma.companyTypes.upsert({
where: { companyTypeName: 'Private Limited, Public Limited' },
update: {},
create: { companyTypeName: 'Private Limited, Public Limited', displayOrder: 5 },
});
await prisma.companyTypes.upsert({
where: { companyTypeName: 'Non-Profit Organisation' },
update: {},
create: { companyTypeName: 'Non-Profit Organisation', displayOrder: 6 },
});
// ✅ Food Types // ✅ Food Types
await prisma.foodTypes.createMany({ await prisma.foodTypes.createMany({
data: [ data: [

View File

@@ -88,6 +88,7 @@ functions:
- ${file(./serverless/functions/host.yml)} - ${file(./serverless/functions/host.yml)}
- ${file(./serverless/functions/minglaradmin.yml)} - ${file(./serverless/functions/minglaradmin.yml)}
- ${file(./serverless/functions/prepopulate.yml)} - ${file(./serverless/functions/prepopulate.yml)}
- ${file(./serverless/functions/pqq.yml)}
plugins: plugins:
- serverless-offline - serverless-offline

View File

@@ -161,7 +161,7 @@ getPQQ_LastUpdatedQuestion:
path: /host/Activity_Hub/OnBoarding/get-latest-pqq-question-details path: /host/Activity_Hub/OnBoarding/get-latest-pqq-question-details
method: get method: get
getAllActivityType: prePopulateNewActivity:
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllActivityType.handler handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllActivityType.handler
memorySize: 384 memorySize: 384
package: package:
@@ -174,7 +174,7 @@ getAllActivityType:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /host/Activity_Hub/OnBoarding/get-activity-type path: /host/Activity_Hub/OnBoarding/prepopulate-new-activity
method: get method: get
showSuggestion: showSuggestion:
@@ -330,6 +330,23 @@ updatePQQ_LastAnswer:
path: /host/Activity_Hub/OnBoarding/submit-final-pqq-answer path: /host/Activity_Hub/OnBoarding/submit-final-pqq-answer
method: post method: post
submitPQQForReview:
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQQForReview.handler
memorySize: 384
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/submitPQQForReview.*'
- 'src/modules/host/services/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /host/Activity_Hub/OnBoarding/submit-pqq-for-review
method: patch
getAllPQQwithSubmittedAns: getAllPQQwithSubmittedAns:
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllPQQwithSubmittedAns.handler handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllPQQwithSubmittedAns.handler
memorySize: 512 memorySize: 512

View File

@@ -1,8 +1,6 @@
# Minglar Admin Module Functions # Minglar Admin Module Functions
# Admin dashboard and management endpoints # Admin dashboard and management endpoints
minglarRegistration: minglarRegistration:
handler: src/modules/minglaradmin/handlers/registration.handler handler: src/modules/minglaradmin/handlers/registration.handler
memorySize: 384 memorySize: 384
@@ -96,7 +94,6 @@ prepopulateRole:
path: /minglaradmin/prepopulate-Roles path: /minglaradmin/prepopulate-Roles
method: get method: get
getHostDetailsById: getHostDetailsById:
handler: src/modules/minglaradmin/handlers/hosthub/hosts/getByIdHostDetails.handler handler: src/modules/minglaradmin/handlers/hosthub/hosts/getByIdHostDetails.handler
memorySize: 384 memorySize: 384
@@ -283,8 +280,8 @@ assignAMToHost:
path: /minglaradmin/hosthub/onboarding/assign-am path: /minglaradmin/hosthub/onboarding/assign-am
method: patch method: patch
editAgreementDetails: editAgreementDetailsAndAccept:
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/editAgreementDetails.handler handler: src/modules/minglaradmin/handlers/hosthub/onboarding/editAgreementDetailsAndAccept.handler
memorySize: 384 memorySize: 384
package: package:
patterns: patterns:
@@ -296,9 +293,24 @@ editAgreementDetails:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /minglaradmin/hosthub/onboarding/edit-agreement path: /minglaradmin/hosthub/onboarding/edit-agreement-accept-host
method: patch method: patch
getAllPqqQuesAnsForAM:
handler: src/modules/host/handlers/Activity_Hub/OnBoarding/getAllPQQwithSubmittedAns.handler
memorySize: 512
package:
patterns:
- 'src/modules/host/handlers/Activity_Hub/OnBoarding/getAllPQQwithSubmittedAns**'
- ${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: /minglaradmin/hosthub/onboarding/get-all-pqq-ques-ans-for-am
method: get
acceptHostApplication: acceptHostApplication:
handler: src/modules/minglaradmin/handlers/hosthub/hosts/acceptHostApplication.handler handler: src/modules/minglaradmin/handlers/hosthub/hosts/acceptHostApplication.handler
memorySize: 384 memorySize: 384
@@ -328,15 +340,15 @@ RejectPQQByAM:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /minglaradmin/hosthub/hosts/reject-pqq-by-am path: /minglaradmin/hosthub/hosts/reject-pq-by-am
method: patch method: patch
acceptHostApplicationMinglar: acceptPQByAM:
handler: src/modules/minglaradmin/handlers/hosthub/onboarding/acceptHostAppMinglar.handler handler: src/modules/minglaradmin/handlers/hosthub/hosts/acceptPQByAM.handler
memorySize: 384 memorySize: 384
package: package:
patterns: patterns:
- 'src/modules/minglaradmin/handlers/hosthub/onboarding/**' - 'src/modules/minglaradmin/handlers/hosthub/hosts/acceptPQByAM**'
- 'src/modules/minglaradmin/services/**' - 'src/modules/minglaradmin/services/**'
- ${file(./serverless/patterns/base.yml):pattern1} - ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2} - ${file(./serverless/patterns/base.yml):pattern2}
@@ -344,7 +356,7 @@ acceptHostApplicationMinglar:
- ${file(./serverless/patterns/base.yml):pattern4} - ${file(./serverless/patterns/base.yml):pattern4}
events: events:
- httpApi: - httpApi:
path: /minglaradmin/hosthub/onboarding/accept-host-application-minglar path: /minglaradmin/hosthub/hosts/accept-pq-by-am
method: patch method: patch
rejectHostApplication: rejectHostApplication:
@@ -361,7 +373,7 @@ rejectHostApplication:
events: events:
- httpApi: - httpApi:
path: /minglaradmin/hosthub/onboarding/reject-host-application path: /minglaradmin/hosthub/onboarding/reject-host-application
method: post method: patch
rejectHostApplicationAM: rejectHostApplicationAM:
handler: src/modules/minglaradmin/handlers/hosthub/hosts/rejectHostApplicationAM.handler handler: src/modules/minglaradmin/handlers/hosthub/hosts/rejectHostApplicationAM.handler
@@ -377,7 +389,7 @@ rejectHostApplicationAM:
events: events:
- httpApi: - httpApi:
path: /minglaradmin/hosthub/hosts/reject-host-application-am path: /minglaradmin/hosthub/hosts/reject-host-application-am
method: post method: patch
addPQQSuggestion: addPQQSuggestion:
handler: src/modules/minglaradmin/handlers/hosthub/hosts/addPQQSuggestion.handler handler: src/modules/minglaradmin/handlers/hosthub/hosts/addPQQSuggestion.handler
@@ -394,3 +406,19 @@ addPQQSuggestion:
- httpApi: - httpApi:
path: /minglaradmin/hosthub/hosts/add-Pqq-suggestion path: /minglaradmin/hosthub/hosts/add-Pqq-suggestion
method: post method: post
getAllPQPDetailsForAM:
handler: src/modules/minglaradmin/handlers/hosthub/pqp/getAllPQPDetailsForAM.handler
memorySize: 384
package:
patterns:
- 'src/modules/minglaradmin/handlers/hosthub/pqp/getAllPQPDetailsForAM**'
- 'src/modules/minglaradmin/services/**'
- ${file(./serverless/patterns/base.yml):pattern1}
- ${file(./serverless/patterns/base.yml):pattern2}
- ${file(./serverless/patterns/base.yml):pattern3}
- ${file(./serverless/patterns/base.yml):pattern4}
events:
- httpApi:
path: /minglaradmin/hosthub/pqp/pqp-details-for-am/{activityXid}
method: get

View File

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

View File

@@ -23,19 +23,16 @@ export const STEPPER = {
REJECTED: 6 REJECTED: 6
} }
export const LAST_QUESTION_ID = {
Q_ID: 55
}
export const ACTIVITY_INTERNAL_STATUS = { export const ACTIVITY_INTERNAL_STATUS = {
DRAFT_PQ: 'Draft - PQ', DRAFT_PQ: 'Draft - PQ',
APPROVED: 'Approved', APPROVED: 'Approved',
REJECTED: 'Rejected', REJECTED: 'Rejected',
DRAFT: 'Draft', DRAFT: 'Draft',
UNDER_REVIEW: 'Under-Review', UNDER_REVIEW: 'Under-Review',
PQQ_FAILED: 'PQQ Failed', PQ_FAILED: 'PQ Failed',
PQQ_TO_UPDATE: 'PQ To Update', PQ_TO_UPDATE: 'PQ To Update',
PQQ_SUBMITTED: 'PQ Submitted' PQ_SUBMITTED: 'PQ Submitted',
PQ_APPROVED: 'PQ Approved'
} }
export const ACTIVITY_DISPLAY_STATUS = { export const ACTIVITY_DISPLAY_STATUS = {
@@ -44,9 +41,10 @@ export const ACTIVITY_DISPLAY_STATUS = {
REJECTED: 'Rejected', REJECTED: 'Rejected',
DRAFT: 'Draft', DRAFT: 'Draft',
UNDER_REVIEW: 'Under-Review', UNDER_REVIEW: 'Under-Review',
PQQ_FAILED: 'PQQ Failed', PQ_FAILED: 'PQ Failed',
ENHANCING: 'Enchancing', ENHANCING: 'Enchancing',
PQ_IN_REVIEW: 'PQ In Review' PQ_IN_REVIEW: 'PQ In Review',
PQ_APPROVED: 'PQ Approved'
} }
export const ACTIVITY_AM_INTERNAL_STATUS = { export const ACTIVITY_AM_INTERNAL_STATUS = {
@@ -55,9 +53,10 @@ export const ACTIVITY_AM_INTERNAL_STATUS = {
REJECTED: 'Rejected', REJECTED: 'Rejected',
DRAFT: 'Draft', DRAFT: 'Draft',
UNDER_REVIEW: 'Under-Review', UNDER_REVIEW: 'Under-Review',
PQQ_FAILED: 'PQQ Failed', PQ_FAILED: 'PQ Failed',
PQQ_REJECTED: 'PQ Rejected', PQ_REJECTED: 'PQ Rejected',
PQQ_TO_REVIEW: 'PQ To Review' PQ_TO_REVIEW: 'PQ To Review',
PQ_APPROVED: 'PQ Approved'
} }
export const ACTIVITY_AM_DISPLAY_STATUS = { export const ACTIVITY_AM_DISPLAY_STATUS = {
@@ -66,7 +65,9 @@ export const ACTIVITY_AM_DISPLAY_STATUS = {
REJECTED: 'Rejected', REJECTED: 'Rejected',
DRAFT: 'Draft', DRAFT: 'Draft',
UNDER_REVIEW: 'Under-Review', UNDER_REVIEW: 'Under-Review',
PQQ_FAILED: 'PQQ Failed', PQ_FAILED: 'PQ Failed',
ENHANCING: 'Enchancing', ENHANCING: 'Enchancing',
NEW: 'New' NEW: 'New',
PQ_APPROVED: 'PQ Approved',
REVISED: 'Revised'
} }

View File

@@ -25,23 +25,35 @@ export const MINGLAR_INVITATION_STATUS = {
INVITED: 'Invited', INVITED: 'Invited',
}; };
export const HOST_SUGGESTION_TITLES = { export const ACTIVITY_TRACK_TYPE = {
COMPANY_DETAILS: 'Complete Details', PQ: 'PQ',
COMPANY_DOCUMENTATION: 'Company documentataion', ACTIVITY: 'Activity'
COMPANY_SOCIAL_PROOF: 'Social Proof', }
ACTIVITY_INFORMATION: 'Activity Information',
ACTIVITY_LOCATION: 'Activity Location', export const ACTIVITY_TRACK_STATUS = {
PICKUP_DROP_LOCATION: 'Pickup-Drop Location', REJECTED_BY_AM: 'Rejected By AM',
NUMBER_OF_PEOPLE: 'Number of People', ACCEPTED_BY_AM: 'Accepted By AM',
INCLUSION: 'Inclusion', ENHANCING: 'Enhancing',
TAX_SETUP: 'Tax Setup', PQ_SUBMITTED: 'PQ Submitted'
ENERGY_LEVEL: 'Energy Level', }
ELIGIBILITY_CRITERIA: 'Eligibility Criteria',
AMENITIES: 'Amenities', // export const HOST_SUGGESTION_TITLES = {
EXLUSIVE_NOTES: 'Exclusive Notes', // COMPANY_DETAILS: 'Complete Details',
CANCELLATION_POLICY: 'Cancellation Policy', // COMPANY_DOCUMENTATION: 'Company documentataion',
DOs_AND_DONTs: 'Dos and Donts', // COMPANY_SOCIAL_PROOF: 'Social Proof',
TIPS_FOR_USERS: 'Tips for Users', // ACTIVITY_INFORMATION: 'Activity Information',
SUSTAINABILITY: 'Sustainability', // ACTIVITY_LOCATION: 'Activity Location',
TERMS_AND_CONDITION_FOR_USER: 'Terms and Conditions for User' // PICKUP_DROP_LOCATION: 'Pickup-Drop Location',
}; // NUMBER_OF_PEOPLE: 'Number of People',
// INCLUSION: 'Inclusion',
// TAX_SETUP: 'Tax Setup',
// ENERGY_LEVEL: 'Energy Level',
// ELIGIBILITY_CRITERIA: 'Eligibility Criteria',
// AMENITIES: 'Amenities',
// EXLUSIVE_NOTES: 'Exclusive Notes',
// CANCELLATION_POLICY: 'Cancellation Policy',
// DOs_AND_DONTs: 'Dos and Donts',
// TIPS_FOR_USERS: 'Tips for Users',
// SUSTAINABILITY: 'Sustainability',
// TERMS_AND_CONDITION_FOR_USER: 'Terms and Conditions for User'
// };

View File

@@ -11,11 +11,6 @@ export const hostBankDetailsSchema = z.object({
.nonempty("Account holder name is required") .nonempty("Account holder name is required")
.min(2, { message: "Account holder name must be at least 2 characters" }), .min(2, { message: "Account holder name must be at least 2 characters" }),
ifscCode: z
.string()
.nonempty("IFSC code is required")
.regex(/^[A-Z]{4}0[A-Z0-9]{6}$/, { message: "Invalid IFSC code format" }),
bankXid: z bankXid: z
.number() .number()
.int({ message: "Bank ID must be an integer" }) .int({ message: "Bank ID must be an integer" })

View File

@@ -6,20 +6,20 @@ export const parentCompanySchema = z.object({
.max(100, "Parent company name cannot exceed 100 characters"), .max(100, "Parent company name cannot exceed 100 characters"),
address1: z.string() address1: z.string()
.min(1, "Address1 is required") .max(150, "Address1 cannot exceed 150 characters")
.max(150, "Address1 cannot exceed 150 characters"), .optional(),
address2: z.string() address2: z.string()
.max(150, "Address2 cannot exceed 150 characters") .max(150, "Address2 cannot exceed 150 characters")
.optional(), .optional(),
cityXid: z.number().min(1, "City is required"), cityXid: z.number().optional(),
stateXid: z.number().min(1, "State is required"), stateXid: z.number().optional(),
countryXid: z.number().min(1, "Country is required"), countryXid: z.number().optional(),
pinCode: z.string() pinCode: z.string()
.min(4, "Pincode/Zipcode is required") .max(30, "Pincode cannot exceed 30 characters")
.max(30, "Pincode cannot exceed 30 characters"), .optional(),
logoPath: z.string() logoPath: z.string()
.max(400, "Logo path cannot exceed 400 characters") .max(400, "Logo path cannot exceed 400 characters")
@@ -45,15 +45,15 @@ export const parentCompanySchema = z.object({
message: "Formation date must be a valid date", message: "Formation date must be a valid date",
}), }),
companyType: z.string() companyTypeXid: z.number()
.min(1, "Company type is required") .min(1, "Company type XID is required"),
.max(30, "Company type cannot exceed 30 characters"),
websiteUrl: z.string().nullable().optional(),
instagramUrl: z.string().nullable().optional(),
facebookUrl: z.string().nullable().optional(),
linkedinUrl: z.string().nullable().optional(),
twitterUrl: z.string().nullable().optional(),
websiteUrl: z.string().url().max(80, "Website URL cannot exceed 80 characters").optional(),
instagramUrl: z.string().url().max(80, "Instagram URL cannot exceed 80 characters").optional(),
facebookUrl: z.string().url().max(80, "Facebook URL cannot exceed 80 characters").optional(),
linkedinUrl: z.string().url().max(80, "LinkedIn URL cannot exceed 80 characters").optional(),
twitterUrl: z.string().url().max(80, "Twitter URL cannot exceed 80 characters").optional(),
}); });
@@ -106,15 +106,20 @@ export const hostCompanyDetailsSchema = z.object({
message: "Formation date must be a valid date", message: "Formation date must be a valid date",
}), }),
companyType: z.string() companyTypeXid: z.number()
.min(1, "Company type is required") .int("Company type must be a valid integer")
.max(30, "Company type cannot exceed 30 characters"), .min(1, "Company type is required"),
referencedBy: z.string()
.optional(),
websiteUrl: z.string().nullable().optional(),
instagramUrl: z.string().nullable().optional(),
facebookUrl: z.string().nullable().optional(),
linkedinUrl: z.string().nullable().optional(),
twitterUrl: z.string().nullable().optional(),
websiteUrl: z.string().url().max(50, "Website URL cannot exceed 50 characters").optional(),
instagramUrl: z.string().url().max(80, "Instagram URL cannot exceed 80 characters").optional(),
facebookUrl: z.string().url().max(80, "Facebook URL cannot exceed 80 characters").optional(),
linkedinUrl: z.string().url().max(80, "LinkedIn URL cannot exceed 80 characters").optional(),
twitterUrl: z.string().url().max(80, "Twitter URL cannot exceed 80 characters").optional(),
parentCompany: parentCompanySchema.optional(), parentCompany: parentCompanySchema.optional(),
}); });

View File

@@ -90,5 +90,6 @@ export class AddPaymentDetailsDTO {
this.accountHolderName = accountHolderName; this.accountHolderName = accountHolderName;
this.ifscCode = ifscCode; this.ifscCode = ifscCode;
this.hostXid = hostXid; this.hostXid = hostXid;
this.currencyXid = currencyXid;
} }
} }

View File

@@ -0,0 +1,62 @@
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service';
const prismaService = new PrismaService();
const hostService = new HostService(prismaService);
export const handler = safeHandler(
async (
event: APIGatewayProxyEvent,
context?: Context,
): Promise<APIGatewayProxyResult> => {
// Verify authentication token
const token =
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(
401,
'This is a protected route. Please provide a valid token.',
);
}
// Verify token and get user info
const userInfo = await verifyHostToken(token);
let body: any = {};
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (err) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { activityTypeXid, frequenciesXid } = body;
if (!activityTypeXid || !frequenciesXid) {
throw new ApiError(400, 'activityType and frequency ID is required');
}
// Get all host applications from service based on user role
const createdData = await hostService.createActivityAndAllQuestionsEntry(userInfo.id, activityTypeXid, frequenciesXid);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Activity created successfully',
data: createdData
}),
};
},
);

View File

@@ -1,4 +1,4 @@
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';

View File

@@ -1,10 +1,10 @@
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
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 { string } from 'zod'; import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
@@ -29,11 +29,18 @@ export const handler = safeHandler(async (
// Verify token and get user info // Verify token and get user info
const userInfo = await verifyHostToken(token); const userInfo = await verifyHostToken(token);
// Get pagination params from event
const paginationParams = paginationService.getPaginationFromEvent(event);
const paginationOptions = paginationService.parsePaginationParams(paginationParams);
// Read optional search query (supports ?search= or ?q=) // Read optional search query (supports ?search= or ?q=)
const search = event.queryStringParameters?.search || event.queryStringParameters?.q || undefined; const search = event.queryStringParameters?.search || event.queryStringParameters?.q || undefined;
const data = await hostService.getAllHostActivity(search ? String(search) : undefined, Number(userInfo.id)); const result = await hostService.getAllHostActivity(
search ? String(search) : undefined,
Number(userInfo.id),
paginationOptions
);
return { return {
@@ -45,8 +52,7 @@ export const handler = safeHandler(async (
body: JSON.stringify({ body: JSON.stringify({
success: true, success: true,
message: 'Data retrieved successfully', message: 'Data retrieved successfully',
data, ...result,
}), }),
}; };
}); });

View File

@@ -1,11 +1,9 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
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 { PrePopulateService } from '../../../../prepopulate/services/prepopulate.service';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import { verifyMinglarAdminHostToken } from '@/common/middlewares/jwt/authForMinglarAdmin&Host'; import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const hostService = new HostService(prismaService); const hostService = new HostService(prismaService);
@@ -23,7 +21,15 @@ export const handler = safeHandler(async (
// Authenticate user using the shared authForHost function // Authenticate user using the shared authForHost function
await verifyMinglarAdminHostToken(token); await verifyMinglarAdminHostToken(token);
const result = await hostService.getAllPQQQuesAndSubmittedAns(); const activity_xid = event.queryStringParameters?.activity_xid
? Number(event.queryStringParameters.activity_xid)
: null;
if (!activity_xid) {
throw new ApiError(409, "Activity ID is required")
}
const result = await hostService.getAllPQQQuesAndSubmittedAns(Number(activity_xid));
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -1,4 +1,4 @@
import config from '@/config/config'; import config from '../../../../../config/config';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import AWS from 'aws-sdk'; import AWS from 'aws-sdk';
import Busboy from 'busboy'; import Busboy from 'busboy';
@@ -8,48 +8,36 @@ 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 { LAST_QUESTION_ID } from '@/common/utils/constants/host.constant';
const prisma = new PrismaService(); const prisma = new PrismaService();
const pqqService = new HostService(prisma); const hostService = new HostService(prisma);
const s3 = new AWS.S3({ region: config.aws.region }); const s3 = new AWS.S3({ region: config.aws.region });
// Function to extract S3 key from URL // 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/`;
return url.replace(bucketBaseUrl, ''); return url.replace(bucketBaseUrl, '');
} }
// Function to delete file from S3 // Delete file from S3
async function deleteFromS3(s3Key: string): Promise<void> { async function deleteFromS3(s3Key: string): Promise<void> {
try { try {
await s3.deleteObject({ await s3.deleteObject({
Bucket: config.aws.bucketName, Bucket: config.aws.bucketName,
Key: s3Key, Key: s3Key,
}).promise(); }).promise();
console.log(`File deleted from S3: ${s3Key}`); console.log(`Deleted from S3: ${s3Key}`);
} catch (error) { } catch (err) {
console.error(`Error deleting file from S3: ${s3Key}`, error); console.error(`Failed to delete from S3: ${s3Key}`, err);
// Don't throw error here, continue with upload
} }
} }
async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string, existingUrl?: string): Promise<string> { // Upload new file
let s3Key: string; async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string): Promise<string> {
const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
const s3Key = `${prefix}/${uniqueKey}`;
// If existing URL provided, use the same S3 key to replace the file
if (existingUrl) {
s3Key = getS3KeyFromUrl(existingUrl);
// Delete existing file first
await deleteFromS3(s3Key);
} else {
// Generate new unique key for new file
const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
s3Key = `${prefix}/${uniqueKey}`;
}
// Upload new file (replaces existing if same key)
await s3.upload({ await s3.upload({
Bucket: config.aws.bucketName, Bucket: config.aws.bucketName,
Key: s3Key, Key: s3Key,
@@ -58,253 +46,160 @@ async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string
ACL: 'private' ACL: 'private'
}).promise(); }).promise();
console.log(`File uploaded to S3: ${s3Key}`); console.log(`Uploaded to S3: ${s3Key}`);
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`; return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
} }
export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try { try {
// 1) Auth // AUTH
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']; const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) throw new ApiError(401, 'Missing token.'); if (!token) throw new ApiError(401, 'Missing token');
const user = await verifyHostToken(token); const user = await verifyHostToken(token);
// 2) Content-Type check // Content-Type
const contentType = event.headers["content-type"] || event.headers["Content-Type"]; const contentType = event.headers["content-type"] || event.headers["Content-Type"];
if (!contentType?.startsWith("multipart/form-data")) if (!contentType?.startsWith("multipart/form-data"))
throw new ApiError(400, "Content-Type must be multipart/form-data"); throw new ApiError(400, "Content-Type must be multipart/form-data");
if (!event.isBase64Encoded) if (!event.isBase64Encoded) throw new ApiError(400, "Body must be base64 encoded");
throw new ApiError(400, "Body must be base64 encoded");
const bodyBuffer = Buffer.from(event.body!, "base64"); const bodyBuffer = Buffer.from(event.body!, "base64");
const fields: any = {}; const fields: any = {};
const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = []; const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = [];
// 3) Parse multipart data // Parse multipart
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const bb = Busboy({ headers: { 'content-type': contentType } }); const bb = Busboy({ headers: { 'content-type': contentType } });
bb.on('file', (fieldname, file, info) => { bb.on('file', (fieldname, file, info) => {
const { filename, mimeType } = info; const { filename, mimeType } = info;
if (!filename) return file.resume();
// Skip if no filename (empty file field)
if (!filename) {
file.resume();
return;
}
const chunks: Buffer[] = []; const chunks: Buffer[] = [];
let totalSize = 0; let size = 0;
const MAX_SIZE = 5 * 1024 * 1024; // 5 MB
file.on('data', chunk => {
size += chunk.length;
if (size > 5 * 1024 * 1024)
return reject(new ApiError(400, `File ${filename} exceeds 5MB limit`));
file.on('data', (chunk) => {
totalSize += chunk.length;
if (totalSize > MAX_SIZE) {
file.resume();
return reject(new ApiError(400, `File ${filename} exceeds 5MB limit.`));
}
chunks.push(chunk); chunks.push(chunk);
}); });
file.on('end', () => { file.on('end', () => {
// Only add file if we have data
if (chunks.length > 0) { if (chunks.length > 0) {
files.push({ files.push({
buffer: Buffer.concat(chunks), buffer: Buffer.concat(chunks),
mimeType, mimeType,
fileName: filename, fileName: filename,
fieldName: fieldname, fieldName: fieldname
}); });
} }
}); });
file.on('error', (err) => {
reject(new ApiError(400, `File upload error: ${err.message}`));
});
}); });
bb.on('field', (fieldname, val) => { bb.on('field', (fieldname, val) => {
// Handle empty or null values try { fields[fieldname] = JSON.parse(val); }
if (val === '' || val === 'null' || val === 'undefined') { catch { fields[fieldname] = val; }
fields[fieldname] = null;
} else {
try {
fields[fieldname] = JSON.parse(val);
} catch {
fields[fieldname] = val;
}
}
});
bb.on('close', () => {
console.log("✅ Busboy parsing completed");
console.log("📌 Fields:", fields);
console.log("📁 Files:", files.length);
resolve();
});
bb.on('error', (err) => {
console.error("❌ Busboy error:", err);
reject(new ApiError(400, `Multipart parsing error: ${err.message}`));
}); });
bb.on('close', resolve);
bb.on('error', err => reject(new ApiError(400, err.message)));
bb.end(bodyBuffer); bb.end(bodyBuffer);
}); });
// 4) Extract required fields - only activityXid, pqqQuestionXid, pqqAnswerXid are required // Required fields
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);
// Comments and files are optional
const comments = fields.comments || null; const comments = fields.comments || null;
// Validate required fields if (!activityXid || !pqqQuestionXid || !pqqAnswerXid)
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Valid activityXid is required"); throw new ApiError(400, "Missing required fields");
if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Valid pqqQuestionXid is required");
if (pqqQuestionXid !== LAST_QUESTION_ID.Q_ID) throw new ApiError(400, "Wrong question id.")
if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Valid pqqAnswerXid is required");
// console.log(`📝 Processing - Activity: ${activityXid}, Question: ${pqqQuestionXid}, Answer: ${pqqAnswerXid}`);
// console.log(`💬 Comments: ${comments ? 'Provided' : 'Not provided'}`);
// console.log(`📎 Files: ${files.length}`);
// 5) UPSERT: Check if header already exists for this combination
const existingHeader = await pqqService.findHeaderByCompositeKey(
activityXid,
pqqQuestionXid,
pqqAnswerXid
);
// UPSERT header
const existingHeader = await hostService.findHeaderByCompositeKey(activityXid, pqqQuestionXid);
let header; let header;
if (existingHeader) { if (existingHeader) {
console.log("🔄 Updating existing PQQ header"); header = await hostService.updateHeader(existingHeader.id, pqqAnswerXid, comments);
// Update existing header (comments can be null)
header = await pqqService.updateHeader(
existingHeader.id,
comments
);
} else { } else {
console.log("🆕 Creating new PQQ header"); header = await hostService.createHeader(activityXid, pqqQuestionXid, pqqAnswerXid, comments);
// Create new header (comments can be null)
header = await pqqService.createHeader(
activityXid,
pqqQuestionXid,
pqqAnswerXid,
comments
);
} }
// Calculate score after answer submission
const score = await pqqService.calculatePqqScoreForUser(activityXid);
// SCORE
const score = await hostService.calculatePqqScoreForUser(activityXid);
// 6) Get existing supporting files for this header // Existing supporting files
const existingSupportingFiles = await pqqService.getSupportingFilesByHeaderId(header.id); const existingSupportingFiles = await hostService.getSupportingFilesByHeaderId(header.id);
console.log(`📁 Found ${existingSupportingFiles.length} existing supporting files`);
// 7) Handle file UPSERT - only if files are provided // Read deletedFiles from frontend
const uploadedFiles: any[] = []; const deletedFiles = Array.isArray(fields.deletedFiles) ? fields.deletedFiles : [];
const deleteResults = [];
const addResults = [];
// DELETE explicitly requested files (Case 3)
if (deletedFiles.length > 0) {
for (const del of deletedFiles) {
const id = Number(del.id);
const record = existingSupportingFiles.find(f => f.id === id);
if (!record) continue;
// Delete from S3
if (record.mediaFileName) {
const key = getS3KeyFromUrl(record.mediaFileName);
await deleteFromS3(key);
}
// Delete from DB
await hostService.deleteSupportingFile(record.id);
deleteResults.push({ id: record.id, deleted: true });
}
}
// ADD new uploaded files (Case 1 + Case 3 new files)
if (files.length > 0) { if (files.length > 0) {
console.log("📤 Processing file uploads..."); for (const file of files) {
for (let i = 0; i < files.length; i++) {
const file = files[i];
const existingFile = existingSupportingFiles[i] || null;
const url = await uploadToS3( const url = await uploadToS3(
file.buffer, file.buffer,
file.mimeType, file.mimeType,
file.fileName, file.fileName,
`ActivityOnboarding/supportings/${activityXid}`, `ActivityOnboarding/supportings/${activityXid}`
existingFile ? existingFile.mediaFileName : undefined
); );
let supporting; const newRec = await hostService.addSupportingFile(header.id, file.mimeType, url);
if (existingFile) { addResults.push(newRec);
// Update existing supporting file record
supporting = await pqqService.updateSupportingFile(
existingFile.id,
file.mimeType,
url
);
console.log(`🔄 Updated supporting file: ${existingFile.id}`);
} else {
// Create new supporting file record
supporting = await pqqService.addSupportingFile(
header.id,
file.mimeType,
url
);
console.log(`🆕 Created new supporting file: ${supporting.id}`);
}
uploadedFiles.push(supporting);
}
// 8) Delete any remaining existing files that weren't replaced
if (existingSupportingFiles.length > files.length) {
const filesToDelete = existingSupportingFiles.slice(files.length);
console.log(`🗑️ Deleting ${filesToDelete.length} unused supporting files`);
for (const fileToDelete of filesToDelete) {
await pqqService.deleteSupportingFile(fileToDelete.id);
// Also delete from S3
const s3Key = getS3KeyFromUrl(fileToDelete.mediaFileName);
await deleteFromS3(s3Key);
console.log(`🗑️ Deleted supporting file: ${fileToDelete.id}`);
}
}
} else {
console.log("📭 No files provided in request");
// If no files provided but existing files exist, delete them (cleanup)
if (existingSupportingFiles.length > 0) {
console.log(`🗑️ No new files provided, deleting ${existingSupportingFiles.length} existing files`);
for (const fileToDelete of existingSupportingFiles) {
await pqqService.deleteSupportingFile(fileToDelete.id);
const s3Key = getS3KeyFromUrl(fileToDelete.mediaFileName);
await deleteFromS3(s3Key);
console.log(`🗑️ Deleted supporting file: ${fileToDelete.id}`);
}
} }
} }
// 9) Prepare response const getAllUpdatedQuestionResponse = await hostService.getAllPQUpdatedResponse(activityXid)
const responseMessage = existingHeader ? "PQQ answer updated successfully" : "PQQ answer submitted successfully";
// CASE 2 — NO deletion & NO new files => DO NOTHING to existing files
return { return {
statusCode: 200, statusCode: 200,
headers: { headers: { "Content-Type": "application/json", "Access-Control-Allow-Origin": "*" },
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify({ body: JSON.stringify({
success: true, success: true,
message: responseMessage, message: existingHeader ? "PQQ answer updated successfully" : "PQQ answer submitted successfully",
data: { data: {
headerId: header.id, headerId: header.id,
activityXid, activityXid,
pqqQuestionXid, pqqQuestionXid,
pqqAnswerXid, pqqAnswerXid,
comments: comments, comments,
score, score,
files: { getAllUpdatedQuestionResponse
uploaded: uploadedFiles,
total: uploadedFiles.length
},
operation: existingHeader ? 'updated' : 'created',
fileOperation: files.length > 0 ?
(existingSupportingFiles.length > 0 ? 'replaced' : 'added') :
(existingSupportingFiles.length > 0 ? 'removed' : 'unchanged')
} }
}) })
}; };
} catch (error: any) { } catch (err: any) {
console.error("❌ Error in submitPqqAnswer:", error); console.error("❌ Error:", err);
throw error; throw err;
} }
}); });

View File

@@ -1,10 +1,9 @@
import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError'; import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import { verifyMinglarAdminHostToken } from '@/common/middlewares/jwt/authForMinglarAdmin&Host';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const hostService = new HostService(prismaService); const hostService = new HostService(prismaService);

View File

@@ -2,7 +2,7 @@ import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.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 { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
@@ -29,15 +29,18 @@ export const handler = safeHandler(async (
if (!activity_xid || isNaN(activity_xid)) { if (!activity_xid || isNaN(activity_xid)) {
throw new ApiError(400, "Activity id is required and must be a number."); throw new ApiError(400, "Activity id is required and must be a number.");
} }
let result = null;
// Fetch user with their HostHeader stepper info // Fetch user with their HostHeader stepper info
const pqqQuestionDetails = await hostService.getLatestQuestionDetailsPQQ(activity_xid); const pqqQuestionDetails = await hostService.getLatestQuestionDetailsPQQ(activity_xid);
const result = { if (pqqQuestionDetails) {
pqqQuestionXid: pqqQuestionDetails.pqqQuestionXid, result = {
pqqAnswerXid: pqqQuestionDetails.pqqAnswerXid, pqqQuestionXid: pqqQuestionDetails.pqqQuestionXid,
pqqSubCategoryXid: pqqQuestionDetails.pqqQuestions.pqqSubCategoryXid, pqqAnswerXid: pqqQuestionDetails.pqqAnswerXid || null,
categoryXid: pqqQuestionDetails.pqqQuestions.pqqSubCategories.categoryXid pqqSubCategoryXid: pqqQuestionDetails.pqqQuestions.pqqSubCategoryXid || null,
categoryXid: pqqQuestionDetails.pqqQuestions.pqqSubCategories.categoryXid || null
}
} }
return { return {

View File

@@ -1,4 +1,4 @@
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';

View File

@@ -0,0 +1,301 @@
import config from '../../../../../config/config';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import AWS from 'aws-sdk';
import Busboy from 'busboy';
import crypto from 'crypto';
import { PrismaService } from '../../../../../common/database/prisma.service';
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service';
const prisma = new PrismaService();
const hostService = new HostService(prisma);
const s3 = new AWS.S3({ region: config.aws.region });
// Function to extract S3 key from URL
function getS3KeyFromUrl(url: string): string {
const bucketBaseUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
return url.replace(bucketBaseUrl, '');
}
// Function to delete file from S3
async function deleteFromS3(s3Key: string): Promise<void> {
try {
await s3.deleteObject({
Bucket: config.aws.bucketName,
Key: s3Key,
}).promise();
console.log(`✅ File deleted from S3: ${s3Key}`);
} catch (error) {
console.error(`❌ Error deleting file from S3: ${s3Key}`, error);
// continue — we don't want S3 deletion failure to crash the whole request
}
}
async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string, existingUrl?: string): Promise<string> {
// We intentionally do NOT reuse old key. If existingUrl is provided we delete old file and create a new random key.
if (existingUrl) {
try {
const oldKey = getS3KeyFromUrl(existingUrl);
await deleteFromS3(oldKey);
} catch (err) {
console.warn('Warning deleting existingUrl before upload', err);
}
}
const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
const s3Key = `${prefix}/${uniqueKey}`;
await s3.upload({
Bucket: config.aws.bucketName,
Key: s3Key,
Body: buffer,
ContentType: mimeType,
ACL: 'private'
}).promise();
console.log(`✅ File uploaded to S3: ${s3Key}`);
return `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/${s3Key}`;
}
export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
// 1) Auth
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) throw new ApiError(401, 'Missing token.');
const user = await verifyHostToken(token);
// 2) Content-Type check
const contentType = event.headers["content-type"] || event.headers["Content-Type"];
if (!contentType?.includes("multipart/form-data"))
throw new ApiError(400, "Content-Type must be multipart/form-data");
// 3) Body decoding
const bodyBuffer = event.isBase64Encoded
? Buffer.from(event.body!, "base64")
: Buffer.from(event.body!, "binary");
const fields: any = {};
const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = [];
// 4) Parse multipart data
await new Promise<void>((resolve, reject) => {
const bb = Busboy({ headers: { 'content-type': contentType } });
bb.on('file', (fieldname, file, info) => {
const { filename, mimeType } = info;
if (!filename) {
file.resume();
return;
}
const chunks: Buffer[] = [];
let size = 0;
const MAX_SIZE = 5 * 1024 * 1024;
file.on("data", (chunk) => {
size += chunk.length;
if (size > MAX_SIZE) {
file.destroy(new Error(`File ${filename} exceeds 5MB limit.`));
return;
}
chunks.push(chunk);
});
file.on("end", () => {
if (chunks.length > 0) {
files.push({
buffer: Buffer.concat(chunks),
mimeType,
fileName: filename,
fieldName: fieldname,
});
}
});
file.on("error", (err) =>
reject(new ApiError(400, `File upload error: ${err.message}`))
);
});
bb.on("field", (fieldname, val) => {
console.log(`FIELD RAW: ${fieldname} =`, val);
if (val === '' || val === 'null' || val === 'undefined') fields[fieldname] = null;
else {
try {
const cleaned = val.trim();
// If it starts and ends with quotes, remove them
const withoutQuotes =
(cleaned.startsWith('"') && cleaned.endsWith('"'))
? cleaned.slice(1, -1)
: cleaned;
fields[fieldname] = JSON.parse(withoutQuotes);
} catch {
fields[fieldname] = val;
}
}
});
bb.on("close", () => resolve());
bb.on("error", (err) =>
reject(new ApiError(400, `Multipart parsing error: ${err.message}`))
);
// IMPORTANT FIX for HTTP API
bb.write(bodyBuffer);
bb.end();
});
// 5) Extract required fields
const activityXid = Number(fields.activityXid);
const pqqQuestionXid = Number(fields.pqqQuestionXid);
const pqqAnswerXid = Number(fields.pqqAnswerXid);
const comments = fields.comments || null;
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Valid activityXid is required");
if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Valid pqqQuestionXid is required");
if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Valid pqqAnswerXid is required");
// 6) UPSERT header
const existingHeader = await hostService.findHeaderByCompositeKey(
activityXid,
pqqQuestionXid,
);
let header;
if (existingHeader) {
console.log("🔄 Updating existing PQQ header");
header = await hostService.updateHeader(
existingHeader.id,
pqqAnswerXid,
comments
);
} else {
console.log("🆕 Creating new PQQ header");
header = await hostService.createHeader(
activityXid,
pqqQuestionXid,
pqqAnswerXid,
comments
);
}
// 7) Get existing supporting files
const existingSupportingFiles = await hostService.getSupportingFilesByHeaderId(header.id);
console.log(`📁 Found ${existingSupportingFiles.length} existing supporting files`);
// 8) Parse incoming control fields
// fields.deletedFiles should be array like [{ id: number, url: string }, ...] or null
const deletedFiles: Array<{ id: number; url?: string }> = Array.isArray(fields.deletedFiles) ? fields.deletedFiles : [];
// fields.existingFiles can be an array of urls; we accept it but do not require it
const existingFilesFromFront: string[] = Array.isArray(fields.existingFiles) ? fields.existingFiles : [];
// Prepare response trackers
const deletedResults: Array<{ id: number; success: boolean; reason?: string }> = [];
const addedResults: Array<any> = [];
// 9) Handle explicit deletions (ONLY delete ids provided in deletedFiles)
if (deletedFiles.length > 0) {
console.log(`🗑️ Processing ${deletedFiles.length} explicit deletions`);
// Build a map of existing supporting files by id for quick lookup
const existingById = new Map<number, any>();
for (const f of existingSupportingFiles) {
existingById.set(f.id, f);
}
for (const del of deletedFiles) {
const id = Number(del.id);
if (!id || !existingById.has(id)) {
deletedResults.push({ id, success: false, reason: 'Not found or invalid id' });
continue;
}
const record = existingById.get(id);
try {
// delete from s3
if (record.mediaFileName) {
const s3Key = getS3KeyFromUrl(record.mediaFileName);
await deleteFromS3(s3Key);
}
// delete DB record
await hostService.deleteSupportingFile(record.id);
deletedResults.push({ id: record.id, success: true });
console.log(`🗑️ Deleted supporting file record ${record.id}`);
} catch (err: any) {
console.error(`❌ Failed to delete supporting file id ${id}`, err);
deletedResults.push({ id, success: false, reason: err.message || 'delete failed' });
}
}
} else {
console.log(' No explicit deletions requested (deletedFiles empty)');
}
// 10) Handle new uploaded files (these are ALWAYS added as new rows)
if (files.length > 0) {
console.log(`📤 Processing ${files.length} uploaded new file(s)`);
for (const file of files) {
try {
const url = await uploadToS3(
file.buffer,
file.mimeType,
file.fileName,
`ActivityOnboarding/supportings/${activityXid}`
);
// create DB record
const supporting = await hostService.addSupportingFile(
header.id,
file.mimeType,
url
);
addedResults.push(supporting);
console.log(`🆕 Created new supporting file record: ${supporting.id}`);
} catch (err: any) {
console.error('❌ Error uploading/creating supporting file', err);
// push failure result but continue processing other files
addedResults.push({ success: false, reason: err.message || 'upload/create failed' });
}
}
} else {
console.log('📭 No new files uploaded in request');
}
// NOTE: We DO NOT delete or modify existing supporting files that were not listed in deletedFiles.
// This satisfies your Case 2: "if no files are provided, do not touch existing supporting files".
const allPQPQuestionAnswerResponse = await hostService.getAllPQUpdatedResponse(activityXid)
// 11) Compose response
const responseMessage = existingHeader ? "PQQ answer updated successfully" : "PQQ answer submitted successfully";
return {
statusCode: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify({
success: true,
message: responseMessage,
data: {
responseOfUpdatedData: allPQPQuestionAnswerResponse,
operation: existingHeader ? 'updated' : 'created',
// summary label for UI convenience:
fileOperation: (deletedResults.length > 0 || addedResults.length > 0) ? 'modified' : 'unchanged'
}
})
};
} catch (error: any) {
console.error("❌ Error in submitPqqAnswer:", error);
throw error;
}
});

View File

@@ -0,0 +1,41 @@
import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { HostService } from '../../../services/host.service';
const prisma = new PrismaService();
const pqqService = new HostService(prisma);
export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try {
// 1) Auth
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) throw new ApiError(401, 'Missing token.');
const user = await verifyHostToken(token);
const activity_xid = event.queryStringParameters?.activity_xid
? Number(event.queryStringParameters.activity_xid)
: null;
await pqqService.submitpqqforreview(Number(activity_xid), Number(user.id))
return {
statusCode: 200,
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*"
},
body: JSON.stringify({
success: true,
message: "Your PQQ has been submitted for review.",
data: null
})
};
} catch (error: any) {
console.error("❌ Error in submitPqqAnswer:", error);
throw error;
}
});

View File

@@ -1,10 +1,10 @@
import config from '@/config/config'; import config from '../../../../../config/config';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import AWS from 'aws-sdk'; import AWS from 'aws-sdk';
import Busboy from 'busboy'; import Busboy from 'busboy';
import crypto from 'crypto'; import crypto from 'crypto';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
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';
@@ -30,34 +30,24 @@ async function deleteFromS3(s3Key: string): Promise<void> {
console.log(`✅ File deleted from S3: ${s3Key}`); console.log(`✅ File deleted from S3: ${s3Key}`);
} catch (error) { } catch (error) {
console.error(`❌ Error deleting file from S3: ${s3Key}`, error); console.error(`❌ Error deleting file from S3: ${s3Key}`, error);
// Don't throw error here, continue with upload // continue — we don't want S3 deletion failure to crash the whole request
} }
} }
async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string, existingUrl?: string): Promise<string> { async function uploadToS3(buffer: Buffer, mimeType: string, originalName: string, prefix: string, existingUrl?: string): Promise<string> {
let s3Key: string; // We intentionally do NOT reuse old key. If existingUrl is provided we delete old file and create a new random key.
// If existing URL provided, use the same S3 key to replace the file
// if (existingUrl) {
// s3Key = getS3KeyFromUrl(existingUrl);
// // Delete existing file first
// await deleteFromS3(s3Key);
// } else {
// // Generate new unique key for new file
// const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
// s3Key = `${prefix}/${uniqueKey}`;
// }
if (existingUrl) { if (existingUrl) {
// Delete old file, but DO NOT reuse its name try {
const oldKey = getS3KeyFromUrl(existingUrl); const oldKey = getS3KeyFromUrl(existingUrl);
await deleteFromS3(oldKey); await deleteFromS3(oldKey);
} catch (err) {
console.warn('Warning deleting existingUrl before upload', err);
}
} }
// Create new key always
const uniqueKey = `${crypto.randomUUID()}_${originalName}`; const uniqueKey = `${crypto.randomUUID()}_${originalName}`;
s3Key = `${prefix}/${uniqueKey}`; const s3Key = `${prefix}/${uniqueKey}`;
// Upload new file (replaces existing if same key)
await s3.upload({ await s3.upload({
Bucket: config.aws.bucketName, Bucket: config.aws.bucketName,
Key: s3Key, Key: s3Key,
@@ -82,7 +72,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
if (!contentType?.includes("multipart/form-data")) if (!contentType?.includes("multipart/form-data"))
throw new ApiError(400, "Content-Type must be multipart/form-data"); throw new ApiError(400, "Content-Type must be multipart/form-data");
// 3) Body decoding (FIXED same as addCompanyDetails) // 3) Body decoding
const bodyBuffer = event.isBase64Encoded const bodyBuffer = event.isBase64Encoded
? Buffer.from(event.body!, "base64") ? Buffer.from(event.body!, "base64")
: Buffer.from(event.body!, "binary"); : Buffer.from(event.body!, "binary");
@@ -90,7 +80,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
const fields: any = {}; const fields: any = {};
const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = []; const files: Array<{ buffer: Buffer; mimeType: string; fileName: string; fieldName: string }> = [];
// 4) Parse multipart data (FIXED using bb.write + bb.end exactly like working lambda) // 4) Parse multipart data
await new Promise<void>((resolve, reject) => { await new Promise<void>((resolve, reject) => {
const bb = Busboy({ headers: { 'content-type': contentType } }); const bb = Busboy({ headers: { 'content-type': contentType } });
@@ -152,41 +142,32 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
bb.end(); bb.end();
}); });
// 4) Extract required fields - only activityXid, pqqQuestionXid, pqqAnswerXid are required // 5) Extract required fields
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);
// Comments and files are optional
const comments = fields.comments || null; const comments = fields.comments || null;
// Validate required fields
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Valid activityXid is required"); if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Valid activityXid is required");
if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Valid pqqQuestionXid is required"); if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Valid pqqQuestionXid is required");
if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Valid pqqAnswerXid is required"); if (!pqqAnswerXid || isNaN(pqqAnswerXid)) throw new ApiError(400, "Valid pqqAnswerXid is required");
// console.log(`📝 Processing - Activity: ${activityXid}, Question: ${pqqQuestionXid}, Answer: ${pqqAnswerXid}`); // 6) UPSERT header
// console.log(`💬 Comments: ${comments ? 'Provided' : 'Not provided'}`);
// console.log(`📎 Files: ${files.length}`);
// 5) UPSERT: Check if header already exists for this combination
const existingHeader = await pqqService.findHeaderByCompositeKey( const existingHeader = await pqqService.findHeaderByCompositeKey(
activityXid, activityXid,
pqqQuestionXid, pqqQuestionXid,
pqqAnswerXid
); );
let header; let header;
if (existingHeader) { if (existingHeader) {
console.log("🔄 Updating existing PQQ header"); console.log("🔄 Updating existing PQQ header");
// Update existing header (comments can be null)
header = await pqqService.updateHeader( header = await pqqService.updateHeader(
existingHeader.id, existingHeader.id,
pqqAnswerXid,
comments comments
); );
} else { } else {
console.log("🆕 Creating new PQQ header"); console.log("🆕 Creating new PQQ header");
// Create new header (comments can be null)
header = await pqqService.createHeader( header = await pqqService.createHeader(
activityXid, activityXid,
pqqQuestionXid, pqqQuestionXid,
@@ -195,79 +176,93 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
); );
} }
// 6) Get existing supporting files for this header // 7) Get existing supporting files
const existingSupportingFiles = await pqqService.getSupportingFilesByHeaderId(header.id); const existingSupportingFiles = await pqqService.getSupportingFilesByHeaderId(header.id);
console.log(`📁 Found ${existingSupportingFiles.length} existing supporting files`); console.log(`📁 Found ${existingSupportingFiles.length} existing supporting files`);
// 7) Handle file UPSERT - only if files are provided // 8) Parse incoming control fields
const uploadedFiles: any[] = []; // fields.deletedFiles should be array like [{ id: number, url: string }, ...] or null
const deletedFiles: Array<{ id: number; url?: string }> = Array.isArray(fields.deletedFiles) ? fields.deletedFiles : [];
// fields.existingFiles can be an array of urls; we accept it but do not require it
const existingFilesFromFront: string[] = Array.isArray(fields.existingFiles) ? fields.existingFiles : [];
// Prepare response trackers
const deletedResults: Array<{ id: number; success: boolean; reason?: string }> = [];
const addedResults: Array<any> = [];
// 9) Handle explicit deletions (ONLY delete ids provided in deletedFiles)
if (deletedFiles.length > 0) {
console.log(`🗑️ Processing ${deletedFiles.length} explicit deletions`);
// Build a map of existing supporting files by id for quick lookup
const existingById = new Map<number, any>();
for (const f of existingSupportingFiles) {
existingById.set(f.id, f);
}
for (const del of deletedFiles) {
const id = Number(del.id);
if (!id || !existingById.has(id)) {
deletedResults.push({ id, success: false, reason: 'Not found or invalid id' });
continue;
}
const record = existingById.get(id);
try {
// delete from s3
if (record.mediaFileName) {
const s3Key = getS3KeyFromUrl(record.mediaFileName);
await deleteFromS3(s3Key);
}
// delete DB record
await pqqService.deleteSupportingFile(record.id);
deletedResults.push({ id: record.id, success: true });
console.log(`🗑️ Deleted supporting file record ${record.id}`);
} catch (err: any) {
console.error(`❌ Failed to delete supporting file id ${id}`, err);
deletedResults.push({ id, success: false, reason: err.message || 'delete failed' });
}
}
} else {
console.log(' No explicit deletions requested (deletedFiles empty)');
}
// 10) Handle new uploaded files (these are ALWAYS added as new rows)
if (files.length > 0) { if (files.length > 0) {
console.log("📤 Processing file uploads..."); console.log(`📤 Processing ${files.length} uploaded new file(s)`);
for (const file of files) {
for (let i = 0; i < files.length; i++) { try {
const file = files[i]; const url = await uploadToS3(
const existingFile = existingSupportingFiles[i] || null; file.buffer,
const url = await uploadToS3(
file.buffer,
file.mimeType,
file.fileName,
`ActivityOnboarding/supportings/${activityXid}`,
existingFile ? existingFile.mediaFileName : undefined
);
let supporting;
if (existingFile) {
// Update existing supporting file record
supporting = await pqqService.updateSupportingFile(
existingFile.id,
file.mimeType, file.mimeType,
url file.fileName,
`ActivityOnboarding/supportings/${activityXid}`
); );
console.log(`🔄 Updated supporting file: ${existingFile.id}`);
} else { // create DB record
// Create new supporting file record const supporting = await pqqService.addSupportingFile(
supporting = await pqqService.addSupportingFile(
header.id, header.id,
file.mimeType, file.mimeType,
url url
); );
console.log(`🆕 Created new supporting file: ${supporting.id}`);
}
uploadedFiles.push(supporting); addedResults.push(supporting);
} console.log(`🆕 Created new supporting file record: ${supporting.id}`);
} catch (err: any) {
// 8) Delete any remaining existing files that weren't replaced console.error('❌ Error uploading/creating supporting file', err);
if (existingSupportingFiles.length > files.length) { // push failure result but continue processing other files
const filesToDelete = existingSupportingFiles.slice(files.length); addedResults.push({ success: false, reason: err.message || 'upload/create failed' });
console.log(`🗑️ Deleting ${filesToDelete.length} unused supporting files`);
for (const fileToDelete of filesToDelete) {
await pqqService.deleteSupportingFile(fileToDelete.id);
// Also delete from S3
const s3Key = getS3KeyFromUrl(fileToDelete.mediaFileName);
await deleteFromS3(s3Key);
console.log(`🗑️ Deleted supporting file: ${fileToDelete.id}`);
} }
} }
} else { } else {
console.log("📭 No files provided in request"); console.log('📭 No new files uploaded in request');
// If no files provided but existing files exist, delete them (cleanup)
if (existingSupportingFiles.length > 0) {
console.log(`🗑️ No new files provided, deleting ${existingSupportingFiles.length} existing files`);
for (const fileToDelete of existingSupportingFiles) {
await pqqService.deleteSupportingFile(fileToDelete.id);
const s3Key = getS3KeyFromUrl(fileToDelete.mediaFileName);
await deleteFromS3(s3Key);
console.log(`🗑️ Deleted supporting file: ${fileToDelete.id}`);
}
}
} }
// 9) Prepare response // NOTE: We DO NOT delete or modify existing supporting files that were not listed in deletedFiles.
// This satisfies your Case 2: "if no files are provided, do not touch existing supporting files".
// 11) Compose response
const responseMessage = existingHeader ? "PQQ answer updated successfully" : "PQQ answer submitted successfully"; const responseMessage = existingHeader ? "PQQ answer updated successfully" : "PQQ answer submitted successfully";
return { return {
@@ -284,15 +279,15 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
activityXid, activityXid,
pqqQuestionXid, pqqQuestionXid,
pqqAnswerXid, pqqAnswerXid,
comments: comments, comments,
files: { files: {
uploaded: uploadedFiles, added: addedResults,
total: uploadedFiles.length deleted: deletedResults,
existingKeptCount: (existingSupportingFiles.length - deletedResults.filter(d => d.success).length)
}, },
operation: existingHeader ? 'updated' : 'created', operation: existingHeader ? 'updated' : 'created',
fileOperation: files.length > 0 ? // summary label for UI convenience:
(existingSupportingFiles.length > 0 ? 'replaced' : 'added') : fileOperation: (deletedResults.length > 0 || addedResults.length > 0) ? 'modified' : 'unchanged'
(existingSupportingFiles.length > 0 ? 'removed' : 'unchanged')
} }
}) })
}; };
@@ -301,4 +296,4 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
console.error("❌ Error in submitPqqAnswer:", error); console.error("❌ Error in submitPqqAnswer:", error);
throw error; throw error;
} }
}); });

View File

@@ -1,4 +1,4 @@
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';

View File

@@ -1,4 +1,4 @@
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';

View File

@@ -3,7 +3,7 @@ import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
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';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const hostService = new HostService(prismaService); const hostService = new HostService(prismaService);

View File

@@ -40,15 +40,6 @@ export const handler = safeHandler(async (
throw new ApiError(401, 'Invalid credentials'); throw new ApiError(401, 'Invalid credentials');
} }
const matchPassword = await bcrypt.compare(
userPassword,
loginForHost.userPassword
);
if (!matchPassword) {
throw new ApiError(401, 'Invalid credentials');
}
const generateTokenForHost = await tokenService.generateAuthToken( const generateTokenForHost = await tokenService.generateAuthToken(
loginForHost.id loginForHost.id
); );

View File

@@ -3,7 +3,7 @@ import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { MinglarService } from '../../../../minglaradmin/services/minglar.service'; import { MinglarService } from '../../../../minglaradmin/services/minglar.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';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);

View File

@@ -1,13 +1,12 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import * as bcrypt from 'bcryptjs';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
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 * as bcrypt from 'bcryptjs';
import { OtpGeneratorSixDigit } from '../../../../../common/utils/helper/OtpGenerator';
import { encryptUserId } from '../../../../../common/utils/helper/CodeGenerator'; import { encryptUserId } from '../../../../../common/utils/helper/CodeGenerator';
import { OtpGeneratorSixDigit } from '../../../../../common/utils/helper/OtpGenerator';
import { HostService } from '../../../services/host.service'; import { HostService } from '../../../services/host.service';
import { sendOtpEmailForHost } from '../../../services/sendOTPEmail.service';
import { ROLE } from '../../../../../common/utils/constants/common.constant';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const hostService = new HostService(prismaService); const hostService = new HostService(prismaService);

View File

@@ -1,10 +1,10 @@
// modules/host/handlers/addCompanyDetails.ts // modules/host/handlers/addCompanyDetails.ts
import config from '@/config/config'; import config from '../../../../../config/config';
import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import AWS from 'aws-sdk'; import AWS from 'aws-sdk';
import Busboy from 'busboy'; import Busboy from 'busboy';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost'; import { verifyHostToken } from '../../../../../common/middlewares/jwt/authForHost';
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 { import {
@@ -50,6 +50,25 @@ function cleanEmptyStrings(obj: any) {
return cleaned; return cleaned;
} }
function getS3KeyFromUrl(url: string): string {
const base = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
return url.replace(base, "");
}
async function deleteFromS3(key: string) {
try {
await s3.deleteObject({
Bucket: config.aws.bucketName,
Key: key
}).promise();
console.log("✅ Deleted from S3:", key);
} catch (err) {
console.error("❌ Failed to delete from S3:", key, err);
}
}
export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
try { try {
@@ -112,6 +131,9 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
bb.end(); bb.end();
}); });
const deletedFiles = normalizeJsonField(fields, "deletedFiles") || [];
const parentDeletedFiles = normalizeJsonField(fields, "parentDeletedFiles") || [];
/** 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;
@@ -178,13 +200,8 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
const file = files.find((f) => f.fieldName === doc.fieldName); const file = files.find((f) => f.fieldName === doc.fieldName);
// In DRAFT mode → allow missing documents // In DRAFT mode → allow missing documents
if (isDraft && !file) {
return { ...doc, file: null };
}
// In FINAL mode → file must exist
if (!file) { if (!file) {
throw new ApiError(400, `File not found for field: ${doc.fieldName}`); return { ...doc, file: null };
} }
return { ...doc, file }; return { ...doc, file };
@@ -213,6 +230,63 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
parsedParentCompany = parsedCompany.parentCompany || null; parsedParentCompany = parsedCompany.parentCompany || null;
} }
/** 9.5) DELETE DOCUMENTS IF REQUESTED **/
if (Array.isArray(deletedFiles) && deletedFiles.length > 0) {
console.log(`🗑️ Deleting ${deletedFiles.length} documents...`);
for (const del of deletedFiles) {
const id = Number(del.id);
const url = del.url;
if (!id || !url) {
console.log("❌ Invalid delete entry:", del);
continue;
}
// Extract S3 key
const s3Key = getS3KeyFromUrl(url);
// Delete from S3
await deleteFromS3(s3Key);
// Delete from DB
await prisma.hostDocuments.delete({
where: { id }
});
console.log(`🗑️ Deleted host document ID ${id}`);
}
}
/** 9.6) DELETE PARENT DOCUMENTS **/
if (parsedCompany.isSubsidairy && Array.isArray(parentDeletedFiles) && parentDeletedFiles.length > 0) {
console.log(`🗑️ Deleting ${parentDeletedFiles.length} PARENT documents...`);
for (const del of parentDeletedFiles) {
const id = Number(del.id);
const url = del.url;
if (!id || !url) {
console.log("⚠️ Invalid parent delete entry:", del);
continue;
}
const s3Key = getS3KeyFromUrl(url);
// Delete S3
await deleteFromS3(s3Key);
// Delete DB
await prisma.hostParenetDocuments.delete({
where: { id }
});
console.log(`🗑️ Deleted PARENT document ID ${id}`);
}
}
/** 11) UPLOAD DOCUMENTS */ /** 11) UPLOAD DOCUMENTS */
async function uploadToS3(buffer, mimeType, originalName, folderType, documentTypeXid?, fieldName?) { async function uploadToS3(buffer, mimeType, originalName, folderType, documentTypeXid?, fieldName?) {
const ext = originalName.split('.').pop() || 'jpg'; const ext = originalName.split('.').pop() || 'jpg';
@@ -249,7 +323,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
/** Upload host docs */ /** Upload host docs */
const uploadedHostDocs: Array<any> = []; const uploadedHostDocs: Array<any> = [];
for (const doc of hostDocs) { for (const doc of hostDocs) {
if (isDraft && !doc.file) continue; if (!doc.file) continue;
const path = await uploadToS3( const path = await uploadToS3(
doc.file.buffer, doc.file.buffer,
@@ -270,7 +344,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
/** Upload parent docs */ /** Upload parent docs */
const uploadedParentDocs: Array<any> = []; const uploadedParentDocs: Array<any> = [];
for (const doc of parentDocs) { for (const doc of parentDocs) {
if (!doc.file && isDraft) continue; // skip missing files in draft mode if (!doc.file) continue; // skip missing files in draft mode
const path = await uploadToS3( const path = await uploadToS3(
doc.file.buffer, doc.file.buffer,

View File

@@ -3,8 +3,8 @@ import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
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 { hostBankDetailsSchema } from '@/common/utils/validation/host/addPaymentDetails.validation'; import { hostBankDetailsSchema } from '../../../../../common/utils/validation/host/addPaymentDetails.validation';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const hostService = new HostService(prismaService); const hostService = new HostService(prismaService);
@@ -33,7 +33,7 @@ export const handler = safeHandler(async (
} }
// Parse request body // Parse request body
let body: { bankXid?: number; bankBranchXid?: number; accountNumber?: string; confirmAccountNumber?: string; accountHolderName?: string; ifscCode?: string; currencyXid?: number }; let body: { bankXid?: number; bankBranchXid?: number; accountNumber?: string; confirmAccountNumber?: string; accountHolderName?: string; currencyXid?: number };
try { try {
body = event.body ? JSON.parse(event.body) : {}; body = event.body ? JSON.parse(event.body) : {};
@@ -54,7 +54,16 @@ export const handler = safeHandler(async (
const validatedData = validationResult.data; const validatedData = validationResult.data;
await hostService.addPaymentDetails(validatedData); // Fetch IFSC code from bank branch
const bankBranch = await hostService.getBankBranchById(validatedData.bankBranchXid);
if (!bankBranch) {
throw new ApiError(404, 'Bank branch not found');
}
await hostService.addPaymentDetails({
...validatedData,
ifscCode: bankBranch.ifscCode,
});
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -26,7 +26,7 @@ export const handler = safeHandler(async (
} }
// Fetch user with their HostHeader stepper info // Fetch user with their HostHeader stepper info
const host = await hostService.getHostById(userId); const host = await hostService.getHostIdByUserXid(userId);
if (!host) { if (!host) {
throw new ApiError(404, 'Host record not found'); throw new ApiError(404, 'Host record not found');

View File

@@ -1,4 +1,4 @@
import { verifyMinglarAdminHostToken } from '@/common/middlewares/jwt/authForMinglarAdmin&Host'; import { verifyMinglarAdminHostToken } from '../../../common/middlewares/jwt/authForMinglarAdminHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { PrismaService } from '../../../common/database/prisma.service';
import { safeHandler } from '../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../common/utils/handlers/safeHandler';

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,9 @@
import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../common/database/prisma.service'; import { PrismaService } from '../../../common/database/prisma.service';
import { MinglarService } from '../services/minglar.service'; import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError'; import ApiError from '../../../common/utils/helper/ApiError';
import { verifyHostToken } from '../../../common/middlewares/jwt/authForHost'; import { MinglarService } from '../services/minglar.service';
import { verifyMinglarAdminToken } from '@/common/middlewares/jwt/authForMinglarAdmin';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);

View File

@@ -3,11 +3,11 @@ import {
APIGatewayProxyResult, APIGatewayProxyResult,
Context, Context,
} from 'aws-lambda'; } from 'aws-lambda';
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../common/database/prisma.service'; import { PrismaService } from '../../../common/database/prisma.service';
import { MinglarService } from '../services/minglar.service';
import ApiError from '../../../common/utils/helper/ApiError';
import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin';
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError';
import { MinglarService } from '../services/minglar.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);

View File

@@ -3,8 +3,8 @@ import { PrismaService } from '../../../../../common/database/prisma.service';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
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 { sendEmailToHostForApprovedApplication } from '../../../services/approvalMailtoHost.service';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import { sendEmailToHostForApprovedApplication } from '../../../services/approvalMailtoHost.service'
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);

View File

@@ -0,0 +1,43 @@
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { MinglarService } from '../../../services/minglar.service';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService);
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(401, 'This is a protected route. Please provide a valid token.');
const userInfo = await verifyMinglarAdminToken(token);
const activityId = event.pathParameters?.activityId;
if (!activityId) {
throw new ApiError(400, 'activityId is required');
}
await minglarService.acceptPQByAM(
Number(activityId),
Number(userInfo.id)
);
return {
statusCode: 201,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Approved PQ successfully',
data: null,
}),
};
});

View File

@@ -1,10 +1,10 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { MinglarService } from '../../../services/minglar.service';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { HOST_SUGGESTION_TITLES } from '../../../../../common/utils/constants/minglar.constant'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service';
// import { HOST_SUGGESTION_TITLES } from '../../../../../common/utils/constants/minglar.constant';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);
@@ -67,10 +67,10 @@ export const handler = safeHandler(async (
} }
// Validate title is one of the allowed types // Validate title is one of the allowed types
const allowedTitles = Object.values(HOST_SUGGESTION_TITLES); // const allowedTitles = Object.values(HOST_SUGGESTION_TITLES);
if (!allowedTitles.includes(title)) { // if (!allowedTitles.includes(title)) {
throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`); // throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`);
} // }
// Add suggestion using service // Add suggestion using service
await minglarService.addPqqSuggestion(title, comments, activity_pqq_header_xid,user.id); await minglarService.addPqqSuggestion(title, comments, activity_pqq_header_xid,user.id);

View File

@@ -1,10 +1,9 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { MinglarService } from '../../../services/minglar.service';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { HOST_SUGGESTION_TITLES } from '../../../../../common/utils/constants/minglar.constant'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);
@@ -13,6 +12,7 @@ interface AddSuggestionBody {
hostXid: number; hostXid: number;
title: string; title: string;
comments: string; comments: string;
isParent: boolean;
} }
/** /**
@@ -52,7 +52,7 @@ export const handler = safeHandler(async (
throw new ApiError(400, 'Invalid JSON in request body'); throw new ApiError(400, 'Invalid JSON in request body');
} }
const { hostXid, title, comments } = body; const { hostXid, title, comments, isParent } = body;
// Validate required fields // Validate required fields
if (!hostXid) { if (!hostXid) {
@@ -68,13 +68,13 @@ export const handler = safeHandler(async (
} }
// Validate title is one of the allowed types // Validate title is one of the allowed types
const allowedTitles = Object.values(HOST_SUGGESTION_TITLES); // const allowedTitles = Object.values(HOST_SUGGESTION_TITLES);
if (!allowedTitles.includes(title)) { // if (!allowedTitles.includes(title)) {
throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`); // throw new ApiError(400, `Invalid title. Allowed values: ${allowedTitles.join(', ')}`);
} // }
// Add suggestion using service // Add suggestion using service
await minglarService.addHostSuggestion(hostXid, title, comments, user.id); await minglarService.addHostSuggestion(hostXid, title, comments, user.id, isParent);
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -1,10 +1,10 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { MinglarService } from '../../../services/minglar.service';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { paginationService } from '../../../../../common/utils/pagination/pagination.service'; import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);
@@ -43,6 +43,7 @@ export const handler = safeHandler(async (
// Parse pagination parameters // Parse pagination parameters
const paginationParams = paginationService.getPaginationFromEvent(event); const paginationParams = paginationService.getPaginationFromEvent(event);
const paginationOptions = paginationService.parsePaginationParams(paginationParams); const paginationOptions = paginationService.parsePaginationParams(paginationParams);
const applicationStatus = event.queryStringParameters?.applicationStatus || '';
// Get paginated host applications // Get paginated host applications
const { data, totalCount } = await minglarService.getAllHostApplications( const { data, totalCount } = await minglarService.getAllHostApplications(
@@ -51,7 +52,8 @@ export const handler = safeHandler(async (
search, search,
userStatus, userStatus,
paginationOptions, paginationOptions,
roleFilter roleFilter,
applicationStatus
); );
// Create paginated response // Create paginated response

View File

@@ -1,10 +1,9 @@
import { verifyMinglarAdminHostToken } from '@/common/middlewares/jwt/authForMinglarAdmin&Host'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import { PrismaService } from '@/common/database/prisma.service';
import { safeHandler } from '@/common/utils/handlers/safeHandler';
import ApiError from '@/common/utils/helper/ApiError';
import { verifyMinglarAdminToken } from '@/common/middlewares/jwt/authForMinglarAdmin';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);

View File

@@ -1,4 +1,4 @@
import { verifyMinglarAdminToken } from '@/common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';

View File

@@ -1,9 +1,9 @@
import { verifyMinglarAdminToken } from '@/common/middlewares/jwt/authForMinglarAdmin';
import { MinglarService } from '@/modules/minglaradmin/services/minglar.service';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
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 { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);
@@ -17,14 +17,7 @@ export const handler = safeHandler(async (
const userInfo = await verifyMinglarAdminToken(token); const userInfo = await verifyMinglarAdminToken(token);
let body: any = {}; const activityId = event.pathParameters?.activityId;
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (err) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { activityId } = body;
if (!activityId) { if (!activityId) {
throw new ApiError(400, 'activityId is required'); throw new ApiError(400, 'activityId is required');
@@ -32,6 +25,7 @@ export const handler = safeHandler(async (
await minglarService.rejectPQQbyAM( await minglarService.rejectPQQbyAM(
Number(activityId), Number(activityId),
Number(userInfo.id)
); );
return { return {
@@ -42,7 +36,7 @@ export const handler = safeHandler(async (
}, },
body: JSON.stringify({ body: JSON.stringify({
success: true, success: true,
message: 'Rejected successfully', message: 'Rejected PQ successfully',
data: null, data: null,
}), }),
}; };

View File

@@ -1,68 +0,0 @@
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { sendEmailToHostForMinglarApproval } from '../../../services/approvalMailtoHost.service';
import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService);
interface AddSuggestionBody {
hostXid: number;
title: string;
comments: string;
}
/**
* Add suggestion handler for host applications
* Allows Minglar Admin, Co_Admin, and Account Manager to add suggestions
* Types: Setup Profile, Review Account, Add Payment Details, Agreement
*/
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Verify authentication token
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) {
throw new ApiError(401, 'This is a protected route. Please provide a valid token.');
}
// Verify token and get user info
const userInfo = await verifyOnlyMinglarAdminToken(token);
// Parse request body
let body: AddSuggestionBody;
try {
body = event.body ? JSON.parse(event.body) : {};
} catch (error) {
throw new ApiError(400, 'Invalid JSON in request body');
}
const { hostXid } = body;
// Add suggestion using service
await minglarService.acceptHostApplicationMinglarAdmin(hostXid, userInfo.id);
const hostDetails = await minglarService.getUserDetails(userInfo.id)
if (!hostDetails?.emailAddress) {
throw new ApiError(404, 'Host details or email address not found');
}
await sendEmailToHostForMinglarApproval(hostDetails.emailAddress)
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Application accepted successfully',
data: null,
}),
};
});

View File

@@ -3,12 +3,11 @@ import {
APIGatewayProxyResult, APIGatewayProxyResult,
Context, Context,
} from 'aws-lambda'; } from 'aws-lambda';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { MinglarService } from '../../../services/minglar.service';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { sendAMEmailForHostAssign } from '../../../services/AMEmail.service'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);

View File

@@ -1,4 +1,4 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
@@ -64,8 +64,9 @@ export const handler = safeHandler(async (
payoutDurationFrequency payoutDurationFrequency
} = body; } = body;
await minglarService.editAgreementDetails( await minglarService.acceptHostApplicationMinglarAdmin(
host_xid, host_xid,
userInfo.id,
agreementStartDate, agreementStartDate,
duration, duration,
isCommisionBase, isCommisionBase,
@@ -73,8 +74,8 @@ export const handler = safeHandler(async (
amountPerBooking, amountPerBooking,
durationFrequency, durationFrequency,
payoutDurationNum, payoutDurationNum,
payoutDurationFrequency payoutDurationFrequency);
); // await sendEmailToHostForMinglarApproval(hostDetails.emailAddress)
return { return {
statusCode: 200, statusCode: 200,

View File

@@ -1,10 +1,11 @@
import { verifyMinglarAdminToken } from '@/common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
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 { PrePopulateService } from '../../../../prepopulate/services/prepopulate.service'; import { PrePopulateService } from '../../../../prepopulate/services/prepopulate.service';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);
@@ -30,11 +31,18 @@ export const handler = safeHandler(async (
const hostXid = Number(event.pathParameters?.id) const hostXid = Number(event.pathParameters?.id)
// Get pagination params from event
const paginationParams = paginationService.getPaginationFromEvent(event);
const paginationOptions = paginationService.parsePaginationParams(paginationParams);
// Read optional search query (supports ?search= or ?q=) // Read optional search query (supports ?search= or ?q=)
const search = event.queryStringParameters?.search || event.queryStringParameters?.q || undefined; const search = event.queryStringParameters?.search || event.queryStringParameters?.q || undefined;
const data = await minglarService.getAllHostActivityForMinglar(search ? String(search) : undefined, hostXid); const result = await minglarService.getAllHostActivityForMinglar(
search ? String(search) : undefined,
hostXid,
paginationOptions
);
return { return {
@@ -46,7 +54,7 @@ export const handler = safeHandler(async (
body: JSON.stringify({ body: JSON.stringify({
success: true, success: true,
message: 'Data retrieved successfully', message: 'Data retrieved successfully',
data, ...result,
}), }),
}; };
}); });

View File

@@ -1,8 +1,13 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
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 { paginationService } from '../../../../../common/utils/pagination/pagination.service';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
@@ -12,42 +17,70 @@ const minglarService = new MinglarService(prismaService);
* Get all host applications handler * Get all host applications handler
* Returns host details with status, submission date, and account manager info * Returns host details with status, submission date, and account manager info
*/ */
export const handler = safeHandler(async ( export const handler = safeHandler(
event: APIGatewayProxyEvent, async (
context?: Context event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> => { context?: Context,
// Verify authentication token ): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']; // Verify authentication token
if (!token) { const token =
throw new ApiError(401, 'This is a protected route. Please provide a valid token.'); event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
} if (!token) {
throw new ApiError(
401,
'This is a protected route. Please provide a valid token.',
);
}
// Verify token and get user info // Verify token and get user info
const userInfo = await verifyOnlyMinglarAdminToken(token); const userInfo = await verifyOnlyMinglarAdminToken(token);
// Get user details including role // Get user details including role
const user = await prismaService.user.findUnique({ const user = await prismaService.user.findUnique({
where: { id: userInfo.id }, where: { id: userInfo.id },
select: { id: true, roleXid: true } select: { id: true, roleXid: true },
}); });
if (!user) { if (!user) {
throw new ApiError(404, 'User not found'); throw new ApiError(404, 'User not found');
} }
// Get all host applications from service based on user role // Extract optional search query
const hostApplications = await minglarService.getAllOnboardingHostApplications(); const queryParams = event.queryStringParameters || {};
const search =
(queryParams.search as string) ||
(queryParams.q as string) ||
undefined;
return { // Pagination
statusCode: 200, const paginationParams = paginationService.getPaginationFromEvent(event);
headers: { const paginationOptions =
'Content-Type': 'application/json', paginationService.parsePaginationParams(paginationParams);
'Access-Control-Allow-Origin': '*',
}, // Get all host applications from service based on user role
body: JSON.stringify({ const { data, totalCount } =
success: true, await minglarService.getAllOnboardingHostApplications(
message: 'Host applications retrieved successfully', paginationOptions,
data: hostApplications, search,
}), );
};
}); const paginatedResponse = paginationService.createPaginatedResponse(
data,
totalCount,
paginationOptions,
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Host applications retrieved successfully',
...paginatedResponse,
}),
};
},
);

View File

@@ -1,25 +1,35 @@
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin'; import {
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
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 { paginationService } from '../../../../../common/utils/pagination/pagination.service';
import { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);
/** /**
* Get all host applications handler * Get all NEW host applications handler
* Returns host details with status, submission date, and account manager info * Returns host details with status, submission date, and account manager info
*/ */
export const handler = safeHandler(async ( export const handler = safeHandler(
async (
event: APIGatewayProxyEvent, event: APIGatewayProxyEvent,
context?: Context context?: Context,
): Promise<APIGatewayProxyResult> => { ): Promise<APIGatewayProxyResult> => {
// Verify authentication token // Verify authentication token
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token']; const token =
event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
if (!token) { if (!token) {
throw new ApiError(401, 'This is a protected route. Please provide a valid token.'); throw new ApiError(
401,
'This is a protected route. Please provide a valid token.',
);
} }
// Verify token and get user info // Verify token and get user info
@@ -27,27 +37,50 @@ export const handler = safeHandler(async (
// Get user details including role // Get user details including role
const user = await prismaService.user.findUnique({ const user = await prismaService.user.findUnique({
where: { id: userInfo.id }, where: { id: userInfo.id },
select: { id: true, roleXid: true } select: { id: true, roleXid: true },
}); });
if (!user) { if (!user) {
throw new ApiError(404, 'User not found'); throw new ApiError(404, 'User not found');
} }
// Extract optional search query
const queryParams = event.queryStringParameters || {};
const search =
(queryParams.search as string) ||
(queryParams.q as string) ||
undefined;
// Pagination
const paginationParams = paginationService.getPaginationFromEvent(event);
const paginationOptions =
paginationService.parsePaginationParams(paginationParams);
// Get all host applications from service based on user role // Get all host applications from service based on user role
const hostApplications = await minglarService.getAllOnboardingHostApplications_New(); const { data, totalCount } =
await minglarService.getAllOnboardingHostApplications_New(
paginationOptions,
search,
);
const paginatedResponse = paginationService.createPaginatedResponse(
data,
totalCount,
paginationOptions,
);
return { return {
statusCode: 200, statusCode: 200,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
}, },
body: JSON.stringify({ body: JSON.stringify({
success: true, success: true,
message: 'Host applications retrieved successfully', message: 'Host applications retrieved successfully',
data: hostApplications, ...paginatedResponse,
}), }),
}; };
}); },
);

View File

@@ -1,6 +1,6 @@
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
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 { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';

View File

@@ -0,0 +1,50 @@
import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';
import ApiError from '../../../../../common/utils/helper/ApiError';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { MinglarService } from '../../../services/minglar.service';
import { verifyMinglarAdminHostToken } from '../../../../../common/middlewares/jwt/authForMinglarAdminHost';
const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService);
export const handler = safeHandler(async (
event: APIGatewayProxyEvent,
context?: Context
): Promise<APIGatewayProxyResult> => {
// Get host ID from path parameters
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.');
}
await verifyMinglarAdminHostToken(token);
const activityXid = event.pathParameters?.activityXid;
if (!activityXid) {
throw new ApiError(
400,
'Host ID is required in path parameters.',
);
}
const pqpDetails = await minglarService.getAllPQPDetailsForAM(Number(activityXid));
if (!pqpDetails) {
throw new ApiError(404, 'Record not found');
}
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'PQ details retrieved successfully',
data: pqpDetails,
}),
};
});

View File

@@ -3,11 +3,11 @@ import {
APIGatewayProxyResult, APIGatewayProxyResult,
Context, Context,
} from 'aws-lambda'; } from 'aws-lambda';
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import { PrismaService } from '../../../common/database/prisma.service'; import { PrismaService } from '../../../common/database/prisma.service';
import ApiError from '../../../common/utils/helper/ApiError';
import { ROLE } from '../../../common/utils/constants/common.constant';
import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin'; import { verifyMinglarAdminToken } from '../../../common/middlewares/jwt/authForMinglarAdmin';
import { ROLE } from '../../../common/utils/constants/common.constant';
import { safeHandler } from '../../../common/utils/handlers/safeHandler';
import ApiError from '../../../common/utils/helper/ApiError';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
export const handler = safeHandler( export const handler = safeHandler(

View File

@@ -1,10 +1,9 @@
import { ROLE, USER_STATUS } from '@/common/utils/constants/common.constant'; import { ROLE, USER_STATUS } from '../../../common/utils/constants/common.constant';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { PrismaService } from '../../../common/database/prisma.service';
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 { generateOtpHelper } from '../../../common/utils/helper/sendOtp'; import { generateOtpHelper } from '../../../common/utils/helper/sendOtp';
import { sendOtpEmailForMinglarAdmin } from '../services/sendOTPEmail.service';
import { MinglarService } from './../services/minglar.service'; import { MinglarService } from './../services/minglar.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();

View File

@@ -1,4 +1,4 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { safeHandler } from '../../../../../common/utils/handlers/safeHandler'; import { safeHandler } from '../../../../../common/utils/handlers/safeHandler';

View File

@@ -1,42 +1,65 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
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 { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(
event: APIGatewayProxyEvent, async (
context?: Context event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> => { context?: Context,
// Extract token from headers ): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'] // Extract token from headers
if(!token) { const token =
throw new ApiError(400, 'This is a protected route. Please provide a valid token.'); event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
} if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
// Authenticate user using the shared authForHost function // Authenticate user using the shared authForHost function
await verifyOnlyMinglarAdminToken(token); await verifyOnlyMinglarAdminToken(token);
// Extract search parameter from query string // Extract search parameter from query string
const search = event.queryStringParameters?.search || ''; const search = event.queryStringParameters?.search || '';
const result = await minglarService.getAllInvitationDetails(search); const paginationParams = paginationService.getPaginationFromEvent(event);
const paginationOptions =
paginationService.parsePaginationParams(paginationParams);
return { const { data, totalCount } = await minglarService.getAllInvitationDetails(
statusCode: 200, search,
headers: { paginationOptions,
'Content-Type': 'application/json', );
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data: result,
}),
};
});
const paginatedResponse = paginationService.createPaginatedResponse(
data,
totalCount,
paginationOptions,
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
...paginatedResponse,
}),
};
},
);

View File

@@ -1,42 +1,64 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import {
APIGatewayProxyEvent,
APIGatewayProxyResult,
Context,
} from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
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 { MinglarService } from '../../../services/minglar.service'; import { MinglarService } from '../../../services/minglar.service';
import { paginationService } from '../../../../../common/utils/pagination/pagination.service';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const minglarService = new MinglarService(prismaService); const minglarService = new MinglarService(prismaService);
export const handler = safeHandler(async ( export const handler = safeHandler(
event: APIGatewayProxyEvent, async (
context?: Context event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> => { context?: Context,
// Extract token from headers ): Promise<APIGatewayProxyResult> => {
const token = event.headers['x-auth-token'] || event.headers['X-Auth-Token'] // Extract token from headers
if(!token) { const token =
throw new ApiError(400, 'This is a protected route. Please provide a valid token.'); event.headers['x-auth-token'] || event.headers['X-Auth-Token'];
} if (!token) {
throw new ApiError(
400,
'This is a protected route. Please provide a valid token.',
);
}
// Authenticate user using the shared authForHost function // Authenticate user using the shared authForHost function
await verifyOnlyMinglarAdminToken(token); await verifyOnlyMinglarAdminToken(token);
// Extract search parameter from query string // Extract search parameter from query string
const search = event.queryStringParameters?.search || ''; const search = event.queryStringParameters?.search || '';
const response = await minglarService.getAllInvitedCoadminAndAM(search); // Pagination
const paginationParams = paginationService.getPaginationFromEvent(event);
const paginationOptions =
paginationService.parsePaginationParams(paginationParams);
return { const { data, totalCount } =
statusCode: 200, await minglarService.getAllInvitedCoadminAndAM(search, paginationOptions);
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
data: response,
}),
};
});
const paginatedResponse = paginationService.createPaginatedResponse(
data,
totalCount,
paginationOptions,
);
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
body: JSON.stringify({
success: true,
message: 'Data retrieved successfully',
...paginatedResponse,
}),
};
},
);

View File

@@ -1,5 +1,4 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyOnlyMinglarAdminToken } from '../../../../../common/middlewares/jwt/authForOnlyMinglarAdmin';
import { MINGLAR_INVITATION_STATUS } from '@/common/utils/constants/minglar.constant';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../../../common/database/prisma.service'; import { PrismaService } from '../../../../../common/database/prisma.service';
import { ROLE } from '../../../../../common/utils/constants/common.constant'; import { ROLE } from '../../../../../common/utils/constants/common.constant';

View File

@@ -1,5 +1,5 @@
// modules/minglar/handlers/updateProfile.ts // modules/minglar/handlers/updateProfile.ts
import config from '@/config/config'; import config from '../../../config/config';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import AWS from 'aws-sdk'; import AWS from 'aws-sdk';
import { PrismaService } from '../../../common/database/prisma.service'; import { PrismaService } from '../../../common/database/prisma.service';

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,9 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin'; import { verifyMinglarAdminHostToken } from '../../../common/middlewares/jwt/authForMinglarAdminHost';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { PrismaService } from '../../../common/database/prisma.service';
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 { PrePopulateService } from '../services/prepopulate.service'; import { PrePopulateService } from '../services/prepopulate.service';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const prePopulateService = new PrePopulateService(prismaService); const prePopulateService = new PrePopulateService(prismaService);
@@ -20,7 +19,7 @@ export const handler = safeHandler(async (
} }
// Authenticate user using the shared authForHost function // Authenticate user using the shared authForHost function
await verifyHostToken(token); await verifyMinglarAdminHostToken(token);
const bankDetails = await prePopulateService.getAllBankDetails(); const bankDetails = await prePopulateService.getAllBankDetails();

View File

@@ -1,6 +1,6 @@
import { verifyMinglarAdminHostToken } from '@/common/middlewares/jwt/authForMinglarAdmin&Host';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { PrismaService } from '../../../common/database/prisma.service';
import { verifyMinglarAdminHostToken } from '../../../common/middlewares/jwt/authForMinglarAdminHost';
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 { PrePopulateService } from '../services/prepopulate.service'; import { PrePopulateService } from '../services/prepopulate.service';

View File

@@ -1,10 +1,9 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { PrismaService } from '../../../common/database/prisma.service';
import { verifyMinglarAdminHostToken } from '../../../common/middlewares/jwt/authForMinglarAdminHost';
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 { PrePopulateService } from '../services/prepopulate.service'; import { PrePopulateService } from '../services/prepopulate.service';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const prePopulateService = new PrePopulateService(prismaService); const prePopulateService = new PrePopulateService(prismaService);
@@ -20,7 +19,7 @@ export const handler = safeHandler(async (
} }
// Authenticate user using the shared authForHost function // Authenticate user using the shared authForHost function
await verifyHostToken(token); await verifyMinglarAdminHostToken(token);
const result = await prePopulateService.getAllFrequencies(); const result = await prePopulateService.getAllFrequencies();

View File

@@ -1,10 +1,9 @@
import { verifyOnlyMinglarAdminToken } from '@/common/middlewares/jwt/authForOnlyMinglarAdmin';
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda'; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from 'aws-lambda';
import { PrismaService } from '../../../common/database/prisma.service'; import { PrismaService } from '../../../common/database/prisma.service';
import { verifyMinglarAdminHostToken } from '../../../common/middlewares/jwt/authForMinglarAdminHost';
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 { PrePopulateService } from '../services/prepopulate.service'; import { PrePopulateService } from '../services/prepopulate.service';
import { verifyHostToken } from '@/common/middlewares/jwt/authForHost';
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const prePopulateService = new PrePopulateService(prismaService); const prePopulateService = new PrePopulateService(prismaService);
@@ -20,7 +19,7 @@ export const handler = safeHandler(async (
} }
// Authenticate user using the shared authForHost function // Authenticate user using the shared authForHost function
await verifyHostToken(token); await verifyMinglarAdminHostToken(token);
const result = await prePopulateService.getAllPQQQuesAndAns(); const result = await prePopulateService.getAllPQQQuesAndAns();

View File

@@ -1,9 +1,9 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda"; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda";
import { PrismaService } from "../../../common/database/prisma.service"; import { PrismaService } from "../../../common/database/prisma.service";
import { verifyMinglarAdminHostToken } from "../../../common/middlewares/jwt/authForMinglarAdminHost";
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 { PrePopulateService } from "../services/prepopulate.service"; import { PrePopulateService } from "../services/prepopulate.service";
import { verifyHostToken } from "@/common/middlewares/jwt/authForHost";
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const prePopulateService = new PrePopulateService(prismaService); const prePopulateService = new PrePopulateService(prismaService);
@@ -26,7 +26,7 @@ export const handler = safeHandler(async (
} }
// 2) Authenticate user // 2) Authenticate user
await verifyHostToken(token); await verifyMinglarAdminHostToken(token);
// 3) Get bankXid from query params // 3) Get bankXid from query params
const bankXid = Number(event.queryStringParameters?.bankXid); const bankXid = Number(event.queryStringParameters?.bankXid);

View File

@@ -1,9 +1,9 @@
import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda"; import { APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda";
import { PrismaService } from "../../../common/database/prisma.service"; import { PrismaService } from "../../../common/database/prisma.service";
import { verifyMinglarAdminHostToken } from "../../../common/middlewares/jwt/authForMinglarAdminHost";
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 { PrePopulateService } from "../services/prepopulate.service"; import { PrePopulateService } from "../services/prepopulate.service";
import { verifyHostToken } from "@/common/middlewares/jwt/authForHost";
const prismaService = new PrismaService(); const prismaService = new PrismaService();
const prePopulateService = new PrePopulateService(prismaService); const prePopulateService = new PrePopulateService(prismaService);
@@ -26,7 +26,7 @@ export const handler = safeHandler(async (
} }
// 2) Authenticate user // 2) Authenticate user
await verifyHostToken(token); await verifyMinglarAdminHostToken(token);
// 3) Get bankXid from query params // 3) Get bankXid from query params
const stateXid = Number(event.queryStringParameters?.stateXid); const stateXid = Number(event.queryStringParameters?.stateXid);

View File

@@ -11,15 +11,11 @@ export class PrePopulateService {
isActive: true, isActive: true,
deletedAt: null, deletedAt: null,
}, },
include: { select: {
BankBranches: { id: true,
select: { bankName: true,
id: true,
branchAddress: true,
ifscCode: true,
},
},
}, },
orderBy: { bankName: 'asc' }
}); });
} }
@@ -28,7 +24,6 @@ export class PrePopulateService {
where: { where: {
bankXid, bankXid,
isActive: true, isActive: true,
deletedAt: null
}, },
select: { select: {
id: true, id: true,
@@ -37,8 +32,8 @@ export class PrePopulateService {
} }
}); });
} }
async getCityByStateId(stateXid: number) { async getCityByStateId(stateXid: number) {
return await this.prisma.cities.findMany({ return await this.prisma.cities.findMany({
where: { where: {
@@ -60,37 +55,60 @@ export class PrePopulateService {
isActive: true, isActive: true,
deletedAt: null, deletedAt: null,
}, },
select: {
id: true,
currencyName: true,
currencySymbol: true,
},
orderBy: { currencyName: 'asc' }
}); });
} }
async getAllPQQQuesAndAns() { async getAllPQQQuesAndAns() {
return await this.prisma.pQQCategories.findMany({ return await this.prisma.pQQCategories.findMany({
where: { isActive: true }, where: { isActive: true },
include: { select: {
id: true,
categoryName: true,
displayOrder: true,
pqqsubCategories: { pqqsubCategories: {
include: { where: { isActive: true },
select: {
id: true,
subCategoryName: true,
categoryXid: true,
displayOrder: true,
questions: { questions: {
include: { where: { isActive: true },
select: {
id: true,
questionName: true,
maxPoints: true,
displayOrder: true,
PQQAnswers: { PQQAnswers: {
orderBy: { where: { isActive: true },
displayOrder: 'asc' orderBy: { displayOrder: 'asc' },
select: {
id: true,
answerName: true,
answerPoints: true,
displayOrder: true
} }
} }
}, },
orderBy: { orderBy: { displayOrder: 'asc' }
displayOrder: 'asc' },
}
}
}, },
orderBy: { displayOrder: 'asc' } orderBy: { displayOrder: 'asc' },
} },
}, },
orderBy: { displayOrder: 'asc' } orderBy: { displayOrder: 'asc' },
}); });
} }
async getAllDocumentTypeWithCountryStateCity() { async getAllDocumentTypeWithCountryStateCity() {
const [documentDetails, countryDetails, stateDetails] = const [documentDetails, countryDetails, stateDetails, companyTypeDetails] =
await this.prisma.$transaction([ await this.prisma.$transaction([
this.prisma.documentType.findMany({ this.prisma.documentType.findMany({
where: { isActive: true, isVisible: true }, where: { isActive: true, isVisible: true },
@@ -101,10 +119,15 @@ export class PrePopulateService {
}), }),
this.prisma.states.findMany({ this.prisma.states.findMany({
where: { isActive: true }, where: { isActive: true },
orderBy: { stateName: 'asc' }
}),
this.prisma.companyTypes.findMany({
where: { isActive: true },
orderBy: { companyTypeName: 'asc' }
}), }),
]); ]);
return { documentDetails, countryDetails, stateDetails }; return { documentDetails, countryDetails, stateDetails, companyTypeDetails };
} }
async getAllFrequencies() { async getAllFrequencies() {