Files
adopt-a-street/backend/routes/users.js
William Valentin 7c70a8d098 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>
2025-11-01 10:43:08 -07:00

91 lines
2.3 KiB
JavaScript

const express = require("express");
const User = require("../models/User");
const auth = require("../middleware/auth");
const { asyncHandler } = require("../middleware/errorHandler");
const { userIdValidation } = require("../middleware/validators/userValidator");
const { upload, handleUploadError } = require("../middleware/upload");
const { uploadImage, deleteImage } = require("../config/cloudinary");
const router = express.Router();
// Get user by ID
router.get(
"/:id",
auth,
userIdValidation,
asyncHandler(async (req, res) => {
const user = await User.findById(req.params.id).populate("adoptedStreets");
if (!user) {
return res.status(404).json({ msg: "User not found" });
}
res.json(user);
}),
);
// Upload profile picture
router.post(
"/profile-picture",
auth,
upload.single("image"),
handleUploadError,
asyncHandler(async (req, res) => {
if (!req.file) {
return res.status(400).json({ msg: "No image file provided" });
}
const user = await User.findById(req.user.id);
if (!user) {
return res.status(404).json({ msg: "User not found" });
}
// Delete old profile picture if exists
if (user.cloudinaryPublicId) {
await deleteImage(user.cloudinaryPublicId);
}
// Upload new image to Cloudinary
const result = await uploadImage(
req.file.buffer,
"adopt-a-street/profiles",
);
// Update user with new profile picture
user.profilePicture = result.url;
user.cloudinaryPublicId = result.publicId;
await user.save();
res.json({
msg: "Profile picture updated successfully",
profilePicture: user.profilePicture,
});
}),
);
// Delete profile picture
router.delete(
"/profile-picture",
auth,
asyncHandler(async (req, res) => {
const user = await User.findById(req.user.id);
if (!user) {
return res.status(404).json({ msg: "User not found" });
}
if (!user.cloudinaryPublicId) {
return res.status(400).json({ msg: "No profile picture to delete" });
}
// Delete image from Cloudinary
await deleteImage(user.cloudinaryPublicId);
// Remove from user
user.profilePicture = undefined;
user.cloudinaryPublicId = undefined;
await user.save();
res.json({ msg: "Profile picture deleted successfully" });
}),
);
module.exports = router;