condition check lambda
This commit is contained in:
222
lambdas/conditionCheckReminder/handler.js
Normal file
222
lambdas/conditionCheckReminder/handler.js
Normal file
@@ -0,0 +1,222 @@
|
||||
const path = require("path");
|
||||
const {
|
||||
SchedulerClient,
|
||||
DeleteScheduleCommand,
|
||||
} = require("@aws-sdk/client-scheduler");
|
||||
const { queries, email, logger } = require("../shared");
|
||||
const { conditionCheckExists } = require("./queries");
|
||||
|
||||
let schedulerClient = null;
|
||||
|
||||
/**
|
||||
* Get or create an EventBridge Scheduler client.
|
||||
*/
|
||||
function getSchedulerClient() {
|
||||
if (!schedulerClient) {
|
||||
schedulerClient = new SchedulerClient({
|
||||
region: process.env.AWS_REGION || "us-east-1",
|
||||
});
|
||||
}
|
||||
return schedulerClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a one-time EventBridge schedule after it has fired.
|
||||
* @param {string} scheduleName - Name of the schedule to delete
|
||||
*/
|
||||
async function deleteSchedule(scheduleName) {
|
||||
try {
|
||||
const client = getSchedulerClient();
|
||||
const groupName =
|
||||
process.env.SCHEDULE_GROUP_NAME || "condition-check-reminders";
|
||||
|
||||
await client.send(
|
||||
new DeleteScheduleCommand({
|
||||
Name: scheduleName,
|
||||
GroupName: groupName,
|
||||
})
|
||||
);
|
||||
|
||||
logger.info("Deleted schedule after execution", {
|
||||
scheduleName,
|
||||
groupName,
|
||||
});
|
||||
} catch (error) {
|
||||
// Log but don't fail - schedule may have already been deleted
|
||||
logger.warn("Failed to delete schedule", {
|
||||
scheduleName,
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get email content based on check type.
|
||||
* @param {string} checkType - Type of condition check
|
||||
* @param {Object} rental - Rental with item, owner, renter details
|
||||
* @returns {Object} Email content (subject, title, message, recipient)
|
||||
*/
|
||||
function getEmailContent(checkType, rental) {
|
||||
const itemName = rental.item.name;
|
||||
const frontendUrl = process.env.FRONTEND_URL;
|
||||
|
||||
const content = {
|
||||
pre_rental_owner: {
|
||||
recipient: rental.owner,
|
||||
subject: `Condition Check Reminder: ${itemName}`,
|
||||
title: "Pre-Rental Condition Check",
|
||||
message: `Please take photos of "${itemName}" before the rental begins tomorrow. This helps protect both you and the renter.`,
|
||||
deadline: email.formatEmailDate(rental.startDateTime),
|
||||
},
|
||||
rental_start_renter: {
|
||||
recipient: rental.renter,
|
||||
subject: `Document Item Condition: ${itemName}`,
|
||||
title: "Rental Start Condition Check",
|
||||
message: `Please take photos when you receive "${itemName}" to document its condition. This protects you in case of any disputes.`,
|
||||
deadline: email.formatEmailDate(
|
||||
new Date(new Date(rental.startDateTime).getTime() + 24 * 60 * 60 * 1000)
|
||||
),
|
||||
},
|
||||
rental_end_renter: {
|
||||
recipient: rental.renter,
|
||||
subject: `Return Condition Check: ${itemName}`,
|
||||
title: "Rental End Condition Check",
|
||||
message: `Please take photos when returning "${itemName}" to document its condition before handoff.`,
|
||||
deadline: email.formatEmailDate(rental.endDateTime),
|
||||
},
|
||||
post_rental_owner: {
|
||||
recipient: rental.owner,
|
||||
subject: `Review Return: ${itemName}`,
|
||||
title: "Post-Rental Condition Check",
|
||||
message: `Please take photos and mark the return status for "${itemName}". This completes the rental process.`,
|
||||
deadline: email.formatEmailDate(
|
||||
new Date(new Date(rental.endDateTime).getTime() + 48 * 60 * 60 * 1000)
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
return content[checkType];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a condition check reminder.
|
||||
* @param {string} rentalId - UUID of the rental
|
||||
* @param {string} checkType - Type of condition check
|
||||
* @param {string} scheduleName - Name of the EventBridge schedule (for cleanup)
|
||||
* @returns {Promise<Object>} Result of the operation
|
||||
*/
|
||||
async function processReminder(rentalId, checkType, scheduleName) {
|
||||
logger.info("Processing condition check reminder", {
|
||||
rentalId,
|
||||
checkType,
|
||||
scheduleName,
|
||||
});
|
||||
|
||||
try {
|
||||
// 1. Check if condition check already exists (skip if yes)
|
||||
const exists = await conditionCheckExists(rentalId, checkType);
|
||||
if (exists) {
|
||||
logger.info("Condition check already exists, skipping reminder", {
|
||||
rentalId,
|
||||
checkType,
|
||||
});
|
||||
|
||||
// Still delete the schedule
|
||||
if (scheduleName) {
|
||||
await deleteSchedule(scheduleName);
|
||||
}
|
||||
|
||||
return { success: true, skipped: true, reason: "condition_check_exists" };
|
||||
}
|
||||
|
||||
// 2. Fetch rental details
|
||||
const rental = await queries.getRentalWithDetails(rentalId);
|
||||
if (!rental) {
|
||||
logger.error("Rental not found", { rentalId });
|
||||
return { success: false, error: "rental_not_found" };
|
||||
}
|
||||
|
||||
// 3. Check rental status - only send for active rentals
|
||||
const validStatuses = ["confirmed", "active", "completed"];
|
||||
if (!validStatuses.includes(rental.status)) {
|
||||
logger.info("Rental status not valid for reminder", {
|
||||
rentalId,
|
||||
status: rental.status,
|
||||
});
|
||||
|
||||
if (scheduleName) {
|
||||
await deleteSchedule(scheduleName);
|
||||
}
|
||||
|
||||
return { success: true, skipped: true, reason: "invalid_rental_status" };
|
||||
}
|
||||
|
||||
// 4. Get email content and send
|
||||
const emailContent = getEmailContent(checkType, rental);
|
||||
if (!emailContent) {
|
||||
logger.error("Unknown check type", { checkType });
|
||||
return { success: false, error: "unknown_check_type" };
|
||||
}
|
||||
|
||||
// 5. Load and render the email template
|
||||
const templatePath = path.join(
|
||||
__dirname,
|
||||
"templates",
|
||||
"conditionCheckReminderToUser.html"
|
||||
);
|
||||
const template = await email.loadTemplate(templatePath);
|
||||
|
||||
const htmlBody = email.renderTemplate(template, {
|
||||
title: emailContent.title,
|
||||
message: emailContent.message,
|
||||
itemName: rental.item.name,
|
||||
deadline: emailContent.deadline,
|
||||
recipientName: emailContent.recipient.firstName,
|
||||
});
|
||||
|
||||
// 6. Send the email
|
||||
const result = await email.sendEmail(
|
||||
emailContent.recipient.email,
|
||||
emailContent.subject,
|
||||
htmlBody
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
logger.error("Failed to send reminder email", {
|
||||
rentalId,
|
||||
checkType,
|
||||
error: result.error,
|
||||
});
|
||||
return { success: false, error: result.error };
|
||||
}
|
||||
|
||||
logger.info("Sent condition check reminder", {
|
||||
rentalId,
|
||||
checkType,
|
||||
to: emailContent.recipient.email,
|
||||
messageId: result.messageId,
|
||||
});
|
||||
|
||||
// 7. Delete the one-time schedule
|
||||
if (scheduleName) {
|
||||
await deleteSchedule(scheduleName);
|
||||
}
|
||||
|
||||
return { success: true, messageId: result.messageId };
|
||||
} catch (error) {
|
||||
logger.error("Error processing condition check reminder", {
|
||||
rentalId,
|
||||
checkType,
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
});
|
||||
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
processReminder,
|
||||
getEmailContent,
|
||||
deleteSchedule,
|
||||
};
|
||||
Reference in New Issue
Block a user