Compare commits
4 Commits
testingonl
...
split-sche
| Author | SHA1 | Date | |
|---|---|---|---|
| 50f93bbeae | |||
| 3ce9d1d180 | |||
| cb819088a0 | |||
| 8f5f01c287 |
@@ -1,5 +1,5 @@
|
||||
# Legacy monolith config. For new deployments use serverless.*.yml files.
|
||||
service: minglarDev
|
||||
service: minglar
|
||||
|
||||
|
||||
useDotenv: true
|
||||
|
||||
@@ -13,6 +13,40 @@ const hostService = new HostService(prismaClient);
|
||||
|
||||
const s3 = new AWS.S3({ region: config.aws.region });
|
||||
|
||||
function parseMultipartFieldValue(val: string) {
|
||||
if (val === '' || val === 'null' || val === 'undefined') return null;
|
||||
|
||||
const cleaned = val.trim();
|
||||
const looksLikeJson =
|
||||
(cleaned.startsWith('{') && cleaned.endsWith('}')) ||
|
||||
(cleaned.startsWith('[') && cleaned.endsWith(']')) ||
|
||||
(cleaned.startsWith('"') && cleaned.endsWith('"'));
|
||||
|
||||
if (!looksLikeJson) return val;
|
||||
|
||||
try {
|
||||
return JSON.parse(cleaned);
|
||||
} catch {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeComments(comments: unknown): string | null {
|
||||
if (comments === null || comments === undefined || comments === '') return null;
|
||||
|
||||
const value = String(comments).trim();
|
||||
if (!value) return null;
|
||||
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
return value.slice(1, -1);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Function to extract S3 key from URL
|
||||
function getS3KeyFromUrl(url: string): string {
|
||||
const bucketBaseUrl = `https://${config.aws.bucketName}.s3.${config.aws.region}.amazonaws.com/`;
|
||||
@@ -122,22 +156,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
|
||||
bb.on("field", (fieldname, val) => {
|
||||
console.log(`FIELD RAW: ${fieldname} =`, val);
|
||||
if (val === '' || val === 'null' || val === 'undefined') fields[fieldname] = null;
|
||||
else {
|
||||
try {
|
||||
const cleaned = val.trim();
|
||||
|
||||
// If it starts and ends with quotes, remove them
|
||||
const withoutQuotes =
|
||||
(cleaned.startsWith('"') && cleaned.endsWith('"'))
|
||||
? cleaned.slice(1, -1)
|
||||
: cleaned;
|
||||
|
||||
fields[fieldname] = JSON.parse(withoutQuotes);
|
||||
} catch {
|
||||
fields[fieldname] = val;
|
||||
}
|
||||
}
|
||||
fields[fieldname] = parseMultipartFieldValue(val);
|
||||
});
|
||||
|
||||
bb.on("close", () => resolve());
|
||||
@@ -154,7 +173,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
const activityXid = Number(fields.activityXid);
|
||||
const pqqQuestionXid = Number(fields.pqqQuestionXid);
|
||||
const pqqAnswerXid = Number(fields.pqqAnswerXid);
|
||||
const comments = fields.comments || null;
|
||||
const comments = normalizeComments(fields.comments);
|
||||
|
||||
if (!activityXid || isNaN(activityXid)) throw new ApiError(400, "Please provide a valid activity");
|
||||
if (!pqqQuestionXid || isNaN(pqqQuestionXid)) throw new ApiError(400, "Please select a valid question");
|
||||
|
||||
@@ -142,6 +142,10 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
|
||||
const deletedFiles = normalizeJsonField(fields, "deletedFiles") || [];
|
||||
const parentDeletedFiles = normalizeJsonField(fields, "parentDeletedFiles") || [];
|
||||
const deleteCompanyLogo =
|
||||
fields.deleteCompanyLogo === 'true' || fields.deleteCompanyLogo === true;
|
||||
const deleteParentCompanyLogo =
|
||||
fields.deleteParentCompanyLogo === 'true' || fields.deleteParentCompanyLogo === true;
|
||||
|
||||
/** 4) Extract and clean isDraft flag */
|
||||
const isDraft = fields.isDraft === 'true' || fields.isDraft === true;
|
||||
@@ -379,6 +383,63 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
});
|
||||
}
|
||||
|
||||
/** DELETE EXISTING LOGO IF REQUESTED */
|
||||
if (deleteCompanyLogo) {
|
||||
const existingHost = await prismaClient.hostHeader.findFirst({
|
||||
where: { userXid: userInfo.id },
|
||||
select: { logoPath: true },
|
||||
});
|
||||
|
||||
if (existingHost?.logoPath) {
|
||||
try {
|
||||
const s3Key = getS3KeyFromUrl(existingHost.logoPath);
|
||||
await deleteFromS3(s3Key);
|
||||
} catch (e) {
|
||||
console.error('S3 delete failed for company logo:', existingHost.logoPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
parsedCompany.logoPath = null;
|
||||
}
|
||||
|
||||
/** DELETE EXISTING PARENT COMPANY LOGO IF REQUESTED */
|
||||
if (deleteParentCompanyLogo && parsedCompany.isSubsidairy) {
|
||||
const existingHost = await prismaClient.hostHeader.findFirst({
|
||||
where: { userXid: userInfo.id },
|
||||
select: {
|
||||
id: true,
|
||||
hostParent: {
|
||||
select: {
|
||||
id: true,
|
||||
logoPath: true,
|
||||
},
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const existingParent = Array.isArray(existingHost?.hostParent)
|
||||
? existingHost.hostParent[0]
|
||||
: existingHost?.hostParent;
|
||||
|
||||
if (existingParent?.logoPath) {
|
||||
try {
|
||||
const s3Key = getS3KeyFromUrl(existingParent.logoPath);
|
||||
await deleteFromS3(s3Key);
|
||||
} catch (e) {
|
||||
console.error('S3 delete failed for parent company logo:', existingParent.logoPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (parsedParentCompany) {
|
||||
parsedParentCompany.logoPath = null;
|
||||
} else {
|
||||
parsedParentCompany = {
|
||||
logoPath: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/** UPLOAD LOGO (if provided) */
|
||||
const logoFile = files.find(
|
||||
(f) => f.fieldName === 'companyLogo' || f.fieldName === 'companyLogoFile'
|
||||
@@ -449,6 +510,7 @@ export const handler = safeHandler(async (event: APIGatewayProxyEvent): Promise<
|
||||
parsedParentCompany,
|
||||
uploadedParentDocs,
|
||||
isDraft,
|
||||
{ deleteCompanyLogo, deleteParentCompanyLogo },
|
||||
);
|
||||
|
||||
if (!createdOrUpdated) throw new ApiError(400, 'Failed to add/update company details.');
|
||||
|
||||
@@ -518,6 +518,7 @@ export class HostService {
|
||||
select: {
|
||||
id: true,
|
||||
emailAddress: true,
|
||||
dateOfBirth: true,
|
||||
firstName: true,
|
||||
lastName: true,
|
||||
mobileNumber: true,
|
||||
@@ -1390,6 +1391,10 @@ export class HostService {
|
||||
parentCompanyData?: any | null,
|
||||
parentDocuments?: HostDocumentInput[],
|
||||
isDraft: boolean = false,
|
||||
options?: {
|
||||
deleteCompanyLogo?: boolean;
|
||||
deleteParentCompanyLogo?: boolean;
|
||||
},
|
||||
) {
|
||||
return await this.prisma.$transaction(async (tx) => {
|
||||
// Check if host already has a company
|
||||
@@ -1650,7 +1655,9 @@ export class HostService {
|
||||
? { connect: { id: Number(companyData.countryXid) } }
|
||||
: undefined,
|
||||
pinCode: companyData.pinCode,
|
||||
logoPath: companyData.logoPath || existingHostCompany.logoPath,
|
||||
logoPath: options?.deleteCompanyLogo
|
||||
? companyData.logoPath ?? null
|
||||
: companyData.logoPath || existingHostCompany.logoPath,
|
||||
isSubsidairy: companyData.isSubsidairy,
|
||||
registrationNumber: companyData.registrationNumber,
|
||||
panNumber: companyData.panNumber,
|
||||
@@ -1791,8 +1798,9 @@ export class HostService {
|
||||
? { connect: { id: Number(parentCompanyData.countryXid) } }
|
||||
: undefined,
|
||||
pinCode: parentCompanyData.pinCode || null,
|
||||
logoPath:
|
||||
parentCompanyData?.logoPath ||
|
||||
logoPath: options?.deleteParentCompanyLogo
|
||||
? parentCompanyData?.logoPath ?? null
|
||||
: parentCompanyData?.logoPath ||
|
||||
existingParentCompany?.logoPath ||
|
||||
null,
|
||||
registrationNumber: parentCompanyData.registrationNumber || null,
|
||||
@@ -1849,8 +1857,9 @@ export class HostService {
|
||||
? { connect: { id: Number(parentCompanyData.countryXid) } }
|
||||
: undefined,
|
||||
pinCode: parentCompanyData.pinCode || null,
|
||||
logoPath:
|
||||
parentCompanyData?.logoPath ||
|
||||
logoPath: options?.deleteParentCompanyLogo
|
||||
? parentCompanyData?.logoPath ?? null
|
||||
: parentCompanyData?.logoPath ||
|
||||
existingParentCompany?.logoPath ||
|
||||
null,
|
||||
registrationNumber: parentCompanyData.registrationNumber || null,
|
||||
|
||||
@@ -879,6 +879,97 @@ export class ItineraryService {
|
||||
checkInAddress: true,
|
||||
checkInLat: true,
|
||||
checkInLong: true,
|
||||
foodAvailable: true,
|
||||
foodIsChargeable: true,
|
||||
trainerAvailable: true,
|
||||
trainerIsChargeable: true,
|
||||
inActivityAvailable: true,
|
||||
inActivityIsChargeable: true,
|
||||
pickUpDropAvailable: true,
|
||||
pickUpDropIsChargeable: true,
|
||||
activityFoodTypes: {
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
foodTypeXid: true,
|
||||
foodType: {
|
||||
select: {
|
||||
id: true,
|
||||
foodTypeName: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ActivityFoodCost: {
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
baseAmount: true,
|
||||
totalAmount: true,
|
||||
},
|
||||
},
|
||||
ActivityTrainers: {
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
baseAmount: true,
|
||||
totalAmount: true,
|
||||
},
|
||||
},
|
||||
ActivityNavigationModes: {
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
navigationModeName: true,
|
||||
isInActivityChargeable: true,
|
||||
navigationModesBasePrice: true,
|
||||
navigationModesTotalPrice: true,
|
||||
},
|
||||
},
|
||||
ActivityPickUpDetails: {
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
isPickUp: true,
|
||||
locationLat: true,
|
||||
locationLong: true,
|
||||
locationAddress: true,
|
||||
transportBasePrice: true,
|
||||
transportTotalPrice: true,
|
||||
},
|
||||
},
|
||||
activityPickUpTransports: {
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
transportModeXid: true,
|
||||
transportMode: {
|
||||
select: {
|
||||
id: true,
|
||||
transportModeName: true,
|
||||
transportModeIcon: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ActivitiesMedia: {
|
||||
where: {
|
||||
isActive: true,
|
||||
@@ -921,8 +1012,7 @@ export class ItineraryService {
|
||||
|
||||
const formattedItineraries = await Promise.all(
|
||||
itineraries.map(async (itinerary) => {
|
||||
const ownerFullName = `${itinerary.owner.firstName ?? ''} ${
|
||||
itinerary.owner.lastName ?? ''
|
||||
const ownerFullName = `${itinerary.owner.firstName ?? ''} ${itinerary.owner.lastName ?? ''
|
||||
}`.trim();
|
||||
|
||||
const members = await Promise.all(
|
||||
@@ -932,8 +1022,7 @@ export class ItineraryService {
|
||||
memberRole: member.memberRole,
|
||||
memberStatus: member.memberStatus,
|
||||
invitedByXid: member.invitedByXid,
|
||||
fullName: `${member.member.firstName ?? ''} ${
|
||||
member.member.lastName ?? ''
|
||||
fullName: `${member.member.firstName ?? ''} ${member.member.lastName ?? ''
|
||||
}`.trim(),
|
||||
firstName: member.member.firstName,
|
||||
lastName: member.member.lastName,
|
||||
@@ -985,6 +1074,80 @@ export class ItineraryService {
|
||||
media: await attachMediaWithPresignedUrl(
|
||||
item.activity.ActivitiesMedia,
|
||||
),
|
||||
foodDetails: {
|
||||
foodAvailable: item.activity.foodAvailable,
|
||||
foodIsChargeable: item.activity.foodIsChargeable,
|
||||
foodTypes: item.activity.activityFoodTypes.map((foodType) => ({
|
||||
id: foodType.id,
|
||||
foodTypeXid: foodType.foodTypeXid,
|
||||
foodType: foodType.foodType,
|
||||
})),
|
||||
foodCost: item.activity.ActivityFoodCost.map((foodCost) => ({
|
||||
id: foodCost.id,
|
||||
baseAmount: foodCost.baseAmount,
|
||||
totalAmount: foodCost.totalAmount,
|
||||
})),
|
||||
},
|
||||
trainerDetails: {
|
||||
trainerAvailable: item.activity.trainerAvailable,
|
||||
trainerIsChargeable: item.activity.trainerIsChargeable,
|
||||
trainerCost: item.activity.ActivityTrainers.map((trainer) => ({
|
||||
id: trainer.id,
|
||||
baseAmount: trainer.baseAmount,
|
||||
totalAmount: trainer.totalAmount,
|
||||
})),
|
||||
},
|
||||
navigationDetails: {
|
||||
inActivityAvailable: item.activity.inActivityAvailable,
|
||||
inActivityIsChargeable: item.activity.inActivityIsChargeable,
|
||||
navigationModes: item.activity.ActivityNavigationModes.map(
|
||||
(navigationMode) => ({
|
||||
id: navigationMode.id,
|
||||
navigationModeName: navigationMode.navigationModeName,
|
||||
isInActivityChargeable:
|
||||
navigationMode.isInActivityChargeable,
|
||||
navigationModesBasePrice:
|
||||
navigationMode.navigationModesBasePrice,
|
||||
navigationModesTotalPrice:
|
||||
navigationMode.navigationModesTotalPrice,
|
||||
}),
|
||||
),
|
||||
},
|
||||
pickUpDetails: {
|
||||
pickUpDropAvailable: item.activity.pickUpDropAvailable,
|
||||
pickUpDropIsChargeable:
|
||||
item.activity.pickUpDropIsChargeable,
|
||||
details: item.activity.ActivityPickUpDetails.map(
|
||||
(pickUpDetail) => ({
|
||||
id: pickUpDetail.id,
|
||||
isPickUp: pickUpDetail.isPickUp,
|
||||
locationLat: pickUpDetail.locationLat,
|
||||
locationLong: pickUpDetail.locationLong,
|
||||
locationAddress: pickUpDetail.locationAddress,
|
||||
transportBasePrice: pickUpDetail.transportBasePrice,
|
||||
transportTotalPrice: pickUpDetail.transportTotalPrice,
|
||||
}),
|
||||
),
|
||||
transportModes: await Promise.all(
|
||||
item.activity.activityPickUpTransports.map(
|
||||
async (transport) => ({
|
||||
id: transport.id,
|
||||
transportModeXid: transport.transportModeXid,
|
||||
transportMode: {
|
||||
id: transport.transportMode.id,
|
||||
transportModeName:
|
||||
transport.transportMode.transportModeName,
|
||||
transportModeIcon:
|
||||
transport.transportMode.transportModeIcon,
|
||||
transportModeIconPresignedUrl:
|
||||
await attachPresignedUrl(
|
||||
transport.transportMode.transportModeIcon,
|
||||
),
|
||||
},
|
||||
}),
|
||||
),
|
||||
),
|
||||
},
|
||||
},
|
||||
venue: item.venue,
|
||||
scheduleHeader: item.scheduledHeader,
|
||||
@@ -1150,6 +1313,7 @@ export class ItineraryService {
|
||||
id: true,
|
||||
activityTitle: true,
|
||||
activityDurationMins: true,
|
||||
activityDescription: true,
|
||||
checkInLat: true,
|
||||
checkInLong: true,
|
||||
checkInAddress: true,
|
||||
@@ -1246,6 +1410,17 @@ export class ItineraryService {
|
||||
venueName: true,
|
||||
venueLabel: true,
|
||||
venueCapacity: true,
|
||||
ActivityVenueArtifacts: {
|
||||
where: {
|
||||
isActive: true,
|
||||
deletedAt: null,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
mediaType: true,
|
||||
mediaFileName: true,
|
||||
},
|
||||
},
|
||||
availableSeats: true,
|
||||
ScheduleHeader: {
|
||||
where: {
|
||||
@@ -1411,6 +1586,8 @@ export class ItineraryService {
|
||||
venueXid: venue.id,
|
||||
venueName: venue.venueName,
|
||||
venueLabel: venue.venueLabel,
|
||||
mediaFileName: venue.ActivityVenueArtifacts[0]?.mediaFileName ?? null,
|
||||
mediaType: venue.ActivityVenueArtifacts[0]?.mediaType ?? null,
|
||||
venueCapacity: venue.venueCapacity,
|
||||
availableSeats: venue.availableSeats,
|
||||
slotDate: formatDateKey(slotDate),
|
||||
@@ -1430,11 +1607,27 @@ export class ItineraryService {
|
||||
}),
|
||||
);
|
||||
|
||||
if (!availableSlots.length) {
|
||||
const sanitizedAvailableSlots = availableSlots.filter(
|
||||
(
|
||||
slot,
|
||||
): slot is NonNullable<(typeof availableSlots)[number]> =>
|
||||
Boolean(slot),
|
||||
);
|
||||
|
||||
const availableSlotsWithPresignedUrl = await Promise.all(
|
||||
sanitizedAvailableSlots.map(async (slot) => ({
|
||||
...slot,
|
||||
mediaFileNamePresignedUrl: await attachPresignedUrl(
|
||||
slot.mediaFileName,
|
||||
),
|
||||
})),
|
||||
);
|
||||
|
||||
if (!availableSlotsWithPresignedUrl.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
availableSlots.sort(
|
||||
availableSlotsWithPresignedUrl.sort(
|
||||
(first, second) =>
|
||||
new Date(first!.startDateTime).getTime() -
|
||||
new Date(second!.startDateTime).getTime(),
|
||||
@@ -1454,19 +1647,20 @@ export class ItineraryService {
|
||||
bucketTypeName: entry.bucketTypeName,
|
||||
distance,
|
||||
activityTitle: activity.activityTitle,
|
||||
activityDescription: activity.activityDescription,
|
||||
activityDurationMins,
|
||||
activityCoverImage: coverImage?.mediaFileName ?? null,
|
||||
activityCoverImagePresignedUrl: await attachPresignedUrl(
|
||||
coverImage?.mediaFileName,
|
||||
),
|
||||
venue: availableSlots[0]
|
||||
venue: availableSlotsWithPresignedUrl[0]
|
||||
? {
|
||||
venueXid: availableSlots[0].venueXid,
|
||||
venueName: availableSlots[0].venueName,
|
||||
venueLabel: availableSlots[0].venueLabel,
|
||||
venueXid: availableSlotsWithPresignedUrl[0].venueXid,
|
||||
venueName: availableSlotsWithPresignedUrl[0].venueName,
|
||||
venueLabel: availableSlotsWithPresignedUrl[0].venueLabel,
|
||||
}
|
||||
: null,
|
||||
availableSlots,
|
||||
availableSlots: availableSlotsWithPresignedUrl,
|
||||
entryType: activity.ActivityAllowedEntry[0]?.allowedEntryType ?? null,
|
||||
energyLevel: energyLevel
|
||||
? {
|
||||
|
||||
Reference in New Issue
Block a user