Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/api/.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
ENV=dev
TRUSTED_ORIGINS=[]
ACCESS_TOKEN_SECRET=[YOUR_ACCESS_TOKEN_SECRET_HERE]
REFRESH_TOKEN_SECRET=[YOUR_REFRESH_TOKEN_SECRET_HERE]
MONGO_URL=[YOUR_MONGO_CONNECTION_STRING_HERE]
MONGO_URL=[YOUR_MONGO_CONNECTION_STRING_HERE]
29 changes: 1 addition & 28 deletions apps/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,7 @@

## Challenges

### Session 01

- Create `route` for `posts` endpoint with the following methods:
- `GET /posts` Return an array of all the posts with status code 200
- `GET /posts/category/:category` Return an array of all the posts by category with status code 200
- `GET /posts/:id` Return a post by id with category object and each comment object in the array with status code 200
- `POST /posts` Create a new post and return the created post with status code 201
- `POST /posts/:id/comments` Create a comment inside the post and return the comment with status code 201
- `PATCH /posts/:id` Update post information and return the updated post with status code 200
- `DELETE /posts/:id` Delete the post and return the deleted post with status code 200 or 204 if you decide to not return anything
* *Add 404 validation where needed*

- Post model
- id: string
- title: string
- image: string
- description: string
- category: string *Id of the category*
- comments: array *Array of comment ids*

- Comment model
- id: string
- author: string
- content: string

### Session 02

- Refactor the code from last session to add a post controller
### Session *

## How to

Expand Down
Empty file added apps/api/src/config/.gitkeep
Empty file.
25 changes: 10 additions & 15 deletions apps/api/src/config/corsConfig.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
const allowedOrigins = ['http://localhost:4200', 'http://localhost:3000'];

export const corsOptions = {
origin: (origin, callback) => {
// Note: origin will be undefined from same route in local development
if (allowedOrigins.indexOf(origin) !== -1 || !origin) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
optionsSuccessStatus: 200
};

export default { corsOptions };
export const corsOptions = {
origin: (origin, callback) => {
if (process.env.ENV === "dev" || process.env.TRUSTED_ORIGINS.includes(origin)) {
return callback(null, true)
}

callback(new Error("Not Allowed By Cors"))
},
OptionsSuccessStatus: 200
}
Empty file.
179 changes: 69 additions & 110 deletions apps/api/src/controllers/category.ts
Original file line number Diff line number Diff line change
@@ -1,110 +1,69 @@
// 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 = (req, res) => {
// Return all the categories with a 200 status code
res.status(200).json(categories);
};

// Get category by id
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);

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 = (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 = (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;

// 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 = (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' });
}

// Remove the category from the array
categories.splice(categoryIndex, 1);

// Return a 204 status code
res.status(204).send();
};

export default {
getCategories,
getCategoryById,
createCategory,
updateCategory,
deleteCategory
};
import type { Category } from "../models/types"

export const categories: Category[] = []

const findCategory = (id: string): Category | void => {
return categories.find(el => el.id === id)
}

const getCategory = (req, res): void => {
const { id } = req.params;
const currentCategory = findCategory(id)

if (!currentCategory) return res.status(404).json({message: "Category not found"})

res.status(200).json(currentCategory);
}

const updateCategory = (req, res) => {
const { id } = req.params;
const categoryIndex = categories.findIndex(el => el.id === id)
if (categoryIndex < 0) return res.status(404).json({message: "Category not found"})

const { name } = req.body;
if (!name) return res.status(401).json({ message: "Name is required!" })

categories[categoryIndex].name = name;

res.status(203).json(categories[categoryIndex]);
}

const deleteCategory = (req, res) => {
const { id } = req.params;
const categoryIndex = categories.findIndex(el => el.id === id)
if (categoryIndex < 0) return res.status(404).json({message: "Category not found"})

const currentCategory = categories[categoryIndex]

categories.splice(categoryIndex, 1)

res.status(200).json({status: "Item Deleted", item: currentCategory});
}


