diff --git a/backend/models/Item.js b/backend/models/Item.js index 3e3f8f8..c66fc54 100644 --- a/backend/models/Item.js +++ b/backend/models/Item.js @@ -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: { diff --git a/backend/models/User.js b/backend/models/User.js index a875a8b..348ca39 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -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: { diff --git a/backend/routes/items.js b/backend/routes/items.js index 2c784b4..3778e9f 100644 --- a/backend/routes/items.js +++ b/backend/routes/items.js @@ -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, { diff --git a/frontend/src/components/AvailabilitySettings.tsx b/frontend/src/components/AvailabilitySettings.tsx index 4c2b222..3b48676 100644 --- a/frontend/src/components/AvailabilitySettings.tsx +++ b/frontend/src/components/AvailabilitySettings.tsx @@ -60,7 +60,7 @@ const AvailabilitySettings: React.FC = ({
= ({ value={data.generalAvailableBefore} onChange={handleGeneralChange} disabled={data.specifyTimesPerDay} - required > {generateTimeOptions().map((option) => (
+
+ + )} ); };