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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ Thumbs.db

.nx/cache
.angular

.env.local
77 changes: 41 additions & 36 deletions apps/api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
2. Make sure to fork all the branches (You need to unselect the checkbox of fork only main branch)
3. You can clone the repository to your local or you can create a codespace in github (We suggest to use your local because we will use postman to test it)
4. Checkout to branch `node/template` this is the starter boilerplate
- `git checkout node/template`
- `git checkout node/template`
5. From here you can create your own branch (We suggest to name it `node/<your_eid_here>`)
- `git checkout -b node/<your_eid_here>`
- `git checkout -b node/<your_eid_here>`
6. We will be working with some examples during the sessions in this same repository, once we finish with the session you can get the example from the branch `node/session-*` and merge it to your branch (We will provide the branch after each session)
- `git merge node/session-*`
- `git merge node/session-*`
7. Each session branch will have the challenges to accomplish and the expected results. You can validate if your endpoint is correct by [running the postman collection](#run-postman-collection)
8. After finish the challenges you need to create a pull request to the base repository, you will have a branch with your EID (`node/<your_eid_here>`). If you don't know how to do it you can check this [quick guide](#create-pull-request)

Expand All @@ -24,27 +24,30 @@
### 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*

- `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*

- 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
- id: string
- author: string
- content: string

### Session 02

Expand All @@ -64,9 +67,9 @@
- Connect to MongoDB database using mongoose
- Create models for Post and Comment
- Refactor the controller to retrieve information from database
- *Tip: Use `populate` method to get data from reference id*
- _Tip: Use `populate` method to get data from reference id_
- **Extra**
- Remove post comments from database when you delete the post
- Remove post comments from database when you delete the post

## How to

Expand All @@ -75,26 +78,28 @@
1. Download postman collection from `apps/api/src/assets/mfee-node.postman_collection.json`
2. Import collection to postman
3. Configure url variable if needed. We set the default value to `http://localhost:3000` if you change the port you will need to update this, after updating the value you need to save it with `Ctrl + S`
![Postman - Variables](assets/postman-variables.png)
![Postman - Variables](assets/postman-variables.png)
4. Once everything is in place you go to the folder you want to run and click "Run"
![Postman - Open tests](assets/postman-open-tests.png)
![Postman - Open tests](assets/postman-open-tests.png)
5. The endpoints will be displayed and you just need to click on "Run MFEE - Node.js"
![Postman - Run tests](assets/postman-run-tests.png)
![Postman - Run tests](assets/postman-run-tests.png)
6. After this a report will be show and all the test should pass
![Postman - Test results](assets/postman-test-results.png)
![Postman - Test results](assets/postman-test-results.png)

### Create pull request

1. Once you have your commit in place and you push your branch to your forked repository go to `Pull requests` option and then `Create pull request`
![GitHub - Create pull request](assets/github-create-pull-request.png)
![GitHub - Create pull request](assets/github-create-pull-request.png)
2. Click under `compare across forks` to be able to select the main repository and make sure to select the following
- Base repository: `gus-code/mfee-project`
- Base: `node/<your-eid-here>`
- Head repository: `<your-github-user>/mfee-project`
- Compare: `<your-branch-name>`

![GitHub - Pull request branches](assets/github-pull-request-branches.png)

- Base repository: `gus-code/mfee-project`
- Base: `node/<your-eid-here>`
- Head repository: `<your-github-user>/mfee-project`
- Compare: `<your-branch-name>`

![GitHub - Pull request branches](assets/github-pull-request-branches.png)

3. Check that the files you worked on are in place and then click `Create pull request`
4. Add the title with the following format `feat(session-*): <small-title-of-changes>` where * is the number of the session (01, 02, etc.). After that click again on `Create pull request`
![GitHub - Add title](assets/github-add-title.png)
5. After this we will review the PR, give feedback and merge it to your branch
4. Add the title with the following format `feat(session-*): <small-title-of-changes>` where \* is the number of the session (01, 02, etc.). After that click again on `Create pull request`
![GitHub - Add title](assets/github-add-title.png)
5. After this we will review the PR, give feedback and merge it to your branch
106 changes: 106 additions & 0 deletions apps/api/src/api.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// //////////////////// //
// CATEGORIES //
// //////////////////// //

### GET CATEGORIES
GET http://localhost:3000/api/categories
Authorization: Bearer [ACCESS_TOKEN]

### GET NOT VALID ROUTE
GET http://localhost:3000/api/not-valid-route

### GET CATEGORIES BY ID
PATCH http://localhost:3000/api/categories/cat_1
Authorization: Bearer [ACCESS_TOKEN]
Content-Type: application/json

{
"name": "Other"
}

// //////////////////// //
// POSTS //
// //////////////////// //

### 1. GET ALL POSTS
GET http://localhost:3000/api/posts
Authorization: Bearer [ACCESS_TOKEN]

### 2. GET POSTS BY CATEGORY
GET http://localhost:3000/api/posts/category/cat_1
Authorization: Bearer [ACCESS_TOKEN]

### 3. GET POST BY ID
GET http://localhost:3000/api/posts/a47f5337-16f2-49a0-bc14-e97bb24b56a9
Authorization: Bearer [ACCESS_TOKEN]

### 4. CREATE POST
POST http://localhost:3000/api/posts
Authorization: Bearer [ACCESS_TOKEN]
Content-Type: application/json

{
"title": "Post Test Postman",
"image": "https://images.unsplash.com/photo-1556276797-5086e6b45ff9?crop=entropy&cs=tinysrgb&fit=crop&fm=jpg&h=600&ixid=eyJhcHBfaWQiOjF9&ixlib=rb-1.2.1&q=80&w=800",
"description": "Description from Postman",
"category": "1"
}

### 5. CREATE POST COMMENT
POST http://localhost:3000/api/posts/a47f5337-16f2-49a0-bc14-e97bb24b56a9/comments
Authorization: Bearer [ACCESS_TOKEN]
Content-Type: application/json

{
"author": "MFEE",
"content": "Good content"
}

### 6. UPDATE POST
PATCH http://localhost:3000/api/posts/a47f5337-16f2-49a0-bc14-e97bb24b56a9
Authorization: Bearer [ACCESS_TOKEN]
Content-Type: application/json

{
"title": "Test Postman NEW",
"description": "Description NEW"
}

### 7. DELETE POST
DELETE http://localhost:3000/api/posts/a47f5337-16f2-49a0-bc14-e97bb24b56a9
Authorization: Bearer [ACCESS_TOKEN]

// //////////////////// //
// AUTH //
// //////////////////// //

### REGISTER
POST http://localhost:3000/api/auth/register
Content-Type: application/json

{
"username": "mfee-test",
"password": "Aa$123"
}

### LOGIN
POST http://localhost:3000/api/auth/login
Content-Type: application/json

{
"username": "mfee-test",
"password": "Aa$123"
}

### REFRESH
POST http://localhost:3000/api/auth/refresh
Content-Type: application/json

{
"username": "mfee-test",
"password": "Aa$123"
}

### LOGOUT
POST http://localhost:3000/api/auth/logout
Content-Type: application/json
4 changes: 2 additions & 2 deletions apps/api/src/config/corsConfig.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const allowedOrigins = ['http://localhost:4200', 'http://localhost:3000'];

export const corsOptions = {
origin: (origin, callback) => {
origin: (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => {
// Note: origin will be undefined from same route in local development
if (allowedOrigins.indexOf(origin) !== -1 || !origin) {
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
Expand Down
24 changes: 13 additions & 11 deletions apps/api/src/controllers/auth.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import jwt, { Secret } from 'jsonwebtoken';
import { Request, Response } from 'express';

import { User } from '../models/user';

const users: User[] = [];

const register = async (req, res) => {
const register = async (req: Request, res: Response) => {
const { username, password } = req.body;

// Check that we have the correct payload
Expand All @@ -29,12 +30,12 @@ const register = async (req, res) => {
users.push({ username, password: hashedPassword });

res.status(201).json({ message: 'User registered successfully' });
} catch (e) {
res.status(500).json({ message: e.message });
} catch (error) {
res.status(500).json({ message: (error as Error).message });
}
};

const login = async (req, res) => {
const login = async (req: Request, res: Response) => {
const { username, password } = req.body;

// Check that we have the correct payload
Expand All @@ -53,8 +54,8 @@ const login = async (req, res) => {
}

// Generate access token and refresh token
const accessToken = jwt.sign({ username }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' });
const refreshToken = jwt.sign({ username }, process.env.REFRESH_TOKEN_SECRET, { expiresIn: '7d' });
const accessToken = jwt.sign({ username }, process.env.ACCESS_TOKEN_SECRET as Secret, { expiresIn: '15m' });
const refreshToken = jwt.sign({ username }, process.env.REFRESH_TOKEN_SECRET as Secret, { expiresIn: '7d' });

// Save refresh token
res.cookie('refreshToken', refreshToken, {
Expand All @@ -65,26 +66,27 @@ const login = async (req, res) => {
res.json({ accessToken });
};

const refresh = (req, res) => {
const refresh = (req: Request, res: Response) => {
// Get refresh token from cookies
const refreshToken = req.cookies.refreshToken;

if (!refreshToken) {
return res.status(401).json({ message: 'Unauthorized' });
}

jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, { username }) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET as Secret, (err: any, { username }: any) => {
if (err) {
// Invalid token
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 as Secret, { expiresIn: '15m' });
res.json({ accessToken });
});
};

const logout = (req, res) => {
const logout = (req: Request, res: Response) => {
res.clearCookie('refreshToken');
res.json({ message: 'Logged out successfully' });
};
Expand Down
Loading