From 5fca1595e36d155c20e9aea610344dc7fa9ddc7d Mon Sep 17 00:00:00 2001 From: Amish Bajracharya Date: Fri, 24 Jan 2025 20:19:30 +0545 Subject: [PATCH] minor changes for posting news --- admin-panel/src/services/postsService.ts | 4 +- api/controllers/post.controller.js | 873 ++++++++++++----------- api/package.json | 2 + api/routes/post.route.js | 36 +- 4 files changed, 484 insertions(+), 431 deletions(-) diff --git a/admin-panel/src/services/postsService.ts b/admin-panel/src/services/postsService.ts index 62cdfef..8e036cd 100644 --- a/admin-panel/src/services/postsService.ts +++ b/admin-panel/src/services/postsService.ts @@ -22,9 +22,9 @@ import { api } from "@/config/index"; } }; -export const createPost = async (postData: any) => { + const createPost = async (postData: any) => { const response = await api.post("/posts/admin", postData); return response.data.data; }; -export { fetchPosts }; \ No newline at end of file +export { fetchPosts, createPost }; \ No newline at end of file diff --git a/api/controllers/post.controller.js b/api/controllers/post.controller.js index 6ac69cd..6526158 100644 --- a/api/controllers/post.controller.js +++ b/api/controllers/post.controller.js @@ -2,6 +2,7 @@ import prisma from "../config/prisma.js"; import { createResponse } from "../utils/responseModel.js"; import { paginate } from "../utils/pagination.js"; import { config } from "../config/index.js"; +import cloudinary from "cloudinary"; const POSTS_URL = config.server.apiURL + "/posts"; const ADMIN_URL = POSTS_URL + "/admin"; @@ -17,43 +18,48 @@ const NEWS_ALGORITHM_API_URL = config.newsAlgorithm.apiURL; * @returns {void} */ export const getRecentPosts = async (req, res) => { - let { - page = 1, - limit = 6, - sortBy = "createdAt", - order = "desc" - } = req.query; - - page = parseInt(page); - limit = parseInt(limit); - - try { - const total = await prisma.post.count({ where: { status: 'PUBLISHED' } }); - const posts = await prisma.post.findMany({ - where: { status: 'PUBLISHED' }, - skip: (page - 1) * limit, - take: limit, - orderBy: { - [sortBy]: order, - }, - }); - - const pagination = paginate(total, posts.length, page, limit, POSTS_URL + "/recent"); - - if (pagination.error) { - return res.status(pagination.status).json(createResponse(false, pagination.status, pagination.message)); - } - - return res.status(200).json(createResponse( - true, - 200, - "Recent posts fetched successfully", - { pagination, posts } - )); - } catch (error) { - console.error(error.message); - return res.status(500).json(createResponse(false, 500, "Internal server error")); + let { page = 1, limit = 6, sortBy = "createdAt", order = "desc" } = req.query; + + page = parseInt(page); + limit = parseInt(limit); + + try { + const total = await prisma.post.count({ where: { status: "PUBLISHED" } }); + const posts = await prisma.post.findMany({ + where: { status: "PUBLISHED" }, + skip: (page - 1) * limit, + take: limit, + orderBy: { + [sortBy]: order, + }, + }); + + const pagination = paginate( + total, + posts.length, + page, + limit, + POSTS_URL + "/recent" + ); + + if (pagination.error) { + return res + .status(pagination.status) + .json(createResponse(false, pagination.status, pagination.message)); } + + return res.status(200).json( + createResponse(true, 200, "Recent posts fetched successfully", { + pagination, + posts, + }) + ); + } catch (error) { + console.error(error.message); + return res + .status(500) + .json(createResponse(false, 500, "Internal server error")); + } }; /** @@ -63,86 +69,99 @@ export const getRecentPosts = async (req, res) => { * @returns {void} */ export const searchNews = async (req, res) => { - let { - q, - page = 1, - limit = 9, - sortBy = "createdAt", - order = "desc" - } = req.query; - - // Ensure page and limit are integers and apply a maximum limit - page = parseInt(page); - limit = Math.min(parseInt(limit), 10); // Limit the number of items per request to 100 - - // Validate the search term `q` - if (!q || q.trim().length === 0) { - return res.status(400).json(createResponse(false, 400, "Search value is required")); + let { + q, + page = 1, + limit = 9, + sortBy = "createdAt", + order = "desc", + } = req.query; + + // Ensure page and limit are integers and apply a maximum limit + page = parseInt(page); + limit = Math.min(parseInt(limit), 10); // Limit the number of items per request to 100 + + // Validate the search term `q` + if (!q || q.trim().length === 0) { + return res + .status(400) + .json(createResponse(false, 400, "Search value is required")); + } + + // Validate sortBy to ensure it's a valid field + const validSortFields = ["createdAt", "title", "description"]; // Add more if necessary + if (!validSortFields.includes(sortBy)) { + return res + .status(400) + .json(createResponse(false, 400, "Invalid sort field")); + } + + try { + // Count total number of posts matching the search query + const total = await prisma.post.count({ + where: { + status: "PUBLISHED", + OR: [ + { title: { contains: q, mode: "insensitive" } }, + { description: { contains: q, mode: "insensitive" } }, + ], + }, + }); + + // Fetch posts matching the search query with pagination and sorting + const posts = await prisma.post.findMany({ + where: { + status: "PUBLISHED", + OR: [ + { title: { contains: q, mode: "insensitive" } }, + { description: { contains: q, mode: "insensitive" } }, + ], + }, + skip: (page - 1) * limit, // Skip the posts based on current page + take: limit, // Limit the number of posts returned + orderBy: { + [sortBy]: order, // Order by the selected field (createdAt, title, description) + }, + select: { + id: true, + title: true, + description: true, + createdAt: true, + image: true, + }, + }); + + if (!posts || posts.length === 0) { + return res.status(404).json(createResponse(false, 404, "No news found")); } - // Validate sortBy to ensure it's a valid field - const validSortFields = ['createdAt', 'title', 'description']; // Add more if necessary - if (!validSortFields.includes(sortBy)) { - return res.status(400).json(createResponse(false, 400, "Invalid sort field")); + // Calculate pagination details + const pagination = paginate( + total, + posts.length, + page, + limit, + POSTS_URL + "/search?q=" + q + ); + + if (pagination.error) { + return res + .status(pagination.status) + .json(createResponse(false, pagination.status, pagination.message)); } - try { - // Count total number of posts matching the search query - const total = await prisma.post.count({ - where: { - status: 'PUBLISHED', - OR: [ - { title: { contains: q, mode: 'insensitive' } }, - { description: { contains: q, mode: 'insensitive' } }, - ] - } - }); - - // Fetch posts matching the search query with pagination and sorting - const posts = await prisma.post.findMany({ - where: { - status: 'PUBLISHED', - OR: [ - { title: { contains: q, mode: 'insensitive' } }, - { description: { contains: q, mode: 'insensitive' } }, - ] - }, - skip: (page - 1) * limit, // Skip the posts based on current page - take: limit, // Limit the number of posts returned - orderBy: { - [sortBy]: order, // Order by the selected field (createdAt, title, description) - }, - select: { - id: true, - title: true, - description: true, - createdAt: true, - image: true, - }, - }); - - if (!posts || posts.length === 0) { - return res.status(404).json(createResponse(false, 404, "No news found")); - } - - // Calculate pagination details - const pagination = paginate(total, posts.length, page, limit, POSTS_URL + "/search?q=" + q); - - if (pagination.error) { - return res.status(pagination.status).json(createResponse(false, pagination.status, pagination.message)); - } - - // Return the results - return res.status(200).json(createResponse( - true, - 200, - "News search results", - { pagination, posts } - )); - } catch (error) { - console.error(error.message); - return res.status(500).json(createResponse(false, 500, "Internal server error")); - } + // Return the results + return res + .status(200) + .json( + createResponse(true, 200, "News search results", { pagination, posts }) + ); + } catch (error) { + console.error(error.message); + return res + .status(500) + .json(createResponse(false, 500, "Internal server error")); + } }; /** @@ -152,33 +171,39 @@ export const searchNews = async (req, res) => { * @returns {void} */ export const getRecommendedNews = async (req, res) => { - const { postId } = req.params; - try { - const post = await prisma.post.findUnique({ where: { id: postId } }); - if (!post) { - return res.status(404).json(createResponse(false, 404, "Post not found")); - } - - const relatedPosts = await prisma.post.findMany({ - where: { - id: { not: postId }, - categoryId: post.categoryId, - status: 'PUBLISHED' - }, - take: 4, - orderBy: { createdAt: 'desc' } - }); - - return res.status(200).json(createResponse( - true, - 200, - "Related posts fetched successfully", - relatedPosts - )); - } catch (error) { - console.error(error.message); - return res.status(500).json(createResponse(false, 500, "Internal server error")); + const { postId } = req.params; + try { + const post = await prisma.post.findUnique({ where: { id: postId } }); + if (!post) { + return res.status(404).json(createResponse(false, 404, "Post not found")); } + + const relatedPosts = await prisma.post.findMany({ + where: { + id: { not: postId }, + categoryId: post.categoryId, + status: "PUBLISHED", + }, + take: 4, + orderBy: { createdAt: "desc" }, + }); + + return res + .status(200) + .json( + createResponse( + true, + 200, + "Related posts fetched successfully", + relatedPosts + ) + ); + } catch (error) { + console.error(error.message); + return res + .status(500) + .json(createResponse(false, 500, "Internal server error")); + } }; /** @@ -188,30 +213,29 @@ export const getRecommendedNews = async (req, res) => { * @returns {void} */ export const getPostBySlug = async (req, res) => { - const { slug } = req.params; - try { - const post = await prisma.post.update({ - where: { slug }, - data: { visitCount: { increment: 1 } }, - include: { - category: true, - } - }); - - if (!post) { - return res.status(404).json(createResponse(false, 404, "Post not found")); - } - - return res.status(200).json(createResponse( - true, - 200, - "Post fetched successfully", - post - )); - } catch (error) { - console.error(error.message); - return res.status(500).json(createResponse(false, 500, "Internal server error")); + const { slug } = req.params; + try { + const post = await prisma.post.update({ + where: { slug }, + data: { visitCount: { increment: 1 } }, + include: { + category: true, + }, + }); + + if (!post) { + return res.status(404).json(createResponse(false, 404, "Post not found")); } + + return res + .status(200) + .json(createResponse(true, 200, "Post fetched successfully", post)); + } catch (error) { + console.error(error.message); + return res + .status(500) + .json(createResponse(false, 500, "Internal server error")); + } }; /** @@ -221,48 +245,58 @@ export const getPostBySlug = async (req, res) => { * @returns {void} */ export const getPopularNews = async (req, res) => { - let { - page = 1, - limit = 6, - sortBy = "visitCount", - order = "desc" - } = req.query; - - page = parseInt(page); - limit = parseInt(limit); - - try { - const total = await prisma.post.count({ - where: { - status: 'PUBLISHED', - } - }); - - const posts = await prisma.post.findMany({ - where: { status: 'PUBLISHED' }, - skip: (page - 1) * limit, - take: limit, - orderBy: { - [sortBy]: order, - }, - }); - - const pagination = paginate(total, posts.length, page, limit, POSTS_URL + "/popular"); - - if (pagination.error) { - return res.status(pagination.status).json(createResponse(false, pagination.status, pagination.message)); - } - - return res.status(200).json(createResponse( - true, - 200, - "Popular posts fetched successfully", - { pagination, posts } - )); - } catch (error) { - console.error(error.message); - return res.status(500).json(createResponse(false, 500, "Internal server error")); + let { + page = 1, + limit = 6, + sortBy = "visitCount", + order = "desc", + } = req.query; + + page = parseInt(page); + limit = parseInt(limit); + + try { + const total = await prisma.post.count({ + where: { + status: "PUBLISHED", + }, + }); + + const posts = await prisma.post.findMany({ + where: { status: "PUBLISHED" }, + skip: (page - 1) * limit, + take: limit, + orderBy: { + [sortBy]: order, + }, + }); + + const pagination = paginate( + total, + posts.length, + page, + limit, + POSTS_URL + "/popular" + ); + + if (pagination.error) { + return res + .status(pagination.status) + .json(createResponse(false, pagination.status, pagination.message)); } + + return res.status(200).json( + createResponse(true, 200, "Popular posts fetched successfully", { + pagination, + posts, + }) + ); + } catch (error) { + console.error(error.message); + return res + .status(500) + .json(createResponse(false, 500, "Internal server error")); + } }; /** Admin routes */ @@ -274,60 +308,55 @@ export const getPopularNews = async (req, res) => { * @returns {void} */ export const getAllPosts = async (req, res) => { - let { - page = 1, - limit = 9, - sortBy = "createdAt", - order = "desc" - } = req.query; - - page = parseInt(page); - limit = parseInt(limit); - - try { - const total = await prisma.post.count(); // Get the total count of posts - const posts = await prisma.post.findMany({ - skip: (page - 1) * limit, - take: limit, - orderBy: { - [sortBy]: order, - }, - select: { - id: true, - title: true, - createdAt: true, - updatedAt: true, - status: true, - category: { - select: { - name: true, - nepaliName: true, - } - } - }, - }); - - // Now call the paginate function to get pagination metadata - const pagination = paginate(total, posts.length, page, limit, ADMIN_URL); - - if (pagination.error) { - return res.status(pagination.status).json(createResponse(false, pagination.status, pagination.message)); - } - - return res.status(200).json(createResponse( - true, - 200, - "All posts fetched successfully", - { pagination, posts } - )); - } catch (error) { - console.error(error.message); - return res.status(500).json(createResponse( - false, - 500, - "Internal server error" - )); + let { page = 1, limit = 9, sortBy = "createdAt", order = "desc" } = req.query; + + page = parseInt(page); + limit = parseInt(limit); + + try { + const total = await prisma.post.count(); // Get the total count of posts + const posts = await prisma.post.findMany({ + skip: (page - 1) * limit, + take: limit, + orderBy: { + [sortBy]: order, + }, + select: { + id: true, + title: true, + createdAt: true, + updatedAt: true, + status: true, + category: { + select: { + name: true, + nepaliName: true, + }, + }, + }, + }); + + // Now call the paginate function to get pagination metadata + const pagination = paginate(total, posts.length, page, limit, ADMIN_URL); + + if (pagination.error) { + return res + .status(pagination.status) + .json(createResponse(false, pagination.status, pagination.message)); } + + return res.status(200).json( + createResponse(true, 200, "All posts fetched successfully", { + pagination, + posts, + }) + ); + } catch (error) { + console.error(error.message); + return res + .status(500) + .json(createResponse(false, 500, "Internal server error")); + } }; /** @@ -337,113 +366,139 @@ export const getAllPosts = async (req, res) => { * @returns {void} */ export const getPostById = async (req, res) => { - const { postId } = req.params; - try { - const post = await prisma.post.findUnique({ - where: { id: postId }, - include: { - category: { - select: { - name: true, - nepaliName: true, - }, - }, - sentiment: { - select: { - name: true, - }, - } - } - }); - if (!post) { - return res.status(404).json(createResponse(false, 404, "Post not found")); - } - - return res.status(200).json(createResponse( - true, - 200, - "Post fetched successfully", - post - )); - } catch (error) { - console.error(error.message); - return res.status(500).json(createResponse(false, 500, "Internal server error")); + const { postId } = req.params; + try { + const post = await prisma.post.findUnique({ + where: { id: postId }, + include: { + category: { + select: { + name: true, + nepaliName: true, + }, + }, + sentiment: { + select: { + name: true, + }, + }, + }, + }); + if (!post) { + return res.status(404).json(createResponse(false, 404, "Post not found")); } + + return res + .status(200) + .json(createResponse(true, 200, "Post fetched successfully", post)); + } catch (error) { + console.error(error.message); + return res + .status(500) + .json(createResponse(false, 500, "Internal server error")); + } }; -// export const createPost = async (req, res) => { -// const { -// title, -// description, -// image, -// status -// } = req.body; -// try { -// // call the algorithm endpoint to get the category label -// const categoryResponse = await fetch(`${NEWS_ALGORITHM_API_URL}/classify`, { -// method: 'GET', -// headers: { -// 'Content-Type': 'application/json', -// }, -// body: JSON.stringify({ text: description }) -// }); - -// // call the algorithm endpoint to get the sentiment -// const sentimentResponse = await fetch(`${NEWS_ALGORITHM_API_URL}/sentiment`, { -// method: 'GET', -// headers: { -// 'Content-Type': 'application/json', -// }, -// body: JSON.stringify({ text: description }) -// }); - -// // call the summary endpoint to get the summary -// const summaryResponse = await fetch(`${NEWS_ALGORITHM_API_URL}/summary`, { -// method: 'GET', -// headers: { -// 'Content-Type': 'application/json', -// }, -// body: JSON.stringify({ text: description }) -// }); - -// const categoryData = await categoryResponse.json(); -// const sentimentData = await sentimentResponse.json(); -// const summaryData = await summaryResponse.json(); - - - -// const post = await prisma.post.create({ -// data: { -// title, -// description, -// image, -// status, -// category: { -// connect: { id: categoryId }, -// }, -// sentiment: { -// connect: { id: sentimentId }, -// }, -// summary: summaryData.summary, -// }, -// }); - -// return res.status(201).json(createResponse( -// true, -// 201, -// "Post created successfully", -// post -// )); -// } catch (error) { -// console.error(error.message); -// return res.status(500).json(createResponse(false, 500, "Internal server error")); -// } -// } +cloudinary.v2.config({ + cloud_name: process.env.CLOUD_NAME, + api_key: process.env.API_KEY, + api_secret: process.env.API_SECRET, + secure: true, +}); -// TODO: Implement the createPost function export const createPost = async (req, res) => { - console.log("createPost"); -} + const { title, description, status } = req.body; + + try { + // Call the algorithm endpoint to get the category label + // const categoryResponse = await fetch(`${NEWS_ALGORITHM_API_URL}/classify`, { + // method: "POST", + // headers: { + // "Content-Type": "application/json", + // }, + // body: JSON.stringify({ text: description }), + // }); + + // const sentimentResponse = await fetch( + // `${NEWS_ALGORITHM_API_URL}/sentiment`, + // { + // method: "POST", // Should be POST if body is included + // headers: { + // "Content-Type": "application/json", + // }, + // body: JSON.stringify({ text: description }), + // } + // ); + + // const summaryResponse = await fetch(`${NEWS_ALGORITHM_API_URL}/summary`, { + // method: "POST", // Should be POST if body is included + // headers: { + // "Content-Type": "application/json", + // }, + // body: JSON.stringify({ text: description }), + // }); + + // if (!categoryResponse.ok) { + // throw new Error("Failed to classify category"); + // } + // if (!sentimentResponse.ok) { + // throw new Error("Failed to analyze sentiment"); + // } + // if (!summaryResponse.ok) { + // throw new Error("Failed to generate summary"); + // } + + // const categoryData = await categoryResponse.json(); + // const sentimentData = await sentimentResponse.json(); + // const summaryData = await summaryResponse.json(); + + // Cloudinary Image Upload + const result = await cloudinary.v2.uploader.upload(req.file.path, { + folder: "news_images", + }); + const imageUrl = result.secure_url; + if (!req.file) { + return res + .status(400) + .json(createResponse(false, 400, "Image is required")); + } + + // const categoryId = categoryData.category; + // const sentimentId = sentimentData.sentiment; + + // Create the post in the database + const post = await prisma.post.create({ + data: { + title, + description, + image: imageUrl, + status, + category: "Health", + // category: { + // connect: { id: categoryId }, + // }, + // sentiment: { + // connect: { id: sentimentId }, + // }, + // summary: summaryData.summary, + }, + }); + + return res + .status(201) + .json(createResponse(true, 201, "Post created successfully", post)); + } catch (error) { + console.error(error.message); + return res + .status(500) + .json(createResponse(false, 500, "Internal server error")); + } +}; + +// // TODO: Implement the createPost function +// export const createPost = async (req, res) => { +// console.log("createPost"); +// } /** * Updates a post by its ID. @@ -452,32 +507,27 @@ export const createPost = async (req, res) => { * @returns {void} */ export const updatePostById = async (req, res) => { - const { postId } = req.params; - const data = req.body; - - try { - const post = await prisma.post.update({ - where: { id: postId }, - data: { - ...data, - }, - }); - - return res.status(200).json(createResponse( - true, - 200, - "Post updated successfully", - post - )); - } catch (error) { - console.error(error.message); - return res.status(500).json(createResponse( - false, - 500, - "Internal server error" - )); - } -} + const { postId } = req.params; + const data = req.body; + + try { + const post = await prisma.post.update({ + where: { id: postId }, + data: { + ...data, + }, + }); + + return res + .status(200) + .json(createResponse(true, 200, "Post updated successfully", post)); + } catch (error) { + console.error(error.message); + return res + .status(500) + .json(createResponse(false, 500, "Internal server error")); + } +}; /** * Deletes a post by its ID. @@ -486,36 +536,29 @@ export const updatePostById = async (req, res) => { * @returns {void} */ export const deletePostById = async (req, res) => { - const { postId } = req.params; - try { - const post = await prisma.post.findUnique({ - where: { id: postId } - }); - - if (!post) { - return res.status(404).json(createResponse( - false, - 404, - "Post not found" - )); - } - - const deletedPost = await prisma.post.delete({ - where: { id: postId } - }); - - return res.status(204).json(createResponse( - true, - 204, - "Post deleted successfully", - deletedPost - )); - } catch (error) { - console.error(error.message); - return res.status(500).json(createResponse( - false, - 500, - "Internal server error" - )); + const { postId } = req.params; + try { + const post = await prisma.post.findUnique({ + where: { id: postId }, + }); + + if (!post) { + return res.status(404).json(createResponse(false, 404, "Post not found")); } -} + + const deletedPost = await prisma.post.delete({ + where: { id: postId }, + }); + + return res + .status(204) + .json( + createResponse(true, 204, "Post deleted successfully", deletedPost) + ); + } catch (error) { + console.error(error.message); + return res + .status(500) + .json(createResponse(false, 500, "Internal server error")); + } +}; diff --git a/api/package.json b/api/package.json index 2fe4ea8..6357d51 100644 --- a/api/package.json +++ b/api/package.json @@ -20,6 +20,7 @@ "@prisma/client": "6.0.1", "bcryptjs": "^2.4.3", "chalk": "^5.4.1", + "cloudinary": "^2.5.1", "compression": "^1.7.5", "cookie-parser": "^1.4.7", "cors": "^2.8.5", @@ -30,6 +31,7 @@ "mailtrap": "^3.4.0", "mongoose": "^8.9.4", "morgan": "^1.10.0", + "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.16" }, "devDependencies": { diff --git a/api/routes/post.route.js b/api/routes/post.route.js index dcce5ec..86e55af 100644 --- a/api/routes/post.route.js +++ b/api/routes/post.route.js @@ -1,24 +1,33 @@ import express from "express"; import { - getAllPosts, - createPost, - getPostById, - updatePostById, - deletePostById, - - getRecentPosts, - searchNews, - getPopularNews, - getPostBySlug, - getRecommendedNews, + getAllPosts, + createPost, + getPostById, + updatePostById, + deletePostById, + getRecentPosts, + searchNews, + getPopularNews, + getPostBySlug, + getRecommendedNews, } from "../controllers/post.controller.js"; import { isAdmin, verifyToken } from "../middleware/verifyToken.js"; - +import multer from "multer"; +// Configure multer for file upload +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + cb(null, "uploads/"); // Specify the folder for uploaded files + }, + filename: (req, file, cb) => { + cb(null, Date.now() + "-" + file.originalname); // Add timestamp to avoid file name conflicts + }, +}); +const upload = multer({ storage }); const router = express.Router(); // Admin routes router.get("/admin", verifyToken, isAdmin, getAllPosts); -router.post("/admin", verifyToken, isAdmin, createPost); +router.post("/admin", verifyToken, isAdmin, upload.single("image"), createPost); router.get("/admin/:postId", verifyToken, isAdmin, getPostById); router.put("/admin/:postId", verifyToken, isAdmin, updatePostById); router.delete("/admin/:postId", verifyToken, isAdmin, deletePostById); @@ -31,4 +40,3 @@ router.get("/:slug", getPostBySlug); // Fetch a single post by slug (updated to router.get("/recommended/:postId", getRecommendedNews); // Fetch related posts by postId export default router; -