const express = require("express"); const Post = require("../models/Post"); const couchdbService = require("../services/couchdbService"); 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 { getCacheMiddleware, invalidateCacheByPattern } = require("../middleware/cache"); const router = express.Router(); // Get all posts (with pagination) router.get( "/", paginate, getCacheMiddleware(60), // Cache for 1 minute asyncHandler(async (req, res) => { const { skip, limit, page } = req.pagination; const posts = await Post.findAll({ skip, limit }); 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; if (!content) { 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 post = await Post.create(postData); // Invalidate posts cache invalidateCacheByPattern('/api/posts'); res.json({ post, pointsAwarded: 5, // Standard post creation points newBalance: 0, // Will be updated by couchdbService badgesEarned: [], // Will be handled by couchdbService }); }), ); // 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.userId !== 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", ); const updatedPost = await Post.updatePost(req.params.id, { imageUrl: result.url, cloudinaryPublicId: result.publicId, }); res.json(updatedPost); }), ); // 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.includes(req.user.id)) { return res.status(400).json({ msg: "Post already liked" }); } const updatedPost = await Post.addLike(req.params.id, req.user.id); res.json(updatedPost.likes); }), ); // Unlike a post router.put( "/unlike/: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 been liked by this user if (!post.likes.includes(req.user.id)) { return res.status(400).json({ msg: "Post not yet liked" }); } const updatedPost = await Post.removeLike(req.params.id, req.user.id); res.json(updatedPost.likes); }), ); module.exports = router;