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
15 changes: 15 additions & 0 deletions apps/api/src/config/corsConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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 };
97 changes: 97 additions & 0 deletions apps/api/src/controllers/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';

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

const users: User[] = [];

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

// Check that we have the correct payload
if (!username || !password) {
return res.status(400).json({
message: 'Username and password are required'
});
}

// 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 {
// Encrypt the password
const hashedPassword = await bcrypt.hash(password, 10);

// Store new user
users.push({ username, password: hashedPassword });

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

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

// Check that we have the correct payload
if (!username || !password) {
return res.status(400).json({
message: 'Username and password are required'
});
}

// Retrieve user
const user = users.find((u) => u.username === username);

// Check if we found the user and the password matches
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ message: 'Invalid credentials' });
}

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

// Save refresh token
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days in milliseconds
});

res.json({ accessToken });
};

const refresh = (req, res) => {
// 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 }) => {
if (err) {
// Invalid token
return res.status(403).json({ message: 'Forbidden' });
}

const accessToken = jwt.sign({ username }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' });
res.json({ accessToken });
});
};

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

export default {
register,
login,
refresh,
logout
};
83 changes: 83 additions & 0 deletions apps/api/src/controllers/category.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Category from '../models/category';

// Get all categories
const getCategories = async (req, res) => {
try {
const categories = await Category.find();
res.status(200).json(categories);
} catch (error) {
const { message } = error;
res.status(500).json({ message });
}
};

// Get category by id
const getCategoryById = async (req, res) => {
const { id } = req.params;

try {
const category = await Category.findById(id);

if (!category) {
return res.status(404).json({ message: 'Category not found' });
}
res.status(200).json(category);
} catch (error) {
const { message } = error;
res.status(500).json({ message });
}
};

// Create category
const createCategory = async (req, res) => {
try {
const category = await Category.create(req.body);
res.status(201).json(category);
} catch (error) {
const { message } = error;
res.status(500).json({ message });
}
};

// Update category
const updateCategory = async (req, res) => {
const { id } = req.params;

try {
const category = await Category.findByIdAndUpdate(id, req.body, { new: true });
if (!category) {
return res.status(404).json({ message: 'Category not found' });
}

res.status(200).json(category);
} catch (error) {
const { message } = error;
res.status(500).json({ message });
}
};

// Delete category
const deleteCategory = async (req, res) => {
const { id } = req.params;

try {
const category = await Category.findByIdAndDelete(id);

if (!category) {
return res.status(404).json({ message: 'Category not found' });
}

res.status(200).json(category);
} catch (error) {
const { message } = error;
res.status(500).json({ message });
}
};

export default {
getCategories,
getCategoryById,
createCategory,
updateCategory,
deleteCategory
};
145 changes: 145 additions & 0 deletions apps/api/src/controllers/post.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import Comment from '../models/comment';
import Post from '../models/post';


export const getPost = (id: string) => {
return posts.find((p) => p.id === id);
};

const posts = [];

const getPosts = async (req, res) => {
try {
const posts = await Post.find();
res.status(200).json(posts);
} catch (error) {
const { message } = error;
res.status(500).json({ message });
}
};

// GET /posts/category/:category
const getPostsByCategory = async (req, res) => {
try {
const { category } = req.params;
const post = await Post.find({category});
if (!post) {
return res.status(404).json({ message: 'Post not found' });
}
res.status(200).json(post);
} catch (error) {
const { message } = error;
res.status(500).json({ message });
}
};

// GET /posts/:id
const getPostById = async (req, res) => {
try {
const { id } = req.params;
const post = await Post.findById(id);

if (!post) {
return res.status(404).json({ message: 'Post not found' });
}
res.status(200).json(post);
} catch (error) {
const { message } = error;
res.status(500).json({ message });
}
};

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

if (!title || !image || !description || !category) {
return res.status(400).json({ message: 'Review some fields are missing.' });
}
const newPost = {
title,
image,
description,
category,
comments
};
const post = await Post.create(newPost);
res.status(201).json(post);
} catch (error) {
const { message } = error;
res.status(500).json({ message });
}
};

// POST /posts/:id/comments
const createCommentForPost = async (req, res) => {
try {
const { id } = req.params;
const { author, content} = req.body;

const post = await Post.findById(id);
if (!post) {
return res.status(404).json({ message: 'Post not found' });
}

if (!author || !content) {
return res.status(400).json({ message: 'Review some fields are missing.' });
}

const newComment = {
author,
content
};
const comment = await Comment.create(newComment);
post.comments.push(comment._id.toString());
await post.save();

res.status(201).json(comment);

} catch (error) {
const { message } = error;
res.status(500).json({ message });
}
};


// PATCH /posts/:id
const updatePost = async (req, res) => {
try {
const { id } = req.params;
const post = await Post.findByIdAndUpdate(id, req.body, { new: true });
if (!post) {
return res.status(404).json({ message: 'Post not found' });
}
res.status(200).json(post);
} catch (error) {
const { message } = error;
res.status(500).json({ message });
}
};

// DELETE /posts/:id
const deletePost = async (req, res) => {
try {
const { id } = req.params;
const post = await Post.findByIdAndDelete(id, {});
if (!post) {
return res.status(404).json({ message: 'Post not found' });
}
await Comment.deleteMany({ _id: { $in: post.comments } });
res.status(200).json(post);
} catch (error) {
const { message } = error;
res.status(500).json({ message });
}
};

export default {
getPosts,
getPostsByCategory,
getPostById,
createPost,
createCommentForPost,
updatePost,
deletePost
};
39 changes: 33 additions & 6 deletions apps/api/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,41 @@
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;

const app = express();

app.get('/', (req, res) => {
res.send({ message: 'Hello MFEE!' });
});
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);

mongoose
.connect(process.env.MONGO_URL)
.then(() => {
console.log('Connected to MongoDB');

app.listen(port, host, () => {
console.log(`[ ready ] http://${host}:${port}`);
});
app.listen(port, host, () => {
console.log(`[ ready ] http://${host}:${port}`);
});
})
.catch((e) => {
console.error(e);
});
Loading