Image is required for creating an item, required fields actually required, Available After and Available Before defaults changed, delete confirmation modal for deleting an item
This commit is contained in:
@@ -95,11 +95,11 @@ const Item = sequelize.define("Item", {
|
||||
},
|
||||
availableAfter: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: "09:00",
|
||||
defaultValue: "00:00",
|
||||
},
|
||||
availableBefore: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: "17:00",
|
||||
defaultValue: "23:00",
|
||||
},
|
||||
specifyTimesPerDay: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
@@ -108,13 +108,13 @@ const Item = sequelize.define("Item", {
|
||||
weeklyTimes: {
|
||||
type: DataTypes.JSONB,
|
||||
defaultValue: {
|
||||
sunday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
monday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
tuesday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
wednesday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
thursday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
friday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
saturday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
sunday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
monday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
tuesday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
wednesday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
thursday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
friday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
saturday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
},
|
||||
},
|
||||
ownerId: {
|
||||
|
||||
@@ -89,11 +89,11 @@ const User = sequelize.define(
|
||||
},
|
||||
defaultAvailableAfter: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: "09:00",
|
||||
defaultValue: "00:00",
|
||||
},
|
||||
defaultAvailableBefore: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue: "17:00",
|
||||
defaultValue: "23:00",
|
||||
},
|
||||
defaultSpecifyTimesPerDay: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
@@ -102,13 +102,13 @@ const User = sequelize.define(
|
||||
defaultWeeklyTimes: {
|
||||
type: DataTypes.JSONB,
|
||||
defaultValue: {
|
||||
sunday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
monday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
tuesday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
wednesday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
thursday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
friday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
saturday: { availableAfter: "09:00", availableBefore: "17:00" },
|
||||
sunday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
monday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
tuesday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
wednesday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
thursday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
friday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
saturday: { availableAfter: "00:00", availableBefore: "23:00" },
|
||||
},
|
||||
},
|
||||
stripeConnectedAccountId: {
|
||||
|
||||
@@ -328,22 +328,46 @@ router.post("/", authenticateToken, requireVerifiedEmail, async (req, res, next)
|
||||
// Extract only allowed fields (prevents mass assignment)
|
||||
const allowedData = extractAllowedFields(req.body);
|
||||
|
||||
// Validate imageFilenames if provided
|
||||
if (allowedData.imageFilenames) {
|
||||
const imageFilenames = Array.isArray(allowedData.imageFilenames)
|
||||
? allowedData.imageFilenames
|
||||
: [];
|
||||
// Validate imageFilenames - at least one image is required
|
||||
const imageFilenames = Array.isArray(allowedData.imageFilenames)
|
||||
? allowedData.imageFilenames
|
||||
: [];
|
||||
|
||||
const keyValidation = validateS3Keys(imageFilenames, 'items', { maxKeys: IMAGE_LIMITS.items });
|
||||
if (!keyValidation.valid) {
|
||||
return res.status(400).json({
|
||||
error: keyValidation.error,
|
||||
details: keyValidation.invalidKeys
|
||||
});
|
||||
}
|
||||
allowedData.imageFilenames = imageFilenames;
|
||||
if (imageFilenames.length === 0) {
|
||||
return res.status(400).json({
|
||||
error: "At least one image is required to create a listing"
|
||||
});
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if (!allowedData.name || !allowedData.name.trim()) {
|
||||
return res.status(400).json({ error: "Item name is required" });
|
||||
}
|
||||
if (!allowedData.address1 || !allowedData.address1.trim()) {
|
||||
return res.status(400).json({ error: "Address is required" });
|
||||
}
|
||||
if (!allowedData.city || !allowedData.city.trim()) {
|
||||
return res.status(400).json({ error: "City is required" });
|
||||
}
|
||||
if (!allowedData.state || !allowedData.state.trim()) {
|
||||
return res.status(400).json({ error: "State is required" });
|
||||
}
|
||||
if (!allowedData.zipCode || !allowedData.zipCode.trim()) {
|
||||
return res.status(400).json({ error: "ZIP code is required" });
|
||||
}
|
||||
if (!allowedData.replacementCost || Number(allowedData.replacementCost) <= 0) {
|
||||
return res.status(400).json({ error: "Replacement cost is required" });
|
||||
}
|
||||
|
||||
const keyValidation = validateS3Keys(imageFilenames, 'items', { maxKeys: IMAGE_LIMITS.items });
|
||||
if (!keyValidation.valid) {
|
||||
return res.status(400).json({
|
||||
error: keyValidation.error,
|
||||
details: keyValidation.invalidKeys
|
||||
});
|
||||
}
|
||||
allowedData.imageFilenames = imageFilenames;
|
||||
|
||||
const item = await Item.create({
|
||||
...allowedData,
|
||||
ownerId: req.user.id,
|
||||
@@ -428,6 +452,13 @@ router.put("/:id", authenticateToken, async (req, res, next) => {
|
||||
? allowedData.imageFilenames
|
||||
: [];
|
||||
|
||||
// Require at least one image
|
||||
if (imageFilenames.length === 0) {
|
||||
return res.status(400).json({
|
||||
error: "At least one image is required for a listing"
|
||||
});
|
||||
}
|
||||
|
||||
const keyValidation = validateS3Keys(imageFilenames, 'items', { maxKeys: IMAGE_LIMITS.items });
|
||||
if (!keyValidation.valid) {
|
||||
return res.status(400).json({
|
||||
@@ -438,6 +469,26 @@ router.put("/:id", authenticateToken, async (req, res, next) => {
|
||||
allowedData.imageFilenames = imageFilenames;
|
||||
}
|
||||
|
||||
// Validate required fields if they are being updated
|
||||
if (allowedData.name !== undefined && (!allowedData.name || !allowedData.name.trim())) {
|
||||
return res.status(400).json({ error: "Item name is required" });
|
||||
}
|
||||
if (allowedData.address1 !== undefined && (!allowedData.address1 || !allowedData.address1.trim())) {
|
||||
return res.status(400).json({ error: "Address is required" });
|
||||
}
|
||||
if (allowedData.city !== undefined && (!allowedData.city || !allowedData.city.trim())) {
|
||||
return res.status(400).json({ error: "City is required" });
|
||||
}
|
||||
if (allowedData.state !== undefined && (!allowedData.state || !allowedData.state.trim())) {
|
||||
return res.status(400).json({ error: "State is required" });
|
||||
}
|
||||
if (allowedData.zipCode !== undefined && (!allowedData.zipCode || !allowedData.zipCode.trim())) {
|
||||
return res.status(400).json({ error: "ZIP code is required" });
|
||||
}
|
||||
if (allowedData.replacementCost !== undefined && (!allowedData.replacementCost || Number(allowedData.replacementCost) <= 0)) {
|
||||
return res.status(400).json({ error: "Replacement cost is required" });
|
||||
}
|
||||
|
||||
await item.update(allowedData);
|
||||
|
||||
const updatedItem = await Item.findByPk(item.id, {
|
||||
|
||||
Reference in New Issue
Block a user