225 lines
6.5 KiB
JavaScript
225 lines
6.5 KiB
JavaScript
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,
|
|
});
|
|
}
|
|
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,
|
|
};
|