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>
64 lines
1.7 KiB
JavaScript
64 lines
1.7 KiB
JavaScript
const cloudinary = require("cloudinary").v2;
|
|
|
|
// Configure Cloudinary with environment variables
|
|
cloudinary.config({
|
|
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
|
|
api_key: process.env.CLOUDINARY_API_KEY,
|
|
api_secret: process.env.CLOUDINARY_API_SECRET,
|
|
});
|
|
|
|
/**
|
|
* Upload image buffer to Cloudinary
|
|
* @param {Buffer} fileBuffer - Image file buffer from multer
|
|
* @param {string} folder - Cloudinary folder path
|
|
* @returns {Promise<Object>} Cloudinary upload result with url and public_id
|
|
*/
|
|
const uploadImage = (fileBuffer, folder = "adopt-a-street") => {
|
|
return new Promise((resolve, reject) => {
|
|
cloudinary.uploader
|
|
.upload_stream(
|
|
{
|
|
folder: folder,
|
|
resource_type: "image",
|
|
transformation: [
|
|
{ width: 1000, height: 1000, crop: "limit" }, // Limit max dimensions
|
|
{ quality: "auto" }, // Auto quality optimization
|
|
{ fetch_format: "auto" }, // Auto format selection (WebP, etc.)
|
|
],
|
|
},
|
|
(error, result) => {
|
|
if (error) {
|
|
reject(error);
|
|
} else {
|
|
resolve({
|
|
url: result.secure_url,
|
|
publicId: result.public_id,
|
|
});
|
|
}
|
|
},
|
|
)
|
|
.end(fileBuffer);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Delete image from Cloudinary
|
|
* @param {string} publicId - Cloudinary public_id of the image
|
|
* @returns {Promise<Object>} Cloudinary deletion result
|
|
*/
|
|
const deleteImage = async (publicId) => {
|
|
try {
|
|
const result = await cloudinary.uploader.destroy(publicId);
|
|
return result;
|
|
} catch (error) {
|
|
console.error("Error deleting image from Cloudinary:", error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
uploadImage,
|
|
deleteImage,
|
|
cloudinary,
|
|
};
|