admin can soft delete listings
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
const express = require("express");
|
||||
const { Op } = require("sequelize");
|
||||
const { Item, User, Rental } = require("../models"); // Import from models/index.js to get models with associations
|
||||
const { authenticateToken, requireVerifiedEmail } = require("../middleware/auth");
|
||||
const { authenticateToken, requireVerifiedEmail, requireAdmin, optionalAuth } = require("../middleware/auth");
|
||||
const logger = require("../utils/logger");
|
||||
const router = express.Router();
|
||||
|
||||
@@ -17,7 +17,9 @@ router.get("/", async (req, res) => {
|
||||
limit = 20,
|
||||
} = req.query;
|
||||
|
||||
const where = {};
|
||||
const where = {
|
||||
isDeleted: false // Always exclude soft-deleted items from public browse
|
||||
};
|
||||
|
||||
if (minPrice || maxPrice) {
|
||||
where.pricePerDay = {};
|
||||
@@ -97,6 +99,7 @@ router.get("/recommendations", authenticateToken, async (req, res) => {
|
||||
const recommendations = await Item.findAll({
|
||||
where: {
|
||||
availability: true,
|
||||
isDeleted: false,
|
||||
},
|
||||
limit: 10,
|
||||
order: [["createdAt", "DESC"]],
|
||||
@@ -170,7 +173,7 @@ router.get('/:id/reviews', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/:id", async (req, res) => {
|
||||
router.get("/:id", optionalAuth, async (req, res) => {
|
||||
try {
|
||||
const item = await Item.findByPk(req.params.id, {
|
||||
include: [
|
||||
@@ -179,6 +182,11 @@ router.get("/:id", async (req, res) => {
|
||||
as: "owner",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: "deleter",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@@ -186,6 +194,15 @@ router.get("/:id", async (req, res) => {
|
||||
return res.status(404).json({ error: "Item not found" });
|
||||
}
|
||||
|
||||
// Check if item is deleted - only allow admins to view
|
||||
if (item.isDeleted) {
|
||||
const isAdmin = req.user?.role === 'admin';
|
||||
|
||||
if (!isAdmin) {
|
||||
return res.status(404).json({ error: "Item not found" });
|
||||
}
|
||||
}
|
||||
|
||||
// Round coordinates to 2 decimal places for map display while keeping precise values in database
|
||||
const itemResponse = item.toJSON();
|
||||
if (itemResponse.latitude !== null && itemResponse.latitude !== undefined) {
|
||||
@@ -347,4 +364,156 @@ router.delete("/:id", authenticateToken, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Admin endpoints
|
||||
router.delete("/admin/:id", authenticateToken, requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const { reason } = req.body;
|
||||
|
||||
if (!reason || !reason.trim()) {
|
||||
return res.status(400).json({ error: "Deletion reason is required" });
|
||||
}
|
||||
|
||||
const item = await Item.findByPk(req.params.id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: "owner",
|
||||
attributes: ["id", "username", "firstName", "lastName", "email"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (!item) {
|
||||
return res.status(404).json({ error: "Item not found" });
|
||||
}
|
||||
|
||||
if (item.isDeleted) {
|
||||
return res.status(400).json({ error: "Item is already deleted" });
|
||||
}
|
||||
|
||||
// Check for active or upcoming rentals
|
||||
const activeRentals = await Rental.count({
|
||||
where: {
|
||||
itemId: req.params.id,
|
||||
status: {
|
||||
[Op.in]: ['pending', 'confirmed', 'active']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (activeRentals > 0) {
|
||||
return res.status(400).json({
|
||||
error: "Cannot delete item with active or upcoming rentals",
|
||||
code: "ACTIVE_RENTALS_EXIST",
|
||||
activeRentalsCount: activeRentals
|
||||
});
|
||||
}
|
||||
|
||||
// Soft delete the item
|
||||
await item.update({
|
||||
isDeleted: true,
|
||||
deletedBy: req.user.id,
|
||||
deletedAt: new Date(),
|
||||
deletionReason: reason.trim()
|
||||
});
|
||||
|
||||
const updatedItem = await Item.findByPk(item.id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: "owner",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
},
|
||||
{
|
||||
model: User,
|
||||
as: "deleter",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
// Send email notification to owner
|
||||
try {
|
||||
const emailServices = require("../services/email");
|
||||
await emailServices.userEngagement.sendItemDeletionNotificationToOwner(
|
||||
item.owner,
|
||||
item,
|
||||
reason.trim()
|
||||
);
|
||||
console.log(`Item deletion notification email sent to owner ${item.ownerId}`);
|
||||
} catch (emailError) {
|
||||
// Log but don't fail the deletion
|
||||
console.error('Failed to send item deletion notification email:', emailError.message);
|
||||
}
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item soft deleted by admin", {
|
||||
itemId: req.params.id,
|
||||
deletedBy: req.user.id,
|
||||
ownerId: item.ownerId,
|
||||
reason: reason.trim()
|
||||
});
|
||||
|
||||
res.json(updatedItem);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Admin item soft delete failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
itemId: req.params.id,
|
||||
adminId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.patch("/admin/:id/restore", authenticateToken, requireAdmin, async (req, res) => {
|
||||
try {
|
||||
const item = await Item.findByPk(req.params.id);
|
||||
|
||||
if (!item) {
|
||||
return res.status(404).json({ error: "Item not found" });
|
||||
}
|
||||
|
||||
if (!item.isDeleted) {
|
||||
return res.status(400).json({ error: "Item is not deleted" });
|
||||
}
|
||||
|
||||
// Restore the item
|
||||
await item.update({
|
||||
isDeleted: false,
|
||||
deletedBy: null,
|
||||
deletedAt: null
|
||||
});
|
||||
|
||||
const updatedItem = await Item.findByPk(item.id, {
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: "owner",
|
||||
attributes: ["id", "username", "firstName", "lastName"],
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.info("Item restored by admin", {
|
||||
itemId: req.params.id,
|
||||
restoredBy: req.user.id,
|
||||
ownerId: item.ownerId
|
||||
});
|
||||
|
||||
res.json(updatedItem);
|
||||
} catch (error) {
|
||||
const reqLogger = logger.withRequestId(req.id);
|
||||
reqLogger.error("Admin item restore failed", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
itemId: req.params.id,
|
||||
adminId: req.user.id
|
||||
});
|
||||
res.status(500).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user