From 33deae6b4224b801438eef9eb14500722576b10c Mon Sep 17 00:00:00 2001 From: Jesus Saucedo Date: Mon, 24 Jun 2024 00:44:12 -0700 Subject: [PATCH 1/2] feat: posts crud --- apps/api/src/controllers/category.ts | 137 ++++++++++++----------- apps/api/src/controllers/post.ts | 156 +++++++++++++++++++++++++++ apps/api/src/main.ts | 20 ++-- apps/api/src/models/category.ts | 23 +--- apps/api/src/models/comment.ts | 5 + apps/api/src/models/post.ts | 8 ++ apps/api/src/routes/posts.ts | 21 ++++ 7 files changed, 272 insertions(+), 98 deletions(-) create mode 100644 apps/api/src/controllers/post.ts create mode 100644 apps/api/src/models/comment.ts create mode 100644 apps/api/src/models/post.ts create mode 100644 apps/api/src/routes/posts.ts diff --git a/apps/api/src/controllers/category.ts b/apps/api/src/controllers/category.ts index cf881549..33bbacb3 100644 --- a/apps/api/src/controllers/category.ts +++ b/apps/api/src/controllers/category.ts @@ -1,95 +1,104 @@ -import Category from '../models/category'; +// Initialize categories array to save data in memory +const categories = []; + +export const getCategory = (id: string) => { + return categories.find((p) => p.id === id); +}; // Get all categories -const getCategories = async (req, res) => { - try { - const categories = await Category.find(); - // Return all the categories with a 200 status code - res.status(200).json(categories); - } catch (error) { - const { message } = error; - res.status(500).json({ message }); - } +const getCategories = (req, res) => { + // Return all the categories with a 200 status code + res.status(200).json(categories); }; // Get category by id -const getCategoryById = async (req, res) => { +const getCategoryById = (req, res) => { // Retrieve the id from the route params const { id } = req.params; + // Check if we have a category with that id + const category = getCategory(id); - try { - // Check if we have a category with that id - const category = await Category.findById(id); - - if (!category) { - // If we don't find the category return a 404 status code with a message - return res.status(404).json({ message: 'Category not found' }); - // Note: Remember that json method doesn't interrupt the workflow - // therefore is important to add a "return" to break the process - } - - // Return the category with a 200 status code - res.status(200).json(category); - } catch (error) { - const { message } = error; - res.status(500).json({ message }); + if (!category) { + // If we don't find the category return a 404 status code with a message + return res.status(404).json({ message: 'Category not found' }); + // Note: Remember that json method doesn't interrupt the workflow + // therefore is important to add a "return" to break the process } + + // Return the category with a 200 status code + res.status(200).json(category); }; // Create category -const createCategory = async (req, res) => { - try { - const category = await Category.create(req.body); - // Return the created category with a 201 status code - res.status(201).json(category); - } catch (error) { - const { message } = error; - res.status(500).json({ message }); +const createCategory = (req, res) => { + // Retrieve the name from the request body + const { name } = req.body; + + if (!name) { + // If name is empty or undefined return a 400 status code with a message + return res.status(400).json({ message: 'The name is required.' }); } + + // Generate a new category + const newCategory = { + id: Date.now().toString(), // Convert id to string to match the value in get by id endpoint + name + }; + // Add the new category to our array + categories.push(newCategory); + + // Return the created category with a 201 status code + res.status(201).json(newCategory); }; // Update category -const updateCategory = async (req, res) => { +const updateCategory = (req, res) => { // Retrieve the id from the route params const { id } = req.params; + // Retrieve the index of the category in the array + const categoryIndex = categories.findIndex((p) => p.id === id); - try { - // Check and update if we have a category with that id - const category = await Category.findByIdAndUpdate(id, req.body, { new: true }); - + // "findIndex" will return -1 if there is no match + if (categoryIndex === -1) { // If we don't find the category return a 404 status code with a message - if (!category) { - return res.status(404).json({ message: 'Category not found' }); - } - - // Return the updated category with a 200 status code - res.status(200).json(category); - } catch (error) { - const { message } = error; - res.status(500).json({ message }); + return res.status(404).json({ message: 'Category not found' }); } + + // Generate a copy of our cateogory + const updatedCategory = { ...categories[categoryIndex] }; + // Retrieve the name from the request body + const { name } = req.body; + + // Check if we have a name, if so update the property + if (name) { + updatedCategory.name = name; + } + + // Update the category in our array + categories[categoryIndex] = updatedCategory; + + // Return the updated category with a 200 status code + res.status(200).json(updatedCategory); }; // Delete category -const deleteCategory = async (req, res) => { +const deleteCategory = (req, res) => { // Retrieve the id from the route params const { id } = req.params; + // Retrieve the index of the category in the array + const categoryIndex = categories.findIndex((p) => p.id === id); - try { - // Check and delete if we have a category with that id - const category = await Category.findByIdAndDelete(id); - + // "findIndex" will return -1 if there is no match + if (categoryIndex === -1) { // If we don't find the category return a 404 status code with a message - if (!category) { - return res.status(404).json({ message: 'Category not found' }); - } - - // Return a 200 status code - res.status(200).json(category); - } catch (error) { - const { message } = error; - res.status(500).json({ message }); + return res.status(404).json({ message: 'Category not found' }); } + + // Remove the category from the array + categories.splice(categoryIndex, 1); + + // Return a 204 status code + res.status(204).send(); }; export default { @@ -98,4 +107,4 @@ export default { createCategory, updateCategory, deleteCategory -}; +}; \ No newline at end of file diff --git a/apps/api/src/controllers/post.ts b/apps/api/src/controllers/post.ts new file mode 100644 index 00000000..1d579a03 --- /dev/null +++ b/apps/api/src/controllers/post.ts @@ -0,0 +1,156 @@ +import { getCategory } from './category'; +import { Post } from '../models/post'; +import { Comment } from '../models/comment'; + +const posts: Array = []; +const comments: Array = []; + +const getPostById = (id: string) => { + const post = posts.find((p) => p.id === id); + + if (!post) { + return; + } + + const postCategory = getCategory(post.category); + const postComments = comments.filter((c) => post.comments.includes(c.id)); + + return { + ...post, + category: postCategory, + comments: postComments + }; +}; + +const getPosts = (req, res) => { + res.status(200).json(posts); +}; + +const getPostsByCategory = (req, res) => { + const { category } = req.params; + const postsByCategory = posts.filter((p) => p.category === category); + res.status(200).json(postsByCategory); +}; + +const getPost = (req, res) => { + const { id } = req.params; + const post = getPostById(id); + + if (!post) { + return res.status(404).json({ message: 'Post not found' }); + } + + res.status(200).json(post); +}; + +const createPost = (req, res) => { + const { title, image, description, category } = req.body; + + if (!title) { + return res.status(400).json({ message: 'Title is required' }); + } + + if (!image) { + return res.status(400).json({ message: 'Image is required' }); + } + + if (!description) { + return res.status(400).json({ message: 'Description is required' }); + } + + if (!category) { + return res.status(400).json({ message: 'Category is required' }); + } + + const newPost: Post = { + id: Date.now().toString(), + title, + image, + description, + category, + comments: [] + }; + + posts.push(newPost); + res.status(201).json(newPost); +}; + +const createPostComment = (req, res) => { + const { id } = req.params; + const { author, content } = req.body; + + if (!author) { + return res.status(400).json({ message: 'Author is required' }); + } + + if (!content) { + return res.status(400).json({ message: 'Content is required' }); + } + + const newComment: Comment = { + id: Date.now().toString(), + author, + content + }; + + comments.push(newComment); + const post = posts.find((p) => p.id === id); + post.comments.push(newComment.id); + res.status(201).json(newComment); +}; + +const updatePost = (req, res) => { + const { id } = req.params; + const { title, image, description, category } = req.body; + const postIndex = posts.findIndex((p) => p.id === id); + + if (postIndex === -1) { + return res.status(404).json({ message: 'Post not found' }); + } + + const post = posts[postIndex]; + const newPost = { ...post }; + + if (title && title !== post.title) { + newPost.title = title; + } + + if (image && image !== post.image) { + newPost.image = image; + } + + if (description && description !== post.description) { + newPost.description = description; + } + + if (category && category !== post.category) { + newPost.category = category; + } + + posts[postIndex] = newPost; + const updatedPost = getPostById(id); + res.status(200).json(updatedPost); +}; + +const deletePost = (req, res) => { + const { id } = req.params; + const postIndex = posts.findIndex((p) => p.id === id); + + if (postIndex === -1) { + return res.status(404).json({ message: 'Post not found' }); + } + + const deletedPost = getPostById(id); + posts.splice(postIndex, 1); + res.status(200).json(deletedPost); +}; + +export default { + getPosts, + getPostsByCategory, + getPost, + createPost, + createPostComment, + updatePost, + deletePost +}; \ No newline at end of file diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index daf9dc2e..c2a7e4fa 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,14 +1,14 @@ import cors from 'cors'; import express from 'express'; import helmet from 'helmet'; -import mongoose from 'mongoose'; import { corsOptions } from './config/corsConfig'; import { verifyToken } from './middleware/auth'; import { errorHandler } from './middleware/errorHandler'; import auth from './routes/auth'; import categories from './routes/categories'; - +import posts from './routes/posts'; + const host = process.env.HOST ?? 'localhost'; const port = process.env.PORT ? Number(process.env.PORT) : 3000; @@ -22,18 +22,10 @@ app.use('/api/auth', auth); app.use(verifyToken); app.use('/api/categories', categories); +app.use('/api/posts', posts); app.use(errorHandler); -mongoose - .connect(process.env.MONGO_URL) - .then(() => { - console.log('Connected to MongoDB'); - - app.listen(port, host, () => { - console.log(`[ ready ] http://${host}:${port}`); - }); - }) - .catch((e) => { - console.error(e); - }); +app.listen(port, host, () => { + console.log(`[ ready ] http://${host}:${port}`); +}); \ No newline at end of file diff --git a/apps/api/src/models/category.ts b/apps/api/src/models/category.ts index c69bbc87..466f2e42 100644 --- a/apps/api/src/models/category.ts +++ b/apps/api/src/models/category.ts @@ -1,21 +1,4 @@ -import mongoose, { Document, Schema } from 'mongoose'; - -interface ICategory extends Document { +export type Category = { + id: string; name: string; -} - -export const categorySchema = new Schema( - { - name: { - type: String, - required: [true, 'Property is required'] - } - }, - { - timestamps: true - } -); - -const Category = mongoose.model('Category', categorySchema); - -export default Category; +}; \ No newline at end of file diff --git a/apps/api/src/models/comment.ts b/apps/api/src/models/comment.ts new file mode 100644 index 00000000..506e0d15 --- /dev/null +++ b/apps/api/src/models/comment.ts @@ -0,0 +1,5 @@ +export type Comment = { + id: string; + author: string; + content: string; +}; \ No newline at end of file diff --git a/apps/api/src/models/post.ts b/apps/api/src/models/post.ts new file mode 100644 index 00000000..a5f46c17 --- /dev/null +++ b/apps/api/src/models/post.ts @@ -0,0 +1,8 @@ +export type Post = { + id: string; + title: string; + image: string; + description: string; + category: string; + comments: string[]; +}; \ No newline at end of file diff --git a/apps/api/src/routes/posts.ts b/apps/api/src/routes/posts.ts new file mode 100644 index 00000000..601ba678 --- /dev/null +++ b/apps/api/src/routes/posts.ts @@ -0,0 +1,21 @@ +import express from 'express'; + +import postController from '../controllers/post'; + +const router = express.Router(); + +router.get('/', postController.getPosts); + +router.get('/category/:category', postController.getPostsByCategory); + +router.get('/:id', postController.getPost); + +router.post('/', postController.createPost); + +router.post('/:id/comments', postController.createPostComment); + +router.patch('/:id', postController.updatePost); + +router.delete('/:id', postController.deletePost); + +export default router; \ No newline at end of file From fd8184b0b9165ccb7d8759ee970f52f4492e47d6 Mon Sep 17 00:00:00 2001 From: Jesus Saucedo Date: Mon, 24 Jun 2024 22:07:07 -0700 Subject: [PATCH 2/2] feat: mongoose added with posts crud --- apps/api/.env | 2 +- apps/api/src/controllers/auth.ts | 27 ++-- apps/api/src/controllers/category.ts | 135 +++++++++---------- apps/api/src/controllers/post.ts | 193 ++++++++++----------------- apps/api/src/main.ts | 21 ++- apps/api/src/middleware/auth.ts | 2 +- apps/api/src/models/category.ts | 23 +++- apps/api/src/models/comment.ts | 25 +++- apps/api/src/models/post.ts | 65 +++++++-- apps/api/src/models/user.ts | 26 +++- apps/api/src/routes/categories.ts | 7 +- apps/api/src/routes/posts.ts | 3 +- 12 files changed, 286 insertions(+), 243 deletions(-) diff --git a/apps/api/.env b/apps/api/.env index ecaf5a7c..32b8d970 100644 --- a/apps/api/.env +++ b/apps/api/.env @@ -1,3 +1,3 @@ ACCESS_TOKEN_SECRET=[YOUR_ACCESS_TOKEN_SECRET_HERE] REFRESH_TOKEN_SECRET=[YOUR_REFRESH_TOKEN_SECRET_HERE] -MONGO_URL=[YOUR_MONGO_CONNECTION_STRING_HERE] \ No newline at end of file +MONGO_URL=mongodb+srv://jesussaucedo:1234@cluster0.pbp95rs.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0 \ No newline at end of file diff --git a/apps/api/src/controllers/auth.ts b/apps/api/src/controllers/auth.ts index 96bb74b5..cb8c160c 100644 --- a/apps/api/src/controllers/auth.ts +++ b/apps/api/src/controllers/auth.ts @@ -1,9 +1,7 @@ import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; -import { User } from '../models/user'; - -const users: User[] = []; +import User from '../models/user'; const register = async (req, res) => { const { username, password } = req.body; @@ -15,18 +13,19 @@ const register = async (req, res) => { }); } - // Check that we don't have duplicates - const duplicate = users.find((u) => u.username === username); - if (duplicate) { - return res.status(409).json({ message: 'User already exist' }); - } - try { + // Check that we don't have duplicates + const user = await User.findOne({ username }); + + if (user) { + return res.status(409).json({ message: 'User already exist' }); + } + // Encrypt the password const hashedPassword = await bcrypt.hash(password, 10); // Store new user - users.push({ username, password: hashedPassword }); + await User.create({ username, password: hashedPassword }); res.status(201).json({ message: 'User registered successfully' }); } catch (e) { @@ -45,7 +44,7 @@ const login = async (req, res) => { } // Retrieve user - const user = users.find((u) => u.username === username); + const user = await User.findOne({ username }).exec(); // Check if we found the user and the password matches if (!user || !(await bcrypt.compare(password, user.password))) { @@ -53,7 +52,7 @@ const login = async (req, res) => { } // Generate access token and refresh token - const accessToken = jwt.sign({ username }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' }); + const accessToken = jwt.sign({ username }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '1h' }); const refreshToken = jwt.sign({ username }, process.env.REFRESH_TOKEN_SECRET, { expiresIn: '7d' }); // Save refresh token @@ -79,7 +78,7 @@ const refresh = (req, res) => { return res.status(403).json({ message: 'Forbidden' }); } - const accessToken = jwt.sign({ username }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' }); + const accessToken = jwt.sign({ username }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '1h' }); res.json({ accessToken }); }); }; @@ -94,4 +93,4 @@ export default { login, refresh, logout -}; +}; \ No newline at end of file diff --git a/apps/api/src/controllers/category.ts b/apps/api/src/controllers/category.ts index 33bbacb3..1d64842e 100644 --- a/apps/api/src/controllers/category.ts +++ b/apps/api/src/controllers/category.ts @@ -1,104 +1,95 @@ -// Initialize categories array to save data in memory -const categories = []; - -export const getCategory = (id: string) => { - return categories.find((p) => p.id === id); -}; +import Category from '../models/category'; // Get all categories -const getCategories = (req, res) => { - // Return all the categories with a 200 status code - res.status(200).json(categories); +const getCategories = async (req, res) => { + try { + const categories = await Category.find(); + // Return all the categories with a 200 status code + res.status(200).json(categories); + } catch (error) { + const { message } = error; + res.status(500).json({ message }); + } }; // Get category by id -const getCategoryById = (req, res) => { +const getCategoryById = async (req, res) => { // Retrieve the id from the route params const { id } = req.params; - // Check if we have a category with that id - const category = getCategory(id); - if (!category) { - // If we don't find the category return a 404 status code with a message - return res.status(404).json({ message: 'Category not found' }); - // Note: Remember that json method doesn't interrupt the workflow - // therefore is important to add a "return" to break the process + try { + // Check if we have a category with that id + const category = await Category.findById(id); + + if (!category) { + // If we don't find the category return a 404 status code with a message + return res.status(404).json({ message: 'Category not found' }); + // Note: Remember that json method doesn't interrupt the workflow + // therefore is important to add a "return" to break the process + } + + // Return the category with a 200 status code + res.status(200).json(category); + } catch (error) { + const { message } = error; + res.status(500).json({ message }); } - - // Return the category with a 200 status code - res.status(200).json(category); }; // Create category -const createCategory = (req, res) => { - // Retrieve the name from the request body - const { name } = req.body; - - if (!name) { - // If name is empty or undefined return a 400 status code with a message - return res.status(400).json({ message: 'The name is required.' }); +const createCategory = async (req, res) => { + try { + const category = await Category.create(req.body); + // Return the created category with a 201 status code + res.status(201).json(category); + } catch (error) { + const { message } = error; + res.status(500).json({ message }); } - - // Generate a new category - const newCategory = { - id: Date.now().toString(), // Convert id to string to match the value in get by id endpoint - name - }; - // Add the new category to our array - categories.push(newCategory); - - // Return the created category with a 201 status code - res.status(201).json(newCategory); }; // Update category -const updateCategory = (req, res) => { +const updateCategory = async (req, res) => { // Retrieve the id from the route params const { id } = req.params; - // Retrieve the index of the category in the array - const categoryIndex = categories.findIndex((p) => p.id === id); - // "findIndex" will return -1 if there is no match - if (categoryIndex === -1) { - // If we don't find the category return a 404 status code with a message - return res.status(404).json({ message: 'Category not found' }); - } - - // Generate a copy of our cateogory - const updatedCategory = { ...categories[categoryIndex] }; - // Retrieve the name from the request body - const { name } = req.body; + try { + // Check and update if we have a category with that id + const category = await Category.findByIdAndUpdate(id, req.body, { new: true }); - // Check if we have a name, if so update the property - if (name) { - updatedCategory.name = name; + // If we don't find the category return a 404 status code with a message + if (!category) { + return res.status(404).json({ message: 'Category not found' }); + } + + // Return the updated category with a 200 status code + res.status(200).json(category); + } catch (error) { + const { message } = error; + res.status(500).json({ message }); } - - // Update the category in our array - categories[categoryIndex] = updatedCategory; - - // Return the updated category with a 200 status code - res.status(200).json(updatedCategory); }; // Delete category -const deleteCategory = (req, res) => { +const deleteCategory = async (req, res) => { // Retrieve the id from the route params const { id } = req.params; - // Retrieve the index of the category in the array - const categoryIndex = categories.findIndex((p) => p.id === id); - // "findIndex" will return -1 if there is no match - if (categoryIndex === -1) { + try { + // Check and delete if we have a category with that id + const category = await Category.findByIdAndDelete(id); + // If we don't find the category return a 404 status code with a message - return res.status(404).json({ message: 'Category not found' }); + if (!category) { + return res.status(404).json({ message: 'Category not found' }); + } + + // Return a 200 status code + res.status(200).json(category); + } catch (error) { + const { message } = error; + res.status(500).json({ message }); } - - // Remove the category from the array - categories.splice(categoryIndex, 1); - - // Return a 204 status code - res.status(204).send(); }; export default { diff --git a/apps/api/src/controllers/post.ts b/apps/api/src/controllers/post.ts index 1d579a03..22cc5f89 100644 --- a/apps/api/src/controllers/post.ts +++ b/apps/api/src/controllers/post.ts @@ -1,154 +1,101 @@ -import { getCategory } from './category'; -import { Post } from '../models/post'; -import { Comment } from '../models/comment'; - -const posts: Array = []; -const comments: Array = []; - -const getPostById = (id: string) => { - const post = posts.find((p) => p.id === id); - - if (!post) { - return; +import Post from '../models/post'; +import Comment from '../models/comment'; + +const getPosts = async (req, res) => { + try { + const posts = await Post.find().populate('category').exec(); + res.status(200).json(posts); + } catch (error) { + const { message } = error; + res.status(500).json({ message }); } - - const postCategory = getCategory(post.category); - const postComments = comments.filter((c) => post.comments.includes(c.id)); - - return { - ...post, - category: postCategory, - comments: postComments - }; }; -const getPosts = (req, res) => { - res.status(200).json(posts); -}; - -const getPostsByCategory = (req, res) => { +const getPostsByCategory = async (req, res) => { const { category } = req.params; - const postsByCategory = posts.filter((p) => p.category === category); - res.status(200).json(postsByCategory); + try { + const posts = await Post.find({ category }).populate('category').exec(); + res.status(200).json(posts); + } catch (error) { + const { message } = error; + res.status(500).json({ message }); + } }; -const getPost = (req, res) => { +const getPostById = async (req, res) => { const { id } = req.params; - const post = getPostById(id); - - if (!post) { - return res.status(404).json({ message: 'Post not found' }); + try { + const post = await Post.findById(id).populate('category').populate('comments').exec(); + if (!post) { + return res.status(404).send({ message: 'Post not found' }); + } + res.status(200).json(post); + } catch (error) { + const { message } = error; + res.status(500).json({ message }); } - - res.status(200).json(post); }; -const createPost = (req, res) => { - const { title, image, description, category } = req.body; - - if (!title) { - return res.status(400).json({ message: 'Title is required' }); - } - - if (!image) { - return res.status(400).json({ message: 'Image is required' }); - } - - if (!description) { - return res.status(400).json({ message: 'Description is required' }); +const createPost = async (req, res) => { + try { + const post = await Post.create(req.body); + res.status(201).json(post); + } catch (error) { + const { message } = error; + res.status(500).json({ message }); } - - if (!category) { - return res.status(400).json({ message: 'Category is required' }); - } - - const newPost: Post = { - id: Date.now().toString(), - title, - image, - description, - category, - comments: [] - }; - - posts.push(newPost); - res.status(201).json(newPost); }; -const createPostComment = (req, res) => { +const createPostComment = async (req, res) => { const { id } = req.params; - const { author, content } = req.body; - - if (!author) { - return res.status(400).json({ message: 'Author is required' }); - } - - if (!content) { - return res.status(400).json({ message: 'Content is required' }); + try { + const post = await Post.findById(id); + + if (!post) { + return res.status(404).send({ message: 'Post not found' }); + } + const newComment = await Comment.create(req.body); + post.comments.push(newComment._id); + await post.save(); + res.status(201).json(newComment); + } catch (error) { + const { message } = error; + res.status(500).json({ message }); } - - const newComment: Comment = { - id: Date.now().toString(), - author, - content - }; - - comments.push(newComment); - const post = posts.find((p) => p.id === id); - post.comments.push(newComment.id); - res.status(201).json(newComment); }; -const updatePost = (req, res) => { +const updatePost = async (req, res) => { const { id } = req.params; - const { title, image, description, category } = req.body; - const postIndex = posts.findIndex((p) => p.id === id); - - if (postIndex === -1) { - return res.status(404).json({ message: 'Post not found' }); - } - - const post = posts[postIndex]; - const newPost = { ...post }; - - if (title && title !== post.title) { - newPost.title = title; + try { + const post = await Post.findByIdAndUpdate(id, req.body, { new: true }); + if (!post) { + return res.status(404).send({ message: 'Post not found' }); + } + res.status(200).json(post); + } catch (error) { + const { message } = error; + res.status(500).json({ message }); } - - if (image && image !== post.image) { - newPost.image = image; - } - - if (description && description !== post.description) { - newPost.description = description; - } - - if (category && category !== post.category) { - newPost.category = category; - } - - posts[postIndex] = newPost; - const updatedPost = getPostById(id); - res.status(200).json(updatedPost); }; -const deletePost = (req, res) => { +const deletePost = async (req, res) => { const { id } = req.params; - const postIndex = posts.findIndex((p) => p.id === id); - - if (postIndex === -1) { - return res.status(404).json({ message: 'Post not found' }); + try { + const post = await Post.findByIdAndDelete(id); + if (!post) { + return res.status(404).send({ message: 'Post not found' }); + } + res.status(204).json(post); + } catch (error) { + const { message } = error; + res.status(500).json({ message }); } - - const deletedPost = getPostById(id); - posts.splice(postIndex, 1); - res.status(200).json(deletedPost); }; export default { getPosts, getPostsByCategory, - getPost, + getPostById, createPost, createPostComment, updatePost, diff --git a/apps/api/src/main.ts b/apps/api/src/main.ts index c2a7e4fa..270ce442 100644 --- a/apps/api/src/main.ts +++ b/apps/api/src/main.ts @@ -1,14 +1,16 @@ import cors from 'cors'; import express from 'express'; import helmet from 'helmet'; +import mongoose from 'mongoose'; import { corsOptions } from './config/corsConfig'; import { verifyToken } from './middleware/auth'; import { errorHandler } from './middleware/errorHandler'; + import auth from './routes/auth'; import categories from './routes/categories'; import posts from './routes/posts'; - + const host = process.env.HOST ?? 'localhost'; const port = process.env.PORT ? Number(process.env.PORT) : 3000; @@ -17,15 +19,22 @@ const app = express(); app.use(express.json()); app.use(helmet()); app.use(cors(corsOptions)); - app.use('/api/auth', auth); - app.use(verifyToken); + app.use('/api/categories', categories); app.use('/api/posts', posts); app.use(errorHandler); -app.listen(port, host, () => { - console.log(`[ ready ] http://${host}:${port}`); -}); \ No newline at end of file +mongoose + .connect(process.env.MONGO_URL) + .then(() => { + app.listen(port, host, () => { + console.log(`[ ready ] http://${host}:${port}`); + }); + }) + .catch((e) => { + console.error(e); + }); + \ No newline at end of file diff --git a/apps/api/src/middleware/auth.ts b/apps/api/src/middleware/auth.ts index 193a5a9e..c053fcea 100644 --- a/apps/api/src/middleware/auth.ts +++ b/apps/api/src/middleware/auth.ts @@ -21,4 +21,4 @@ export const verifyToken = (req, res, next) => { export default { verifyToken -}; +}; \ No newline at end of file diff --git a/apps/api/src/models/category.ts b/apps/api/src/models/category.ts index 466f2e42..543221cf 100644 --- a/apps/api/src/models/category.ts +++ b/apps/api/src/models/category.ts @@ -1,4 +1,21 @@ -export type Category = { - id: string; +import mongoose, { Document, Schema } from 'mongoose'; + +interface ICategory extends Document { name: string; -}; \ No newline at end of file +} + +export const categorySchema = new Schema( + { + name: { + type: String, + required: [true, 'Property is required'] + } + }, + { + timestamps: true + } +); + +const Category = mongoose.model('Category', categorySchema); + +export default Category; \ No newline at end of file diff --git a/apps/api/src/models/comment.ts b/apps/api/src/models/comment.ts index 506e0d15..426fd79a 100644 --- a/apps/api/src/models/comment.ts +++ b/apps/api/src/models/comment.ts @@ -1,5 +1,20 @@ -export type Comment = { - id: string; - author: string; - content: string; -}; \ No newline at end of file +import mongoose, { Document, Schema } from 'mongoose'; + +interface IComment extends Document { + author: string; + content: string; +} + +const commentSchema = new Schema( + { + author: String, + content: String + }, + { + timestamps: true + } +); + +const Comment = mongoose.model('Comment', commentSchema); + +export default Comment; \ No newline at end of file diff --git a/apps/api/src/models/post.ts b/apps/api/src/models/post.ts index a5f46c17..1b834bc1 100644 --- a/apps/api/src/models/post.ts +++ b/apps/api/src/models/post.ts @@ -1,8 +1,57 @@ -export type Post = { - id: string; - title: string; - image: string; - description: string; - category: string; - comments: string[]; -}; \ No newline at end of file +import mongoose, { Document, Schema } from 'mongoose'; + +import Comment from './comment'; + +interface IPost extends Document { + title: string; + image: string; + description: string; + category: mongoose.Types.ObjectId; + comments: mongoose.Types.ObjectId[]; +} + +const postSchema = new Schema( + { + title: { + type: String, + required: [true, 'Property is required'] + }, + image: { + type: String, + required: [true, 'Property is required'] + }, + description: { + type: String, + required: [true, 'Property is required'] + }, + category: { + type: Schema.Types.ObjectId, + ref: 'Category', + required: [true, 'Property is required'] + }, + comments: [ + { + type: Schema.Types.ObjectId, + ref: 'Comment' + } + ] + }, + { + timestamps: true + } +); + +postSchema.pre('findOneAndDelete', async function (next) { + try { + const postToDelete = await this.model.findById(this.getFilter()); + await Comment.deleteMany({ _id: { $in: postToDelete.comments } }); + next(); + } catch (error) { + console.log(error, 'ERROR!!!'); + next(error); + } +}); + +const Post = mongoose.model('Post', postSchema); + +export default Post; \ No newline at end of file diff --git a/apps/api/src/models/user.ts b/apps/api/src/models/user.ts index 6ac7896e..958c9e87 100644 --- a/apps/api/src/models/user.ts +++ b/apps/api/src/models/user.ts @@ -1,4 +1,26 @@ -export type User = { +import mongoose, { Document, Schema } from 'mongoose'; + +interface IUser extends Document { username: string; password: string; -}; +} + +const userSchema = new Schema( + { + username: { + type: String, + required: [true, 'Property is required'] + }, + password: { + type: String, + required: [true, 'Property is required'] + } + }, + { + timestamps: true + } +); + +const User = mongoose.model('User', userSchema); + +export default User; \ No newline at end of file diff --git a/apps/api/src/routes/categories.ts b/apps/api/src/routes/categories.ts index 78e120a1..2fdab617 100644 --- a/apps/api/src/routes/categories.ts +++ b/apps/api/src/routes/categories.ts @@ -4,19 +4,14 @@ import categoryController from '../controllers/category'; const router = express.Router(); -// Get all categories router.get('/', categoryController.getCategories); -// Get category by id router.get('/:id', categoryController.getCategoryById); -// Create category router.post('/', categoryController.createCategory); -// Update category router.patch('/:id', categoryController.updateCategory); -// Delete category router.delete('/:id', categoryController.deleteCategory); -export default router; +export default router; \ No newline at end of file diff --git a/apps/api/src/routes/posts.ts b/apps/api/src/routes/posts.ts index 601ba678..a91098bd 100644 --- a/apps/api/src/routes/posts.ts +++ b/apps/api/src/routes/posts.ts @@ -1,5 +1,4 @@ import express from 'express'; - import postController from '../controllers/post'; const router = express.Router(); @@ -8,7 +7,7 @@ router.get('/', postController.getPosts); router.get('/category/:category', postController.getPostsByCategory); -router.get('/:id', postController.getPost); +router.get('/:id', postController.getPostById); router.post('/', postController.createPost);