const express = require("express"); const mongoose = require("mongoose"); const Post = require("../models/Post"); const auth = require("../middleware/auth"); const { asyncHandler } = require("../middleware/errorHandler"); const { createPostValidation, postIdValidation, } = require("../middleware/validators/postValidator"); const { upload, handleUploadError } = require("../middleware/upload"); const { uploadImage, deleteImage } = require("../config/cloudinary"); const { paginate, buildPaginatedResponse } = require("../middleware/pagination"); const { awardPostCreationPoints, checkAndAwardBadges, } = require("../services/gamificationService"); const router = express.Router(); // Get all posts (with pagination) router.get( "/", paginate, asyncHandler(async (req, res) => { const { skip, limit, page } = req.pagination; const posts = await Post.find() .sort({ createdAt: -1 }) .skip(skip) .limit(limit) .populate("user", ["name", "profilePicture"]); const totalCount = await Post.countDocuments(); res.json(buildPaginatedResponse(posts, totalCount, page, limit)); }), ); // Create a post with optional image router.post( "/", auth, upload.single("image"), handleUploadError, asyncHandler(async (req, res) => { const { content } = req.body; const session = await mongoose.startSession(); session.startTransaction(); try { if (!content) { await session.abortTransaction(); session.endSession(); return res.status(400).json({ msg: "Content is required" }); } const postData = { user: req.user.id, content, }; // Upload image if provided if (req.file) { const result = await uploadImage( req.file.buffer, "adopt-a-street/posts", ); postData.imageUrl = result.url; postData.cloudinaryPublicId = result.publicId; } const newPost = new Post(postData); const post = await newPost.save({ session }); // Award points for post creation const { transaction } = await awardPostCreationPoints( req.user.id, post._id, session ); // Check and award badges const newBadges = await checkAndAwardBadges(req.user.id, session); await session.commitTransaction(); session.endSession(); // Populate user data before sending response await post.populate("user", ["name", "profilePicture"]); res.json({ post, pointsAwarded: transaction.amount, newBalance: transaction.balanceAfter, badgesEarned: newBadges, }); } catch (err) { await session.abortTransaction(); session.endSession(); throw err; } }), ); // Add image to existing post router.post( "/:id/image", auth, upload.single("image"), handleUploadError, postIdValidation, asyncHandler(async (req, res) => { const post = await Post.findById(req.params.id); if (!post) { return res.status(404).json({ msg: "Post not found" }); } // Verify user owns the post if (post.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 (post.cloudinaryPublicId) { await deleteImage(post.cloudinaryPublicId); } // Upload new image const result = await uploadImage( req.file.buffer, "adopt-a-street/posts", ); post.imageUrl = result.url; post.cloudinaryPublicId = result.publicId; await post.save(); res.json(post); }), ); // Like a post router.put( "/like/:id", auth, postIdValidation, asyncHandler(async (req, res) => { const post = await Post.findById(req.params.id); if (!post) { return res.status(404).json({ msg: "Post not found" }); } // Check if the post has already been liked by this user if ( post.likes.filter((like) => like.toString() === req.user.id).length > 0 ) { return res.status(400).json({ msg: "Post already liked" }); } post.likes.unshift(req.user.id); await post.save(); res.json(post.likes); }), ); module.exports = router;