email verification flow updated
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
const express = require("express");
|
||||
const { Op } = require("sequelize");
|
||||
const { Rental, Item, User } = require("../models"); // Import from models/index.js to get models with associations
|
||||
const { authenticateToken, requireVerifiedEmail } = require("../middleware/auth");
|
||||
const {
|
||||
authenticateToken,
|
||||
requireVerifiedEmail,
|
||||
} = require("../middleware/auth");
|
||||
const FeeCalculator = require("../utils/feeCalculator");
|
||||
const RentalDurationCalculator = require("../utils/rentalDurationCalculator");
|
||||
const RefundService = require("../services/refundService");
|
||||
@@ -304,7 +307,11 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => {
|
||||
|
||||
// Send rental request notification to owner
|
||||
try {
|
||||
await emailServices.rentalFlow.sendRentalRequestEmail(rentalWithDetails.owner, rentalWithDetails.renter, rentalWithDetails);
|
||||
await emailServices.rentalFlow.sendRentalRequestEmail(
|
||||
rentalWithDetails.owner,
|
||||
rentalWithDetails.renter,
|
||||
rentalWithDetails
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental request notification sent to owner", {
|
||||
rentalId: rental.id,
|
||||
@@ -322,7 +329,10 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res) => {
|
||||
|
||||
// Send rental request confirmation to renter
|
||||
try {
|
||||
await emailServices.rentalFlow.sendRentalRequestConfirmationEmail(rentalWithDetails.renter, rentalWithDetails);
|
||||
await emailServices.rentalFlow.sendRentalRequestConfirmationEmail(
|
||||
rentalWithDetails.renter,
|
||||
rentalWithDetails
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental request confirmation sent to renter", {
|
||||
rentalId: rental.id,
|
||||
@@ -358,12 +368,7 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
{
|
||||
model: User,
|
||||
as: "renter",
|
||||
attributes: [
|
||||
"id",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"stripeCustomerId",
|
||||
],
|
||||
attributes: ["id", "firstName", "lastName", "stripeCustomerId"],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -445,7 +450,11 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
// Send confirmation emails
|
||||
// Send approval confirmation to owner with Stripe reminder
|
||||
try {
|
||||
await emailServices.rentalFlow.sendRentalApprovalConfirmationEmail(updatedRental.owner, updatedRental.renter, updatedRental);
|
||||
await emailServices.rentalFlow.sendRentalApprovalConfirmationEmail(
|
||||
updatedRental.owner,
|
||||
updatedRental.renter,
|
||||
updatedRental
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental approval confirmation sent to owner", {
|
||||
rentalId: updatedRental.id,
|
||||
@@ -453,11 +462,14 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
});
|
||||
} catch (emailError) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Failed to send rental approval confirmation email to owner", {
|
||||
error: emailError.message,
|
||||
rentalId: updatedRental.id,
|
||||
ownerId: updatedRental.ownerId,
|
||||
});
|
||||
reqLogger.error(
|
||||
"Failed to send rental approval confirmation email to owner",
|
||||
{
|
||||
error: emailError.message,
|
||||
rentalId: updatedRental.id,
|
||||
ownerId: updatedRental.ownerId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Send rental confirmation to renter with payment receipt
|
||||
@@ -489,11 +501,14 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
}
|
||||
} catch (emailError) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Failed to send rental confirmation email to renter", {
|
||||
error: emailError.message,
|
||||
rentalId: updatedRental.id,
|
||||
renterId: updatedRental.renterId,
|
||||
});
|
||||
reqLogger.error(
|
||||
"Failed to send rental confirmation email to renter",
|
||||
{
|
||||
error: emailError.message,
|
||||
rentalId: updatedRental.id,
|
||||
renterId: updatedRental.renterId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
res.json(updatedRental);
|
||||
@@ -537,7 +552,11 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
// Send confirmation emails
|
||||
// Send approval confirmation to owner (for free rentals, no Stripe reminder shown)
|
||||
try {
|
||||
await emailServices.rentalFlow.sendRentalApprovalConfirmationEmail(updatedRental.owner, updatedRental.renter, updatedRental);
|
||||
await emailServices.rentalFlow.sendRentalApprovalConfirmationEmail(
|
||||
updatedRental.owner,
|
||||
updatedRental.renter,
|
||||
updatedRental
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental approval confirmation sent to owner", {
|
||||
rentalId: updatedRental.id,
|
||||
@@ -545,11 +564,14 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
});
|
||||
} catch (emailError) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Failed to send rental approval confirmation email to owner", {
|
||||
error: emailError.message,
|
||||
rentalId: updatedRental.id,
|
||||
ownerId: updatedRental.ownerId,
|
||||
});
|
||||
reqLogger.error(
|
||||
"Failed to send rental approval confirmation email to owner",
|
||||
{
|
||||
error: emailError.message,
|
||||
rentalId: updatedRental.id,
|
||||
ownerId: updatedRental.ownerId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Send rental confirmation to renter
|
||||
@@ -581,11 +603,14 @@ router.put("/:id/status", authenticateToken, async (req, res) => {
|
||||
}
|
||||
} catch (emailError) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Failed to send rental confirmation email to renter", {
|
||||
error: emailError.message,
|
||||
rentalId: updatedRental.id,
|
||||
renterId: updatedRental.renterId,
|
||||
});
|
||||
reqLogger.error(
|
||||
"Failed to send rental confirmation email to renter",
|
||||
{
|
||||
error: emailError.message,
|
||||
rentalId: updatedRental.id,
|
||||
renterId: updatedRental.renterId,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
res.json(updatedRental);
|
||||
@@ -687,7 +712,11 @@ router.put("/:id/decline", authenticateToken, async (req, res) => {
|
||||
|
||||
// Send decline notification email to renter
|
||||
try {
|
||||
await emailServices.rentalFlow.sendRentalDeclinedEmail(updatedRental.renter, updatedRental, reason);
|
||||
await emailServices.rentalFlow.sendRentalDeclinedEmail(
|
||||
updatedRental.renter,
|
||||
updatedRental,
|
||||
reason
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental decline notification sent to renter", {
|
||||
rentalId: rental.id,
|
||||
@@ -904,7 +933,7 @@ router.post("/cost-preview", authenticateToken, async (req, res) => {
|
||||
// Validate date range
|
||||
if (rentalEndDateTime <= rentalStartDateTime) {
|
||||
return res.status(400).json({
|
||||
error: "End date/time must be after start date/time",
|
||||
error: "End must be after start",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -987,44 +1016,50 @@ router.get("/:id/refund-preview", authenticateToken, async (req, res, next) => {
|
||||
});
|
||||
|
||||
// Get late fee preview
|
||||
router.get("/:id/late-fee-preview", authenticateToken, async (req, res, next) => {
|
||||
try {
|
||||
const { actualReturnDateTime } = req.query;
|
||||
router.get(
|
||||
"/:id/late-fee-preview",
|
||||
authenticateToken,
|
||||
async (req, res, next) => {
|
||||
try {
|
||||
const { actualReturnDateTime } = req.query;
|
||||
|
||||
if (!actualReturnDateTime) {
|
||||
return res.status(400).json({ error: "actualReturnDateTime is required" });
|
||||
if (!actualReturnDateTime) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: "actualReturnDateTime is required" });
|
||||
}
|
||||
|
||||
const rental = await Rental.findByPk(req.params.id, {
|
||||
include: [{ model: Item, as: "item" }],
|
||||
});
|
||||
|
||||
if (!rental) {
|
||||
return res.status(404).json({ error: "Rental not found" });
|
||||
}
|
||||
|
||||
// Check authorization
|
||||
if (rental.ownerId !== req.user.id && rental.renterId !== req.user.id) {
|
||||
return res.status(403).json({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
const lateCalculation = LateReturnService.calculateLateFee(
|
||||
rental,
|
||||
actualReturnDateTime
|
||||
);
|
||||
|
||||
res.json(lateCalculation);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Error getting late fee preview", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
rentalId: req.params.id,
|
||||
userId: req.user.id,
|
||||
});
|
||||
next(error);
|
||||
}
|
||||
|
||||
const rental = await Rental.findByPk(req.params.id, {
|
||||
include: [{ model: Item, as: "item" }],
|
||||
});
|
||||
|
||||
if (!rental) {
|
||||
return res.status(404).json({ error: "Rental not found" });
|
||||
}
|
||||
|
||||
// Check authorization
|
||||
if (rental.ownerId !== req.user.id && rental.renterId !== req.user.id) {
|
||||
return res.status(403).json({ error: "Unauthorized" });
|
||||
}
|
||||
|
||||
const lateCalculation = LateReturnService.calculateLateFee(
|
||||
rental,
|
||||
actualReturnDateTime
|
||||
);
|
||||
|
||||
res.json(lateCalculation);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Error getting late fee preview", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
rentalId: req.params.id,
|
||||
userId: req.user.id,
|
||||
});
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// Cancel rental with refund processing
|
||||
router.post("/:id/cancel", authenticateToken, async (req, res, next) => {
|
||||
@@ -1156,7 +1191,11 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
|
||||
|
||||
// Send completion emails to both renter and owner
|
||||
try {
|
||||
await emailServices.rentalFlow.sendRentalCompletionEmails(rentalWithDetails.owner, rentalWithDetails.renter, rentalWithDetails);
|
||||
await emailServices.rentalFlow.sendRentalCompletionEmails(
|
||||
rentalWithDetails.owner,
|
||||
rentalWithDetails.renter,
|
||||
rentalWithDetails
|
||||
);
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Rental completion emails sent", {
|
||||
rentalId,
|
||||
@@ -1224,7 +1263,11 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
|
||||
// Send notification to customer service
|
||||
const owner = await User.findByPk(rental.ownerId);
|
||||
const renter = await User.findByPk(rental.renterId);
|
||||
await emailServices.customerService.sendLostItemToCustomerService(updatedRental, owner, renter);
|
||||
await emailServices.customerService.sendLostItemToCustomerService(
|
||||
updatedRental,
|
||||
owner,
|
||||
renter
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -1261,14 +1304,14 @@ router.post("/:id/mark-return", authenticateToken, async (req, res, next) => {
|
||||
|
||||
// Allowed fields for damage report (prevents mass assignment)
|
||||
const ALLOWED_DAMAGE_REPORT_FIELDS = [
|
||||
'description',
|
||||
'canBeFixed',
|
||||
'repairCost',
|
||||
'needsReplacement',
|
||||
'replacementCost',
|
||||
'proofOfOwnership',
|
||||
'actualReturnDateTime',
|
||||
'imageFilenames',
|
||||
"description",
|
||||
"canBeFixed",
|
||||
"repairCost",
|
||||
"needsReplacement",
|
||||
"replacementCost",
|
||||
"proofOfOwnership",
|
||||
"actualReturnDateTime",
|
||||
"imageFilenames",
|
||||
];
|
||||
|
||||
function extractAllowedDamageFields(body) {
|
||||
@@ -1295,9 +1338,13 @@ router.post("/:id/report-damage", authenticateToken, async (req, res, next) => {
|
||||
? damageInfo.imageFilenames
|
||||
: [];
|
||||
|
||||
const keyValidation = validateS3Keys(imageFilenamesArray, 'damage-reports', {
|
||||
maxKeys: IMAGE_LIMITS.damageReports,
|
||||
});
|
||||
const keyValidation = validateS3Keys(
|
||||
imageFilenamesArray,
|
||||
"damage-reports",
|
||||
{
|
||||
maxKeys: IMAGE_LIMITS.damageReports,
|
||||
}
|
||||
);
|
||||
if (!keyValidation.valid) {
|
||||
return res.status(400).json({
|
||||
error: keyValidation.error,
|
||||
|
||||
Reference in New Issue
Block a user