restructuring for rental requests. started double blind reviews
This commit is contained in:
@@ -103,10 +103,6 @@ const Item = sequelize.define("Item", {
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
},
|
||||
unavailablePeriods: {
|
||||
type: DataTypes.JSONB,
|
||||
defaultValue: [],
|
||||
},
|
||||
availableAfter: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: "09:00",
|
||||
|
||||
@@ -39,6 +39,12 @@ const Rental = sequelize.define('Rental', {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false
|
||||
},
|
||||
startTime: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
endTime: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
totalAmount: {
|
||||
type: DataTypes.DECIMAL(10, 2),
|
||||
allowNull: false
|
||||
@@ -61,6 +67,50 @@ const Rental = sequelize.define('Rental', {
|
||||
notes: {
|
||||
type: DataTypes.TEXT
|
||||
},
|
||||
// Renter's review of the item (existing fields renamed for clarity)
|
||||
itemRating: {
|
||||
type: DataTypes.INTEGER,
|
||||
validate: {
|
||||
min: 1,
|
||||
max: 5
|
||||
}
|
||||
},
|
||||
itemReview: {
|
||||
type: DataTypes.TEXT
|
||||
},
|
||||
itemReviewSubmittedAt: {
|
||||
type: DataTypes.DATE
|
||||
},
|
||||
itemReviewVisible: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
// Owner's review of the renter
|
||||
renterRating: {
|
||||
type: DataTypes.INTEGER,
|
||||
validate: {
|
||||
min: 1,
|
||||
max: 5
|
||||
}
|
||||
},
|
||||
renterReview: {
|
||||
type: DataTypes.TEXT
|
||||
},
|
||||
renterReviewSubmittedAt: {
|
||||
type: DataTypes.DATE
|
||||
},
|
||||
renterReviewVisible: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
defaultValue: false
|
||||
},
|
||||
// Private messages (always visible to recipient)
|
||||
itemPrivateMessage: {
|
||||
type: DataTypes.TEXT
|
||||
},
|
||||
renterPrivateMessage: {
|
||||
type: DataTypes.TEXT
|
||||
},
|
||||
// Legacy fields for backwards compatibility
|
||||
rating: {
|
||||
type: DataTypes.INTEGER,
|
||||
validate: {
|
||||
|
||||
@@ -4,10 +4,53 @@ const { Rental, Item, User } = require('../models'); // Import from models/index
|
||||
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'] }
|
||||
@@ -15,8 +58,10 @@ router.get('/my-rentals', authenticateToken, async (req, res) => {
|
||||
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 });
|
||||
}
|
||||
});
|
||||
@@ -25,6 +70,7 @@ 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'] }
|
||||
@@ -32,15 +78,17 @@ router.get('/my-listings', authenticateToken, async (req, res) => {
|
||||
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, deliveryMethod, deliveryAddress, notes } = req.body;
|
||||
const { itemId, startDate, endDate, startTime, endTime, deliveryMethod, deliveryAddress, notes } = req.body;
|
||||
|
||||
const item = await Item.findByPk(itemId);
|
||||
if (!item) {
|
||||
@@ -79,6 +127,8 @@ router.post('/', authenticateToken, async (req, res) => {
|
||||
ownerId: item.ownerId,
|
||||
startDate,
|
||||
endDate,
|
||||
startTime,
|
||||
endTime,
|
||||
totalAmount,
|
||||
deliveryMethod,
|
||||
deliveryAddress,
|
||||
@@ -128,6 +178,125 @@ router.put('/:id/status', authenticateToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 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;
|
||||
|
||||
Reference in New Issue
Block a user