text changes

This commit is contained in:
jackiettran
2026-01-21 19:20:07 -05:00
parent 420e0efeb4
commit 5d3c124d3e
31 changed files with 16387 additions and 4053 deletions

View File

@@ -269,11 +269,11 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => {
totalAmount = RentalDurationCalculator.calculateRentalCost(
rentalStartDateTime,
rentalEndDateTime,
item
item,
);
// Check for overlapping rentals using datetime ranges
// Note: "active" rentals are stored as "confirmed" with startDateTime in the past
// "active" rentals are stored as "confirmed" with startDateTime in the past
// Two ranges [A,B] and [C,D] overlap if and only if A < D AND C < B
// Here: existing rental [existingStart, existingEnd], new rental [rentalStartDateTime, rentalEndDateTime]
// Overlap: existingStart < rentalEndDateTime AND rentalStartDateTime < existingEnd
@@ -352,7 +352,7 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => {
await emailServices.rentalFlow.sendRentalRequestEmail(
rentalWithDetails.owner,
rentalWithDetails.renter,
rentalWithDetails
rentalWithDetails,
);
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Rental request notification sent to owner", {
@@ -374,7 +374,7 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => {
try {
await emailServices.rentalFlow.sendRentalRequestConfirmationEmail(
rentalWithDetails.renter,
rentalWithDetails
rentalWithDetails,
);
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Rental request confirmation sent to renter", {
@@ -474,7 +474,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
itemName: rental.item.name,
renterId: rental.renterId,
ownerId: rental.ownerId,
}
},
);
// Check if 3DS authentication is required
@@ -494,7 +494,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
itemName: rental.item.name,
ownerName: rental.owner.firstName,
amount: rental.totalAmount,
}
},
);
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Authentication required email sent to renter", {
@@ -503,15 +503,12 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
});
} catch (emailError) {
const reqLogger = logger.withRequestId(req.id);
reqLogger.error(
"Failed to send authentication required email",
{
error: emailError.message,
stack: emailError.stack,
rentalId: rental.id,
renterId: rental.renterId,
}
);
reqLogger.error("Failed to send authentication required email", {
error: emailError.message,
stack: emailError.stack,
rentalId: rental.id,
renterId: rental.renterId,
});
}
return res.status(402).json({
@@ -557,17 +554,14 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
// Create condition check reminder schedules
try {
await EventBridgeSchedulerService.createConditionCheckSchedules(
updatedRental
updatedRental,
);
} catch (schedulerError) {
const reqLogger = logger.withRequestId(req.id);
reqLogger.error(
"Failed to create condition check schedules",
{
error: schedulerError.message,
rentalId: updatedRental.id,
}
);
reqLogger.error("Failed to create condition check schedules", {
error: schedulerError.message,
rentalId: updatedRental.id,
});
// Don't fail the confirmation - schedules are non-critical
}
@@ -577,7 +571,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
await emailServices.rentalFlow.sendRentalApprovalConfirmationEmail(
updatedRental.owner,
updatedRental.renter,
updatedRental
updatedRental,
);
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Rental approval confirmation sent to owner", {
@@ -593,7 +587,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
stack: emailError.stack,
rentalId: updatedRental.id,
ownerId: updatedRental.ownerId,
}
},
);
}
@@ -616,7 +610,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
renterNotification,
updatedRental,
renter.firstName,
true // isRenter = true to show payment receipt
true, // isRenter = true to show payment receipt
);
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Rental confirmation sent to renter", {
@@ -633,7 +627,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
stack: emailError.stack,
rentalId: updatedRental.id,
renterId: updatedRental.renterId,
}
},
);
}
@@ -670,7 +664,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
itemName: rental.item.name,
declineReason: renterMessage,
rentalId: rental.id,
}
},
);
reqLogger.info("Payment declined email auto-sent to renter", {
rentalId: rental.id,
@@ -728,17 +722,14 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
// Create condition check reminder schedules
try {
await EventBridgeSchedulerService.createConditionCheckSchedules(
updatedRental
updatedRental,
);
} catch (schedulerError) {
const reqLogger = logger.withRequestId(req.id);
reqLogger.error(
"Failed to create condition check schedules",
{
error: schedulerError.message,
rentalId: updatedRental.id,
}
);
reqLogger.error("Failed to create condition check schedules", {
error: schedulerError.message,
rentalId: updatedRental.id,
});
// Don't fail the confirmation - schedules are non-critical
}
@@ -748,7 +739,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
await emailServices.rentalFlow.sendRentalApprovalConfirmationEmail(
updatedRental.owner,
updatedRental.renter,
updatedRental
updatedRental,
);
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Rental approval confirmation sent to owner", {
@@ -764,7 +755,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
stack: emailError.stack,
rentalId: updatedRental.id,
ownerId: updatedRental.ownerId,
}
},
);
}
@@ -787,7 +778,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
renterNotification,
updatedRental,
renter.firstName,
true // isRenter = true (for free rentals, shows "no payment required")
true, // isRenter = true (for free rentals, shows "no payment required")
);
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Rental confirmation sent to renter", {
@@ -804,7 +795,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
stack: emailError.stack,
rentalId: updatedRental.id,
renterId: updatedRental.renterId,
}
},
);
}
@@ -910,7 +901,7 @@ router.put("/:id/decline", authenticateToken, async (req, res) => {
await emailServices.rentalFlow.sendRentalDeclinedEmail(
updatedRental.renter,
updatedRental,
reason
reason,
);
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Rental decline notification sent to renter", {
@@ -1130,7 +1121,7 @@ router.post("/cost-preview", authenticateToken, async (req, res) => {
const totalAmount = RentalDurationCalculator.calculateRentalCost(
rentalStartDateTime,
rentalEndDateTime,
item
item,
);
// Calculate fees
@@ -1202,7 +1193,7 @@ router.get("/:id/refund-preview", authenticateToken, async (req, res, next) => {
try {
const preview = await RefundService.getRefundPreview(
req.params.id,
req.user.id
req.user.id,
);
res.json(preview);
} catch (error) {
@@ -1246,7 +1237,7 @@ router.get(
const lateCalculation = LateReturnService.calculateLateFee(
rental,
actualReturnDateTime
actualReturnDateTime,
);
res.json(lateCalculation);
@@ -1260,7 +1251,7 @@ router.get(
});
next(error);
}
}
},
);
// Cancel rental with refund processing
@@ -1276,7 +1267,7 @@ router.post("/:id/cancel", authenticateToken, async (req, res, next) => {
const result = await RefundService.processCancellation(
req.params.id,
req.user.id,
reason.trim()
reason.trim(),
);
// Return the updated rental with refund information
@@ -1302,7 +1293,7 @@ router.post("/:id/cancel", authenticateToken, async (req, res, next) => {
updatedRental.owner,
updatedRental.renter,
updatedRental,
result.refund
result.refund,
);
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Cancellation emails sent", {
@@ -1403,7 +1394,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
await emailServices.rentalFlow.sendRentalCompletionEmails(
rentalWithDetails.owner,
rentalWithDetails.renter,
rentalWithDetails
rentalWithDetails,
);
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Rental completion emails sent", {
@@ -1441,7 +1432,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
if (statusOptions?.returned_late && actualReturnDateTime) {
const lateReturnDamaged = await LateReturnService.processLateReturn(
rentalId,
actualReturnDateTime
actualReturnDateTime,
);
damageUpdates.status = "returned_late_and_damaged";
damageUpdates.lateFees = lateReturnDamaged.lateCalculation.lateFee;
@@ -1463,7 +1454,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
const lateReturn = await LateReturnService.processLateReturn(
rentalId,
actualReturnDateTime
actualReturnDateTime,
);
updatedRental = lateReturn.rental;
@@ -1484,7 +1475,7 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
await emailServices.customerService.sendLostItemToCustomerService(
updatedRental,
owner,
renter
renter,
);
break;
@@ -1562,7 +1553,7 @@ router.post("/:id/report-damage", authenticateToken, async (req, res, next) => {
"damage-reports",
{
maxKeys: IMAGE_LIMITS.damageReports,
}
},
);
if (!keyValidation.valid) {
return res.status(400).json({
@@ -1576,7 +1567,7 @@ router.post("/:id/report-damage", authenticateToken, async (req, res, next) => {
const result = await DamageAssessmentService.processDamageAssessment(
rentalId,
damageInfo,
userId
userId,
);
const reqLogger = logger.withRequestId(req.id);
@@ -1654,7 +1645,7 @@ router.put("/:id/payment-method", authenticateToken, async (req, res, next) => {
let paymentMethod;
try {
paymentMethod = await StripeService.getPaymentMethod(
stripePaymentMethodId
stripePaymentMethodId,
);
} catch {
return res.status(400).json({ error: "Invalid payment method" });
@@ -1699,7 +1690,7 @@ router.put("/:id/payment-method", authenticateToken, async (req, res, next) => {
status: "pending",
paymentStatus: "pending",
},
}
},
);
if (updateCount === 0) {
@@ -1725,7 +1716,7 @@ router.put("/:id/payment-method", authenticateToken, async (req, res, next) => {
itemName: rental.item.name,
rentalId: rental.id,
approvalUrl: `${process.env.FRONTEND_URL}/rentals/${rentalId}`,
}
},
);
} catch (emailError) {
// Don't fail the request if email fails
@@ -1781,7 +1772,7 @@ router.get(
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const paymentIntent = await stripe.paymentIntents.retrieve(
rental.stripePaymentIntentId
rental.stripePaymentIntentId,
);
return res.json({
@@ -1798,7 +1789,7 @@ router.get(
});
next(error);
}
}
},
);
/**
@@ -1812,8 +1803,29 @@ router.post(
try {
const rental = await Rental.findByPk(req.params.id, {
include: [
{ model: User, as: "renter", attributes: ["id", "firstName", "lastName", "email", "stripeCustomerId"] },
{ model: User, as: "owner", attributes: ["id", "firstName", "lastName", "email", "stripeConnectedAccountId", "stripePayoutsEnabled"] },
{
model: User,
as: "renter",
attributes: [
"id",
"firstName",
"lastName",
"email",
"stripeCustomerId",
],
},
{
model: User,
as: "owner",
attributes: [
"id",
"firstName",
"lastName",
"email",
"stripeConnectedAccountId",
"stripePayoutsEnabled",
],
},
{ model: Item, as: "item" },
],
});
@@ -1837,7 +1849,7 @@ router.post(
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const paymentIntent = await stripe.paymentIntents.retrieve(
rental.stripePaymentIntentId,
{ expand: ['latest_charge.payment_method_details'] }
{ expand: ["latest_charge.payment_method_details"] },
);
if (paymentIntent.status !== "succeeded") {
@@ -1864,7 +1876,8 @@ router.post(
paymentMethodLast4 = paymentMethodDetails.card?.last4 || null;
} else if (type === "us_bank_account") {
paymentMethodBrand = "bank_account";
paymentMethodLast4 = paymentMethodDetails.us_bank_account?.last4 || null;
paymentMethodLast4 =
paymentMethodDetails.us_bank_account?.last4 || null;
}
}
@@ -1882,13 +1895,10 @@ router.post(
await EventBridgeSchedulerService.createConditionCheckSchedules(rental);
} catch (schedulerError) {
const reqLogger = logger.withRequestId(req.id);
reqLogger.error(
"Failed to create condition check schedules",
{
error: schedulerError.message,
rentalId: rental.id,
}
);
reqLogger.error("Failed to create condition check schedules", {
error: schedulerError.message,
rentalId: rental.id,
});
// Don't fail the confirmation - schedules are non-critical
}
@@ -1897,13 +1907,16 @@ router.post(
await emailServices.rentalFlow.sendRentalApprovalConfirmationEmail(
rental.owner,
rental.renter,
rental
rental,
);
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Rental approval confirmation sent to owner (after 3DS)", {
rentalId: rental.id,
ownerId: rental.ownerId,
});
reqLogger.info(
"Rental approval confirmation sent to owner (after 3DS)",
{
rentalId: rental.id,
ownerId: rental.ownerId,
},
);
} catch (emailError) {
const reqLogger = logger.withRequestId(req.id);
reqLogger.error(
@@ -1911,7 +1924,7 @@ router.post(
{
error: emailError.message,
rentalId: rental.id,
}
},
);
}
@@ -1929,7 +1942,7 @@ router.post(
renterNotification,
rental,
rental.renter.firstName,
true
true,
);
const reqLogger = logger.withRequestId(req.id);
reqLogger.info("Rental confirmation sent to renter (after 3DS)", {
@@ -1938,17 +1951,17 @@ router.post(
});
} catch (emailError) {
const reqLogger = logger.withRequestId(req.id);
reqLogger.error(
"Failed to send rental confirmation email after 3DS",
{
error: emailError.message,
rentalId: rental.id,
}
);
reqLogger.error("Failed to send rental confirmation email after 3DS", {
error: emailError.message,
rentalId: rental.id,
});
}
// Trigger payout if owner has payouts enabled
if (rental.owner.stripePayoutsEnabled && rental.owner.stripeConnectedAccountId) {
if (
rental.owner.stripePayoutsEnabled &&
rental.owner.stripeConnectedAccountId
) {
try {
await PayoutService.processRentalPayout(rental);
const reqLogger = logger.withRequestId(req.id);
@@ -1983,7 +1996,7 @@ router.post(
});
next(error);
}
}
},
);
module.exports = router;