Files
2026-01-21 19:00:55 -05:00

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,
};