├── .gitignore ├── .sequelizerc ├── Dockerfile ├── app.js ├── config └── config.js ├── controllers ├── comment.controller.js ├── image.controller.js ├── post.controller.js ├── test.contoller.js └── user.controller.js ├── docker-compose.yml ├── helpers └── image-uploader.js ├── middleware └── check-auth.js ├── migrations ├── 20200610200035-create-post.js ├── 20200610201007-create-user.js ├── 20200610201055-create-category.js └── 20200610201156-create-comment.js ├── models ├── address.js ├── category.js ├── comment.js ├── index.js ├── post.js ├── postcategory.js ├── role.js └── user.js ├── nodemon.json ├── package-lock.json ├── package.json ├── routes ├── comments.js ├── images.js ├── posts.js ├── tests.js └── user.js ├── seeders └── 20201109195900-category-seeder.js ├── server.js └── uploads ├── 1604177873182.png ├── 1604177895511.jpg ├── 1604212798125.jpg └── 1604212807246.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .idea/ 3 | package-lock.json 4 | .env 5 | -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | 'config': path.resolve('config', 'config.js'), 5 | }; -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:alpine 2 | WORKDIR /usr/src/app 3 | COPY ./package.json ./ 4 | COPY ./package-lock.json ./ 5 | RUN npm install 6 | RUN npm i -g sequelize-cli 7 | COPY ./config ./config 8 | COPY ./controllers ./controllers 9 | COPY ./helpers ./helpers 10 | COPY ./middleware ./middleware 11 | COPY ./migrations ./migrations 12 | COPY ./models ./models 13 | COPY ./routes ./routes 14 | COPY ./seeders ./seeders 15 | COPY ./uploads ./uploads 16 | COPY ./.env ./ 17 | COPY ./.sequelizerc ./.sequelizerc 18 | COPY ./app.js ./app.js 19 | COPY ./server.js ./server.js 20 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const bodyParser = require('body-parser'); 3 | 4 | const postsRoute = require('./routes/posts'); 5 | const userRoute = require('./routes/user'); 6 | const commentsRoute = require('./routes/comments'); 7 | const imageRoute = require('./routes/images'); 8 | const testRoutes = require('./routes/tests'); 9 | 10 | const app = express(); 11 | 12 | app.use(bodyParser.json()); 13 | app.use('/uploads', express.static('uploads')); 14 | 15 | app.use("/posts", postsRoute); 16 | app.use("/user", userRoute); 17 | app.use("/comments", commentsRoute); 18 | app.use("/images", imageRoute); 19 | app.use("/test", testRoutes); 20 | 21 | module.exports = app; 22 | -------------------------------------------------------------------------------- /config/config.js: -------------------------------------------------------------------------------- 1 | const dotenv = (require("dotenv").config()).parsed 2 | 3 | let config = { 4 | "username": process.env['DB_USERNAME'], 5 | "password": process.env['DB_PASSWORD'], 6 | "database": process.env['DB_NAME'], 7 | "host": process.env['DB_HOST'], 8 | "dialect": "mysql" 9 | } 10 | 11 | module.exports = { 12 | development: config, 13 | test: config, 14 | production: config 15 | } -------------------------------------------------------------------------------- /controllers/comment.controller.js: -------------------------------------------------------------------------------- 1 | const Validator = require('fastest-validator'); 2 | const models = require('../models') 3 | 4 | function save(req, res) { 5 | const comment = { 6 | content: req.body.content, 7 | postId: req.body.post_id, 8 | userId: 1 9 | } 10 | 11 | const schema = { 12 | content: {type: "string", optional: false, max: "500"}, 13 | postId: {type: "number", optional: false} 14 | } 15 | 16 | const v = new Validator(); 17 | const validationResponse = v.validate(comment, schema); 18 | 19 | if(validationResponse !== true){ 20 | return res.status(400).json({ 21 | message: "Validation failed", 22 | errors: validationResponse 23 | }); 24 | } 25 | 26 | models.Post.findByPk(req.body.post_id).then(post => { 27 | if(post === null){ 28 | res.status(404).json({ 29 | message: "Post not found" 30 | }); 31 | }else{ 32 | models.Comment.create(comment).then(result => { 33 | res.status(201).json({ 34 | message: "Comment created successfully", 35 | comment: result 36 | }); 37 | }).catch(error => { 38 | res.status(500).json({ 39 | message: "Something went wrong", 40 | error: error 41 | }); 42 | }); 43 | } 44 | 45 | }).catch(err => { 46 | res.status(500).json({ 47 | message: "Something went wrong", 48 | error: err 49 | }); 50 | }); 51 | } 52 | 53 | 54 | function show(req, res){ 55 | const id = req.params.id; 56 | 57 | models.Comment.findByPk(id).then(result => { 58 | if(result){ 59 | res.status(200).json(result); 60 | }else{ 61 | res.status(404).json({ 62 | message: "Comment not found!" 63 | }) 64 | } 65 | }).catch(error => { 66 | res.status(500).json({ 67 | message: "Something went wrong!" 68 | }) 69 | }); 70 | } 71 | 72 | 73 | function index(req, res){ 74 | models.Comment.findAll().then(result => { 75 | res.status(200).json(result); 76 | }).catch(error => { 77 | res.status(500).json({ 78 | message: "Something went wrong!" 79 | }); 80 | }); 81 | } 82 | 83 | 84 | function update(req, res){ 85 | const id = req.params.id; 86 | const updatedComment = { 87 | content: req.body.content 88 | } 89 | 90 | const userId = 1; 91 | 92 | const schema = { 93 | content: {type: "string", optional: false, max: "500"}, 94 | } 95 | 96 | const v = new Validator(); 97 | const validationResponse = v.validate(updatedComment, schema); 98 | 99 | if(validationResponse !== true){ 100 | return res.status(400).json({ 101 | message: "Validation failed", 102 | errors: validationResponse 103 | }); 104 | } 105 | 106 | models.Comment.update(updatedComment, {where: {id:id, userId: userId}}).then(result => { 107 | res.status(200).json({ 108 | message: "Comment updated successfully", 109 | post: updatedComment 110 | }); 111 | }).catch(error => { 112 | res.status(200).json({ 113 | message: "Something went wrong", 114 | error: error 115 | }); 116 | }) 117 | } 118 | 119 | 120 | function destroy(req, res){ 121 | const id = req.params.id; 122 | const userId = 1; 123 | 124 | models.Comment.destroy({where:{id:id, userId:userId}}).then(result => { 125 | res.status(200).json({ 126 | message: "Comment deleted successfully" 127 | }); 128 | }).catch(error => { 129 | res.status(200).json({ 130 | message: "Something went wrong", 131 | error: error 132 | }); 133 | }); 134 | } 135 | 136 | module.exports = { 137 | save: save, 138 | show: show, 139 | index: index, 140 | update: update, 141 | destroy: destroy 142 | } 143 | -------------------------------------------------------------------------------- /controllers/image.controller.js: -------------------------------------------------------------------------------- 1 | function upload(req, res){ 2 | if(req.file.filename){ 3 | res.status(201).json({ 4 | mesaage: "Image upload successfully", 5 | url: req.file.filename 6 | }); 7 | }else{ 8 | res.status(500).json({ 9 | mesaage: "Something went wrong!" 10 | }); 11 | } 12 | } 13 | 14 | module.exports = { 15 | upload: upload 16 | } -------------------------------------------------------------------------------- /controllers/post.controller.js: -------------------------------------------------------------------------------- 1 | const Validator = require('fastest-validator'); 2 | const models = require('../models'); 3 | 4 | function save(req, res){ 5 | const post = { 6 | title: req.body.title, 7 | content: req.body.content, 8 | imageUrl: req.body.image_url, 9 | categoryId: req.body.category_id, 10 | userId: req.userData.user 11 | } 12 | 13 | const schema = { 14 | title: {type:"string", optional: false, max: "100"}, 15 | content: {type: "string", optional: false, max: "500"}, 16 | categoryId: {type: "number", optional: false} 17 | } 18 | 19 | const v = new Validator(); 20 | const validationResponse = v.validate(post, schema); 21 | 22 | if(validationResponse !== true){ 23 | return res.status(400).json({ 24 | message: "Validation failed", 25 | errors: validationResponse 26 | }); 27 | } 28 | 29 | models.Category.findByPk(req.body.category_id).then(result => { 30 | if(result !== null){ 31 | models.Post.create(post).then(result => { 32 | res.status(201).json({ 33 | message: "Post created successfully", 34 | post: result 35 | }); 36 | }).catch(error => { 37 | res.status(500).json({ 38 | message: "Something went wrong", 39 | error: error 40 | }); 41 | }); 42 | }else{ 43 | res.status(400).json({ 44 | message: "Invalid Category ID" 45 | }); 46 | } 47 | }); 48 | 49 | 50 | } 51 | 52 | function show(req, res){ 53 | const id = req.params.id; 54 | 55 | models.Post.findByPk(id, { 56 | include:[models.Category, models.User] 57 | }).then(result => { 58 | if(result){ 59 | res.status(200).json(result); 60 | }else{ 61 | res.status(404).json({ 62 | message: "Post not found!" 63 | }) 64 | } 65 | }).catch(error => { 66 | res.status(500).json({ 67 | message: "Something went wrong!" 68 | }) 69 | }); 70 | } 71 | 72 | function index(req, res){ 73 | models.Post.findAll().then(result => { 74 | res.status(200).json(result); 75 | }).catch(error => { 76 | res.status(500).json({ 77 | message: "Something went wrong!" 78 | }); 79 | }); 80 | } 81 | 82 | function update(req, res){ 83 | const id = req.params.id; 84 | const updatedPost = { 85 | title: req.body.title, 86 | content: req.body.content, 87 | imageUrl: req.body.image_url, 88 | categoryId: req.body.category_id, 89 | } 90 | 91 | const userId = req.userData.user; 92 | 93 | const schema = { 94 | title: {type:"string", optional: false, max: "100"}, 95 | content: {type: "string", optional: false, max: "500"}, 96 | categoryId: {type: "number", optional: false} 97 | } 98 | 99 | const v = new Validator(); 100 | const validationResponse = v.validate(updatedPost, schema); 101 | 102 | if(validationResponse !== true){ 103 | return res.status(400).json({ 104 | message: "Validation failed", 105 | errors: validationResponse 106 | }); 107 | } 108 | 109 | models.Category.findByPk(req.body.category_id).then(result => { 110 | if(result !== null){ 111 | models.Post.update(updatedPost, {where: {id:id, userId: userId}}).then(result => { 112 | res.status(200).json({ 113 | message: "Post updated successfully", 114 | post: updatedPost 115 | }); 116 | }).catch(error => { 117 | res.status(200).json({ 118 | message: "Something went wrong", 119 | error: error 120 | }); 121 | }) 122 | }else{ 123 | res.status(400).json({ 124 | message: "Invalid Category ID" 125 | }); 126 | } 127 | }); 128 | } 129 | 130 | function destroy(req, res){ 131 | const id = req.params.id; 132 | const userId = req.userData.user; 133 | 134 | models.Post.destroy({where:{id:id, userId:userId}}).then(result => { 135 | res.status(200).json({ 136 | message: "Post deleted successfully" 137 | }); 138 | }).catch(error => { 139 | res.status(200).json({ 140 | message: "Something went wrong", 141 | error: error 142 | }); 143 | }); 144 | } 145 | 146 | module.exports = { 147 | save: save, 148 | show: show, 149 | index: index, 150 | update: update, 151 | destroy: destroy 152 | } 153 | -------------------------------------------------------------------------------- /controllers/test.contoller.js: -------------------------------------------------------------------------------- 1 | const models = require('../models'); 2 | 3 | async function test(req, res) { 4 | 5 | //One to one - 1:1 - a user has one address, or an address belongs to only one user 6 | //One to many - 1:m - a user has many posts 7 | //Many to many - m:n - a post belongs to many categories 8 | 9 | //One to one 10 | // const user = await models.User.findByPk(19, { 11 | // include:[models.Address] 12 | // }); 13 | 14 | // const address = await models.Address.findByPk(1, { 15 | // include:[models.User] 16 | // }); 17 | 18 | //One to many 19 | // const user = await models.User.findByPk(19, { 20 | // include:[models.Post] 21 | // }); 22 | 23 | //Many to many 24 | const post = await models.Post.findByPk(1, { 25 | include:[models.Category] 26 | }); 27 | 28 | const category = await models.Category.findByPk(2, { 29 | include:[models.Post] 30 | }); 31 | 32 | 33 | 34 | res.status(200).json({ 35 | data: category 36 | }); 37 | } 38 | 39 | 40 | 41 | 42 | module.exports = { 43 | test: test 44 | } 45 | -------------------------------------------------------------------------------- /controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | const models = require('../models'); 2 | const bcryptjs = require('bcryptjs'); 3 | const jwt = require('jsonwebtoken'); 4 | 5 | function signUp(req, res){ 6 | 7 | //Sign up 8 | models.User.findOne({where:{email:req.body.email}}).then(result => { 9 | if(result){ 10 | res.status(409).json({ 11 | message: "Email already exists!", 12 | }); 13 | }else{ 14 | bcryptjs.genSalt(10, function(err, salt){ 15 | bcryptjs.hash(req.body.password, salt, function(err, hash){ 16 | const user = { 17 | name: req.body.name, 18 | email:req.body.email, 19 | password: hash 20 | } 21 | 22 | models.User.create(user).then(result => { 23 | res.status(201).json({ 24 | message: "User created successfully", 25 | }); 26 | }).catch(error => { 27 | console.log(error); 28 | res.status(500).json({ 29 | message: "Something went wrong!", 30 | }); 31 | }); 32 | }); 33 | }); 34 | } 35 | }).catch(error => { 36 | console.log(error); 37 | res.status(500).json({ 38 | message: "Something went wrong!", 39 | }); 40 | }); 41 | } 42 | 43 | 44 | function login(req, res){ 45 | models.User.findOne({where:{email: req.body.email}}).then(user => { 46 | if(user === null){ 47 | res.status(401).json({ 48 | message: "Invalid credentials!", 49 | }); 50 | }else{ 51 | bcryptjs.compare(req.body.password, user.password, function(err, result){ 52 | if(result){ 53 | const token = jwt.sign({ 54 | email: user.email, 55 | userId: user.id 56 | }, process.env.JWT_KEY, function(err, token){ 57 | res.status(200).json({ 58 | message: "Authentication successful!", 59 | token: token 60 | }); 61 | }); 62 | }else{ 63 | res.status(401).json({ 64 | message: "Invalid credentials!", 65 | }); 66 | } 67 | }); 68 | } 69 | }).catch(error => { 70 | res.status(500).json({ 71 | message: "Something went wrong!", 72 | }); 73 | }); 74 | } 75 | 76 | 77 | module.exports = { 78 | signUp: signUp, 79 | login: login 80 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | #MongoDB 5 | db_service: 6 | container_name: database_container 7 | image: mysql:latest 8 | restart: always 9 | ports: 10 | - 3306:3306 11 | volumes: 12 | - ./db_data:/data/db 13 | environment: 14 | - MYSQL_ROOT_USER=${DB_USERNAME} 15 | - MYSQL_ROOT_PASSWORD=${DB_PASSWORD} 16 | - MYSQL_DATABASE=${DB_NAME} 17 | 18 | app: 19 | container_name: app_container 20 | restart: always 21 | build: . 22 | ports: 23 | - 3000:3000 24 | depends_on: 25 | - db_service 26 | 27 | migration: 28 | container_name: migrator 29 | build: . 30 | depends_on: 31 | - db_service 32 | command: sh -c 'until nc -z db_service 3306; do sleep 1; done; npx sequelize-cli db:migrate' -------------------------------------------------------------------------------- /helpers/image-uploader.js: -------------------------------------------------------------------------------- 1 | const multer = require('multer'); 2 | const path = require('path'); 3 | 4 | const storage = multer.diskStorage({ 5 | destination: function(req, file, cb){ 6 | cb(null, './uploads'); 7 | }, 8 | filename: function(req, file, cb){ 9 | cb(null, new Date().getTime() + path.extname(file.originalname)); 10 | } 11 | }); 12 | 13 | const fileFilter = (req, file, cb) => { 14 | if(file.mimetype === 'image/jpeg' || file.mimetype === 'image/png'){ 15 | cb(null, true); 16 | }else{ 17 | cb(new Error('Unsupported files'), false); 18 | } 19 | } 20 | 21 | const upload = multer({ 22 | storage: storage, 23 | limits: { 24 | fileSize:1024*1024*10 25 | }, 26 | fileFilter:fileFilter 27 | }); 28 | 29 | module.exports = { 30 | upload: upload 31 | } -------------------------------------------------------------------------------- /middleware/check-auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | 3 | function checkAuth(req, res, next){ 4 | try{ 5 | const token = req.headers.authorization.split(" ")[1]; 6 | const decodedToken = jwt.verify(token, process.env.JWT_KEY); 7 | req.userData = decodedToken; 8 | next(); 9 | }catch(e){ 10 | return res.status(401).json({ 11 | 'message': "Invalid or expired token provided!", 12 | 'error':e 13 | }); 14 | } 15 | } 16 | 17 | module.exports = { 18 | checkAuth: checkAuth 19 | } -------------------------------------------------------------------------------- /migrations/20200610200035-create-post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Posts', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | title: { 12 | type: Sequelize.STRING 13 | }, 14 | content: { 15 | type: Sequelize.TEXT 16 | }, 17 | imageUrl: { 18 | type: Sequelize.STRING, 19 | allowNull: true 20 | }, 21 | categoryId: { 22 | type: Sequelize.INTEGER 23 | }, 24 | userId: { 25 | type: Sequelize.INTEGER 26 | }, 27 | createdAt: { 28 | allowNull: false, 29 | type: Sequelize.DATE 30 | }, 31 | updatedAt: { 32 | allowNull: false, 33 | type: Sequelize.DATE 34 | } 35 | }); 36 | }, 37 | down: (queryInterface, Sequelize) => { 38 | return queryInterface.dropTable('Posts'); 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /migrations/20200610201007-create-user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Users', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | name: { 12 | type: Sequelize.STRING 13 | }, 14 | email: { 15 | type: Sequelize.STRING 16 | }, 17 | password: { 18 | type: Sequelize.STRING 19 | }, 20 | createdAt: { 21 | allowNull: false, 22 | type: Sequelize.DATE 23 | }, 24 | updatedAt: { 25 | allowNull: false, 26 | type: Sequelize.DATE 27 | } 28 | }); 29 | }, 30 | down: (queryInterface, Sequelize) => { 31 | return queryInterface.dropTable('Users'); 32 | } 33 | }; -------------------------------------------------------------------------------- /migrations/20200610201055-create-category.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Categories', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | name: { 12 | type: Sequelize.STRING 13 | }, 14 | createdAt: { 15 | allowNull: false, 16 | type: Sequelize.DATE 17 | }, 18 | updatedAt: { 19 | allowNull: false, 20 | type: Sequelize.DATE 21 | } 22 | }); 23 | }, 24 | down: (queryInterface, Sequelize) => { 25 | return queryInterface.dropTable('Categories'); 26 | } 27 | }; -------------------------------------------------------------------------------- /migrations/20200610201156-create-comment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = { 3 | up: (queryInterface, Sequelize) => { 4 | return queryInterface.createTable('Comments', { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER 10 | }, 11 | content: { 12 | type: Sequelize.TEXT 13 | }, 14 | postId: { 15 | type: Sequelize.INTEGER 16 | }, 17 | userId: { 18 | type: Sequelize.INTEGER 19 | }, 20 | createdAt: { 21 | allowNull: false, 22 | type: Sequelize.DATE 23 | }, 24 | updatedAt: { 25 | allowNull: false, 26 | type: Sequelize.DATE 27 | } 28 | }); 29 | }, 30 | down: (queryInterface, Sequelize) => { 31 | return queryInterface.dropTable('Comments'); 32 | } 33 | }; -------------------------------------------------------------------------------- /models/address.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | const Address = sequelize.define('Address', { 4 | address: DataTypes.STRING, 5 | userId: DataTypes.INTEGER 6 | }, {}); 7 | Address.associate = function(models) { 8 | // associations can be defined here 9 | Address.belongsTo(models.User); 10 | }; 11 | return Address; 12 | }; 13 | -------------------------------------------------------------------------------- /models/category.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | const Category = sequelize.define('Category', { 4 | name: DataTypes.STRING 5 | }, {}); 6 | Category.associate = function(models) { 7 | // associations can be defined here 8 | Category.belongsToMany(models.Post, {through: 'PostCategory'}); 9 | }; 10 | return Category; 11 | }; -------------------------------------------------------------------------------- /models/comment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | const Comment = sequelize.define('Comment', { 4 | content: DataTypes.TEXT, 5 | postId: DataTypes.INTEGER, 6 | userId: DataTypes.INTEGER 7 | }, {}); 8 | Comment.associate = function(models) { 9 | // associations can be defined here 10 | }; 11 | return Comment; 12 | }; -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const Sequelize = require('sequelize'); 6 | const basename = path.basename(__filename); 7 | const env = process.env.NODE_ENV || 'development'; 8 | const config = require(__dirname + '/../config/config.js')[env]; 9 | const db = {}; 10 | 11 | let sequelize; 12 | if (config.use_env_variable) { 13 | sequelize = new Sequelize(process.env[config.use_env_variable], config); 14 | } else { 15 | sequelize = new Sequelize(config.database, config.username, config.password, config); 16 | } 17 | 18 | fs 19 | .readdirSync(__dirname) 20 | .filter(file => { 21 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); 22 | }) 23 | .forEach(file => { 24 | const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes) 25 | db[model.name] = model; 26 | }); 27 | 28 | Object.keys(db).forEach(modelName => { 29 | if (db[modelName].associate) { 30 | db[modelName].associate(db); 31 | } 32 | }); 33 | 34 | db.sequelize = sequelize; 35 | db.Sequelize = Sequelize; 36 | 37 | module.exports = db; 38 | -------------------------------------------------------------------------------- /models/post.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | const Post = sequelize.define('Post', { 4 | title: DataTypes.STRING, 5 | content: DataTypes.TEXT, 6 | imageUrl: DataTypes.STRING, 7 | userId: DataTypes.INTEGER 8 | }, {}); 9 | Post.associate = function(models) { 10 | // associations can be defined here 11 | Post.belongsToMany(models.Category, {through: 'PostCategory'}); 12 | Post.belongsTo(models.User); 13 | }; 14 | return Post; 15 | }; 16 | -------------------------------------------------------------------------------- /models/postcategory.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | 4 | const PostCategory = sequelize.define('PostCategory', { 5 | postId: DataTypes.INTEGER, 6 | categoryId: DataTypes.INTEGER, 7 | }, {timestamps: false}); 8 | PostCategory.associate = function(models) { 9 | // associations can be defined here 10 | }; 11 | return PostCategory; 12 | }; 13 | -------------------------------------------------------------------------------- /models/role.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | const Role = sequelize.define('Role', { 4 | name: DataTypes.STRING 5 | }, {}); 6 | Role.associate = function(models) { 7 | // associations can be defined here 8 | }; 9 | return Role; 10 | }; -------------------------------------------------------------------------------- /models/user.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | module.exports = (sequelize, DataTypes) => { 3 | const User = sequelize.define('User', { 4 | name: DataTypes.STRING, 5 | email: DataTypes.STRING, 6 | password: DataTypes.STRING 7 | }, {}); 8 | User.associate = function(models) { 9 | // associations can be defined here 10 | User.hasOne(sequelize.define('Address')); 11 | User.hasMany(sequelize.define('Post')); 12 | }; 13 | return User; 14 | }; -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "JWT_KEY": "secret" 4 | } 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nodejs-mysql-api", 3 | "version": "1.0.0", 4 | "description": "A REST API with Nodejs and Mysql", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "start": "nodemon server.js" 9 | }, 10 | "author": "Tharindu Lakshitha", 11 | "license": "ISC", 12 | "dependencies": { 13 | "bcryptjs": "^2.4.3", 14 | "body-parser": "^1.19.0", 15 | "dotenv": "^16.3.1", 16 | "express": "^4.17.1", 17 | "fastest-validator": "^1.8.0", 18 | "jest": "^26.6.3", 19 | "jsonwebtoken": "^8.5.1", 20 | "multer": "^1.4.2", 21 | "mysql2": "^2.2.5", 22 | "nodemon": "^2.0.6", 23 | "sequelize": "^6.3.5", 24 | "supertest": "^6.1.3" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /routes/comments.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const commentController = require('../controllers/comment.controller'); 3 | 4 | const router = express.Router(); 5 | 6 | router.post("/", commentController.save); 7 | router.get("/", commentController.index); 8 | router.get("/:id", commentController.show); 9 | router.patch("/:id", commentController.update); 10 | router.delete("/:id", commentController.destroy); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /routes/images.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const imageController = require('../controllers/image.controller'); 3 | const imageUploader = require('../helpers/image-uploader'); 4 | const checkAuth = require('../middleware/check-auth'); 5 | 6 | const router = express.Router(); 7 | 8 | router.post('/upload', checkAuth.checkAuth, imageUploader.upload.single('image'), imageController.upload); 9 | 10 | module.exports = router; -------------------------------------------------------------------------------- /routes/posts.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const postsController = require('../controllers/post.controller'); 3 | const checkAuthMiddleware = require('../middleware/check-auth'); 4 | 5 | const router = express.Router(); 6 | 7 | router.post("/", checkAuthMiddleware.checkAuth, postsController.save); 8 | router.get("/", postsController.index); 9 | router.get("/:id", postsController.show); 10 | router.patch("/:id", checkAuthMiddleware.checkAuth, postsController.update); 11 | router.delete("/:id", checkAuthMiddleware.checkAuth, postsController.destroy); 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /routes/tests.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const testController = require('../controllers/test.contoller'); 3 | 4 | const router = express.Router(); 5 | 6 | router.get("/associations", testController.test); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /routes/user.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const userController = require('../controllers/user.controller'); 3 | 4 | const router = express.Router(); 5 | 6 | router.post('/sign-up', userController.signUp); 7 | router.post('/login', userController.login) 8 | 9 | module.exports = router; -------------------------------------------------------------------------------- /seeders/20201109195900-category-seeder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | up: (queryInterface, Sequelize) => { 5 | return queryInterface.bulkInsert('categories', [ 6 | { 7 | name: 'NodeJs' 8 | }, 9 | { 10 | name: 'VueJS' 11 | }, 12 | { 13 | name: 'ReactJS' 14 | }, 15 | { 16 | name: 'ReactNative' 17 | }, 18 | { 19 | name: 'Laravel' 20 | }, 21 | { 22 | name: 'Flutter' 23 | } 24 | ]); 25 | }, 26 | 27 | down: (queryInterface, Sequelize) => { 28 | return queryInterface.bulkDelete('categories',{}, null); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const http = require('http'); 2 | const app = require('./app'); 3 | const port = 3000; 4 | 5 | const server = http.createServer(app); 6 | 7 | server.listen(port); 8 | -------------------------------------------------------------------------------- /uploads/1604177873182.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tharindulucky/blog-api-nodejs-mysql/d224542dc23a21b3c536e301084e0883cbfb9806/uploads/1604177873182.png -------------------------------------------------------------------------------- /uploads/1604177895511.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tharindulucky/blog-api-nodejs-mysql/d224542dc23a21b3c536e301084e0883cbfb9806/uploads/1604177895511.jpg -------------------------------------------------------------------------------- /uploads/1604212798125.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tharindulucky/blog-api-nodejs-mysql/d224542dc23a21b3c536e301084e0883cbfb9806/uploads/1604212798125.jpg -------------------------------------------------------------------------------- /uploads/1604212807246.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tharindulucky/blog-api-nodejs-mysql/d224542dc23a21b3c536e301084e0883cbfb9806/uploads/1604212807246.jpg --------------------------------------------------------------------------------