const getAllCategories = (req, res) => {
res.status(200).json(categories);
}

const createCategory = (req, res) => {
const { name } = req.body;

if (!name) return res.status(401).json({ message: "Name is required!" })

const newCategory: Category = {
id: Date.now().toString(),
name
}

categories.push(newCategory)

res.status(201).json(newCategory)
}

export default {
getCategory,
updateCategory,
deleteCategory,
getAllCategories,
createCategory,
}
143 changes: 143 additions & 0 deletions apps/api/src/controllers/post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import type { Post, Comment, PostResponse } from "../models/types"
import { categories } from "../controllers/category"

const posts: Post[] = []
const comments: Comment[] = []

const buildResponsePost = (post: Post): PostResponse => {
return {
...post,
category: categories.filter(cat => post.category.includes(cat.id)),
comments: comments.filter(com => post.category.includes(com.id))
} as PostResponse
}

const findPost = (id: string): Post | void => {
return posts.find(el => el.id === id)
}

const getPost = (req, res): void => {
const { id } = req.params;
const currentPost = findPost(id)

if (!currentPost) return res.status(404).json({message: "Post not found"})

res.status(200).json(buildResponsePost(currentPost));
}

const updatePost = (req, res) => {
const { id } = req.params;
const postIndex = posts.findIndex(el => el.id === id)
if (postIndex < 0) return res.status(404).json({message: "Post not found"})

const post = req.body;
if (!post) return res.status(401).json({ message: "Name is required!" })

Object.keys(post).forEach(key => {
if (!posts[postIndex][key]) return
posts[postIndex][key] = post[key]
})

res.status(203).json(buildResponsePost(posts[postIndex]));
}

const deletePost = (req, res) => {
const { id } = req.params;
const postIndex = categories.findIndex(el => el.id === id)
if (postIndex < 0) return res.status(404).json({message: "Post not found"})

const currentPost = buildResponsePost(posts[postIndex])

posts.splice(postIndex, 1)

res.status(200).json({status: "Item Deleted", item: currentPost});
}

const createComment = (req, res) => {
const { id } = req.params;
const currentPost = findPost(id)
if (!currentPost) return res.status(404).json({message: "Post not found"})

const {comment} = req.body;
if (!comment.author || !comment.content) return res.status(401).json({ message: "Invalid Comment!" })

const commentId = Date.now().toString()
comments.push({
id,
author: comment.author,
content: comment.content,
})

currentPost.comments.push(commentId)

res.status(203).json(buildResponsePost(currentPost));
}


const getAllPosts = (req, res) => {
res.status(200).json(posts.map(post => buildResponsePost(post)));
}

const createPost = (req, res) => {
const {
title,
image,
description,
category,
comments,
} = req.body;

if (
!title
|| !image
|| !description
|| !category
) return res.status(401).json({ message: "Invalid Post!" })

const newComments: string[] = []

comments.forEach(({author, content}) => {
if (!author || !content) res.status(401).json({ message: "Invalid Comment!" })

const id = Date.now().toString()

newComments.push(id)

comments.push({
id,
author,
content,
})
});

const newPost: Post = {
...req.body,
id: Date.now().toString(),
comments: newComments,
}

posts.push(newPost)

res.status(201).json(buildResponsePost(newPost))
}

const getAllPostsByCategory = (req, res): void => {
const { category } = req.params;
let categoryPosts = posts.filter(post => post.category.includes(category))

if (!categoryPosts.length) return res.status(404).json({message: "Category has no posts"})

categoryPosts = categoryPosts.map(post => buildResponsePost(post))

res.status(200).json(categoryPosts);
}

export default {
getPost,
updatePost,
deletePost,
createComment,
getAllPosts,
createPost,
getAllPostsByCategory
}
Loading