feat(backend): implement comments, image uploads, and data consistency
Implement additional backend features and improve data models: Comments System: - Create Comment model with user and post relationships - Add comments routes: GET /api/posts/:postId/comments (paginated), POST (create), DELETE (own comments) - Update Post model with commentsCount field - Emit Socket.IO events for newComment and commentDeleted - Pagination support for comment lists - Authorization checks (users can only delete own comments) - 500 character limit on comments Image Upload System: - Implement Cloudinary configuration (config/cloudinary.js) - Add uploadImage() and deleteImage() helper functions - Image optimization: max 1000x1000, auto quality, auto format (WebP) - Integrate image upload in users routes (profile pictures) - Integrate image upload in posts routes (post images with add/update endpoints) - File validation: 5MB limit, JPG/PNG/GIF/WebP only - Automatic image deletion when removing posts/reports Data Consistency Improvements: - Add cascade deletes in Street model (remove from user, delete associated tasks) - Add cascade deletes in Task model (remove from user completedTasks) - Add cascade deletes in Post model (remove from user posts) - Update user relationships on save (adoptedStreets, completedTasks, posts, events) - Add proper indexes for performance (2dsphere for location, compound indexes) - Add virtual relationships and toJSON configurations Model Updates: - Street: Add cascade hooks, location 2dsphere index - Task: Add cascade hooks, compound indexes for queries - Post: Add imageUrl, cloudinaryPublicId, commentsCount fields - Event: Add participants tracking - Report: Add image upload support - User: Add earnedBadges virtual, profilePicture, cloudinaryPublicId 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,44 +1,126 @@
|
||||
const express = require("express");
|
||||
const Report = require("../models/Report");
|
||||
const auth = require("../middleware/auth");
|
||||
const { asyncHandler } = require("../middleware/errorHandler");
|
||||
const {
|
||||
createReportValidation,
|
||||
reportIdValidation,
|
||||
} = require("../middleware/validators/reportValidator");
|
||||
const { upload, handleUploadError } = require("../middleware/upload");
|
||||
const { uploadImage, deleteImage } = require("../config/cloudinary");
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// Get all reports
|
||||
router.get("/", async (req, res) => {
|
||||
try {
|
||||
// Get all reports (with pagination)
|
||||
router.get(
|
||||
"/",
|
||||
asyncHandler(async (req, res) => {
|
||||
const { paginate, buildPaginatedResponse } = require("../middleware/pagination");
|
||||
|
||||
// Parse pagination params
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = Math.min(parseInt(req.query.limit) || 10, 100);
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const reports = await Report.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit)
|
||||
.populate("street", ["name"])
|
||||
.populate("user", ["name"]);
|
||||
res.json(reports);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
.populate("user", ["name", "profilePicture"]);
|
||||
|
||||
// Create a report
|
||||
router.post("/", auth, async (req, res) => {
|
||||
const { street, issue } = req.body;
|
||||
const totalCount = await Report.countDocuments();
|
||||
|
||||
try {
|
||||
const newReport = new Report({
|
||||
res.json(buildPaginatedResponse(reports, totalCount, page, limit));
|
||||
}),
|
||||
);
|
||||
|
||||
// Create a report with optional image
|
||||
router.post(
|
||||
"/",
|
||||
auth,
|
||||
upload.single("image"),
|
||||
handleUploadError,
|
||||
createReportValidation,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { street, issue } = req.body;
|
||||
|
||||
const reportData = {
|
||||
street,
|
||||
user: req.user.id,
|
||||
issue,
|
||||
});
|
||||
};
|
||||
|
||||
// Upload image if provided
|
||||
if (req.file) {
|
||||
const result = await uploadImage(
|
||||
req.file.buffer,
|
||||
"adopt-a-street/reports",
|
||||
);
|
||||
reportData.imageUrl = result.url;
|
||||
reportData.cloudinaryPublicId = result.publicId;
|
||||
}
|
||||
|
||||
const newReport = new Report(reportData);
|
||||
const report = await newReport.save();
|
||||
|
||||
// Populate user and street data
|
||||
await report.populate([
|
||||
{ path: "user", select: "name profilePicture" },
|
||||
{ path: "street", select: "name" },
|
||||
]);
|
||||
|
||||
res.json(report);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
// Add image to existing report
|
||||
router.post(
|
||||
"/:id/image",
|
||||
auth,
|
||||
upload.single("image"),
|
||||
handleUploadError,
|
||||
reportIdValidation,
|
||||
asyncHandler(async (req, res) => {
|
||||
const report = await Report.findById(req.params.id);
|
||||
if (!report) {
|
||||
return res.status(404).json({ msg: "Report not found" });
|
||||
}
|
||||
|
||||
// Verify user owns the report
|
||||
if (report.user.toString() !== req.user.id) {
|
||||
return res.status(403).json({ msg: "Not authorized" });
|
||||
}
|
||||
|
||||
if (!req.file) {
|
||||
return res.status(400).json({ msg: "No image file provided" });
|
||||
}
|
||||
|
||||
// Delete old image if exists
|
||||
if (report.cloudinaryPublicId) {
|
||||
await deleteImage(report.cloudinaryPublicId);
|
||||
}
|
||||
|
||||
// Upload new image
|
||||
const result = await uploadImage(
|
||||
req.file.buffer,
|
||||
"adopt-a-street/reports",
|
||||
);
|
||||
|
||||
report.imageUrl = result.url;
|
||||
report.cloudinaryPublicId = result.publicId;
|
||||
await report.save();
|
||||
|
||||
res.json(report);
|
||||
}),
|
||||
);
|
||||
|
||||
// Resolve a report
|
||||
router.put("/:id", auth, async (req, res) => {
|
||||
try {
|
||||
router.put(
|
||||
"/:id",
|
||||
auth,
|
||||
reportIdValidation,
|
||||
asyncHandler(async (req, res) => {
|
||||
const report = await Report.findById(req.params.id);
|
||||
if (!report) {
|
||||
return res.status(404).json({ msg: "Report not found" });
|
||||
@@ -49,10 +131,7 @@ router.put("/:id", auth, async (req, res) => {
|
||||
await report.save();
|
||||
|
||||
res.json(report);
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
res.status(500).send("Server error");
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
|
||||
Reference in New Issue
Block a user