Files
rentall-app/backend/routes/rentals.js

325 lines
9.8 KiB
JavaScript

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 } = require('../middleware/auth');
const router = express.Router();
// Helper function to check and update review visibility
const checkAndUpdateReviewVisibility = async (rental) => {
const now = new Date();
const tenMinutesInMs = 10 * 60 * 1000; // 10 minutes
let needsUpdate = false;
let updates = {};
// Check if both reviews are submitted
if (rental.itemReviewSubmittedAt && rental.renterReviewSubmittedAt) {
if (!rental.itemReviewVisible || !rental.renterReviewVisible) {
updates.itemReviewVisible = true;
updates.renterReviewVisible = true;
needsUpdate = true;
}
} else {
// Check item review visibility (10-minute rule)
if (rental.itemReviewSubmittedAt && !rental.itemReviewVisible) {
const timeSinceSubmission = now - new Date(rental.itemReviewSubmittedAt);
if (timeSinceSubmission >= tenMinutesInMs) {
updates.itemReviewVisible = true;
needsUpdate = true;
}
}
// Check renter review visibility (10-minute rule)
if (rental.renterReviewSubmittedAt && !rental.renterReviewVisible) {
const timeSinceSubmission = now - new Date(rental.renterReviewSubmittedAt);
if (timeSinceSubmission >= tenMinutesInMs) {
updates.renterReviewVisible = true;
needsUpdate = true;
}
}
}
if (needsUpdate) {
await rental.update(updates);
}
return rental;
};
router.get('/my-rentals', authenticateToken, async (req, res) => {
try {
const rentals = await Rental.findAll({
where: { renterId: req.user.id },
// Remove explicit attributes to let Sequelize handle missing columns gracefully
include: [
{ model: Item, as: 'item' },
{ model: User, as: 'owner', attributes: ['id', 'username', 'firstName', 'lastName'] }
],
order: [['createdAt', 'DESC']]
});
console.log('My-rentals data:', rentals.length > 0 ? rentals[0].toJSON() : 'No rentals');
res.json(rentals);
} catch (error) {
console.error('Error in my-rentals route:', error);
res.status(500).json({ error: error.message });
}
});
router.get('/my-listings', authenticateToken, async (req, res) => {
try {
const rentals = await Rental.findAll({
where: { ownerId: req.user.id },
// Remove explicit attributes to let Sequelize handle missing columns gracefully
include: [
{ model: Item, as: 'item' },
{ model: User, as: 'renter', attributes: ['id', 'username', 'firstName', 'lastName'] }
],
order: [['createdAt', 'DESC']]
});
console.log('My-listings rentals:', rentals.length > 0 ? rentals[0].toJSON() : 'No rentals');
res.json(rentals);
} catch (error) {
console.error('Error in my-listings route:', error);
res.status(500).json({ error: error.message });
}
});
router.post('/', authenticateToken, async (req, res) => {
try {
const { itemId, startDate, endDate, startTime, endTime, deliveryMethod, deliveryAddress, notes } = req.body;
const item = await Item.findByPk(itemId);
if (!item) {
return res.status(404).json({ error: 'Item not found' });
}
if (!item.availability) {
return res.status(400).json({ error: 'Item is not available' });
}
const overlappingRental = await Rental.findOne({
where: {
itemId,
status: { [Op.in]: ['confirmed', 'active'] },
[Op.or]: [
{
startDate: { [Op.between]: [startDate, endDate] }
},
{
endDate: { [Op.between]: [startDate, endDate] }
}
]
}
});
if (overlappingRental) {
return res.status(400).json({ error: 'Item is already booked for these dates' });
}
const rentalDays = Math.ceil((new Date(endDate) - new Date(startDate)) / (1000 * 60 * 60 * 24));
const totalAmount = rentalDays * (item.pricePerDay || 0);
const rental = await Rental.create({
itemId,
renterId: req.user.id,
ownerId: item.ownerId,
startDate,
endDate,
startTime,
endTime,
totalAmount,
deliveryMethod,
deliveryAddress,
notes
});
const rentalWithDetails = await Rental.findByPk(rental.id, {
include: [
{ model: Item, as: 'item' },
{ model: User, as: 'owner', attributes: ['id', 'username', 'firstName', 'lastName'] },
{ model: User, as: 'renter', attributes: ['id', 'username', 'firstName', 'lastName'] }
]
});
res.status(201).json(rentalWithDetails);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
router.put('/:id/status', authenticateToken, async (req, res) => {
try {
const { status } = req.body;
const rental = await Rental.findByPk(req.params.id);
if (!rental) {
return res.status(404).json({ error: 'Rental not found' });
}
if (rental.ownerId !== req.user.id && rental.renterId !== req.user.id) {
return res.status(403).json({ error: 'Unauthorized' });
}
await rental.update({ status });
const updatedRental = await Rental.findByPk(rental.id, {
include: [
{ model: Item, as: 'item' },
{ model: User, as: 'owner', attributes: ['id', 'username', 'firstName', 'lastName'] },
{ model: User, as: 'renter', attributes: ['id', 'username', 'firstName', 'lastName'] }
]
});
res.json(updatedRental);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Owner reviews renter
router.post('/:id/review-renter', authenticateToken, async (req, res) => {
try {
const { rating, review, privateMessage } = req.body;
const rental = await Rental.findByPk(req.params.id);
if (!rental) {
return res.status(404).json({ error: 'Rental not found' });
}
if (rental.ownerId !== req.user.id) {
return res.status(403).json({ error: 'Only owners can review renters' });
}
if (rental.status !== 'completed') {
return res.status(400).json({ error: 'Can only review completed rentals' });
}
if (rental.renterReviewSubmittedAt) {
return res.status(400).json({ error: 'Renter review already submitted' });
}
// Submit the review and private message
await rental.update({
renterRating: rating,
renterReview: review,
renterReviewSubmittedAt: new Date(),
renterPrivateMessage: privateMessage
});
// Check and update visibility
const updatedRental = await checkAndUpdateReviewVisibility(rental);
res.json({
success: true,
reviewVisible: updatedRental.renterReviewVisible
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Renter reviews item
router.post('/:id/review-item', authenticateToken, async (req, res) => {
try {
const { rating, review, privateMessage } = req.body;
const rental = await Rental.findByPk(req.params.id);
if (!rental) {
return res.status(404).json({ error: 'Rental not found' });
}
if (rental.renterId !== req.user.id) {
return res.status(403).json({ error: 'Only renters can review items' });
}
if (rental.status !== 'completed') {
return res.status(400).json({ error: 'Can only review completed rentals' });
}
if (rental.itemReviewSubmittedAt) {
return res.status(400).json({ error: 'Item review already submitted' });
}
// Submit the review and private message
await rental.update({
itemRating: rating,
itemReview: review,
itemReviewSubmittedAt: new Date(),
itemPrivateMessage: privateMessage
});
// Check and update visibility
const updatedRental = await checkAndUpdateReviewVisibility(rental);
res.json({
success: true,
reviewVisible: updatedRental.itemReviewVisible
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Mark rental as completed (owner only)
router.post('/:id/mark-completed', authenticateToken, async (req, res) => {
try {
console.log('Mark completed endpoint hit for rental ID:', req.params.id);
const rental = await Rental.findByPk(req.params.id);
if (!rental) {
return res.status(404).json({ error: 'Rental not found' });
}
if (rental.ownerId !== req.user.id) {
return res.status(403).json({ error: 'Only owners can mark rentals as completed' });
}
if (!['active', 'confirmed'].includes(rental.status)) {
return res.status(400).json({ error: 'Can only mark active or confirmed rentals as completed' });
}
await rental.update({ status: 'completed' });
const updatedRental = await Rental.findByPk(rental.id, {
include: [
{ model: Item, as: 'item' },
{ model: User, as: 'owner', attributes: ['id', 'username', 'firstName', 'lastName'] },
{ model: User, as: 'renter', attributes: ['id', 'username', 'firstName', 'lastName'] }
]
});
res.json(updatedRental);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Legacy review endpoint (for backward compatibility)
router.post('/:id/review', authenticateToken, async (req, res) => {
try {
const { rating, review } = req.body;
const rental = await Rental.findByPk(req.params.id);
if (!rental) {
return res.status(404).json({ error: 'Rental not found' });
}
if (rental.renterId !== req.user.id) {
return res.status(403).json({ error: 'Only renters can leave reviews' });
}
if (rental.status !== 'completed') {
return res.status(400).json({ error: 'Can only review completed rentals' });
}
await rental.update({ rating, review });
res.json(rental);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
module.exports = router;