From e1e4cd194d82da42473e41586064d3b43f14b516 Mon Sep 17 00:00:00 2001 From: Devansh Agarwal Date: Sun, 8 Mar 2020 19:37:28 +0530 Subject: [PATCH 01/42] API for URL Shortening --- app.js | 2 ++ app/models/ShortUrl.js | 17 +++++++++++++++++ app/routes/index.js | 16 ++++++++++++++++ app/routes/url.js | 43 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 app/models/ShortUrl.js create mode 100644 app/routes/url.js diff --git a/app.js b/app.js index b2ebcdb..4888cac 100644 --- a/app.js +++ b/app.js @@ -9,6 +9,7 @@ const indexRouter = require('./app/routes/index') const authRouter = require('./app/routes/auth') const usersRouter = require('./app/routes/user') const postRouter = require('./app/routes/post') +const urlRouter = require('./app/routes/url') const app = express() @@ -26,6 +27,7 @@ app.use('/', indexRouter) app.use('/auth', authRouter) app.use('/user', usersRouter) app.use('/post', postRouter) +app.use('/api', urlRouter) // catch 404 and forward to error handler app.use(function (req, res, next) { diff --git a/app/models/ShortUrl.js b/app/models/ShortUrl.js new file mode 100644 index 0000000..8d40b97 --- /dev/null +++ b/app/models/ShortUrl.js @@ -0,0 +1,17 @@ +const mongoose = require('mongoose') + +const ShortUrlSchema = new mongoose.Schema({ + longurl: { + type: String, + required: true + }, + urlcode: { + type: String + }, + shorturl: { + type: String + } +}) + +const shortURL = mongoose.model('shortURL',ShortUrlSchema); +module.exports = shortURL; \ No newline at end of file diff --git a/app/routes/index.js b/app/routes/index.js index 741fddc..ebd0483 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -1,9 +1,25 @@ var express = require('express') var router = express.Router() const documentationUrl = 'https://documenter.getpostman.com/view/1159934/SWDze1Rp' +const Url = require('../models/ShortUrl'); /* GET home page. */ router.get('/', function (req, res, next) { res.redirect(documentationUrl) }) +router.get('/:shorturl',async (req,res)=>{ + try { + const url = await Url.findOne({urlcode: req.params.shorturl}); + + if (url) { + return res.redirect(url.longurl); + } + else{ + return res.json('No url found!'); + } + } catch (error) { + res.json('Server error!') + } +}); + module.exports = router diff --git a/app/routes/url.js b/app/routes/url.js new file mode 100644 index 0000000..36dfeb8 --- /dev/null +++ b/app/routes/url.js @@ -0,0 +1,43 @@ +var express = require('express') +var router = express.Router() +const Url = require('../models/ShortUrl'); + +function validURL(myURL) { + var pattern = new RegExp('^(https?:\\/\\/)?'+ + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|'+ + '((\\d{1,3}\\.){3}\\d{1,3}))'+ + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ + '(\\?[;&a-z\\d%_.~+=-]*)?'+ + '(\\#[-a-z\\d_]*)?$','i'); + return pattern.test(myURL); + } + +router.post('/shorten',async (req,res) => { + const longurl = req.body; + var baseurl = req.get('host'); + const urlcode = Date.now(); + if(validURL(longurl.longurl)){ + try { + var url = await Url.findOne(longurl) + if(url){ + res.json(url) + }else{ + var shorturl = baseurl + '/' + urlcode; + url = new Url({ + longurl : longurl.longurl, + shorturl, + urlcode + }) + await url.save(); + res.json(url); + } + } catch (error) { + console.log(error); + res.json('Server error'); + } + }else{ + res.json('invalid long url'); + } +}) + +module.exports = router \ No newline at end of file From 38e04b79f887ffafb21b9901e08f834e693c4b98 Mon Sep 17 00:00:00 2001 From: Devansh Agarwal Date: Wed, 18 Mar 2020 15:31:49 +0530 Subject: [PATCH 02/42] fixed the regex changes --- app/routes/url.js | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/app/routes/url.js b/app/routes/url.js index 36dfeb8..5a4177d 100644 --- a/app/routes/url.js +++ b/app/routes/url.js @@ -1,21 +1,23 @@ -var express = require('express') -var router = express.Router() +const express = require('express') +const router = express.Router() const Url = require('../models/ShortUrl'); +const regex = '^(https?:\\/\\/)?'+ +'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|'+ +'((\\d{1,3}\\.){3}\\d{1,3}))'+ +'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ +'(\\?[;&a-z\\d%_.~+=-]*)?'+ +'(\\#[-a-z\\d_]*)?$'; + function validURL(myURL) { - var pattern = new RegExp('^(https?:\\/\\/)?'+ - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|'+ - '((\\d{1,3}\\.){3}\\d{1,3}))'+ - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ - '(\\?[;&a-z\\d%_.~+=-]*)?'+ - '(\\#[-a-z\\d_]*)?$','i'); + var pattern = new RegExp(regex,'i'); return pattern.test(myURL); } router.post('/shorten',async (req,res) => { - const longurl = req.body; + var longurl = req.body; var baseurl = req.get('host'); - const urlcode = Date.now(); + var urlcode = Date.now(); if(validURL(longurl.longurl)){ try { var url = await Url.findOne(longurl) From 0c8bc41c73c7442970a2cffc376dcc4cb4eb0124 Mon Sep 17 00:00:00 2001 From: Devansh Agarwal Date: Mon, 23 Mar 2020 15:43:03 +0530 Subject: [PATCH 03/42] Changed the required names --- app.js | 4 ++-- app/models/{ShortUrl.js => UrlShortner.js} | 4 ++-- app/routes/index.js | 19 ++++--------------- app/routes/{url.js => urlShortner.js} | 17 ++++++++++++++++- 4 files changed, 24 insertions(+), 20 deletions(-) rename app/models/{ShortUrl.js => UrlShortner.js} (66%) rename app/routes/{url.js => urlShortner.js} (75%) diff --git a/app.js b/app.js index 4888cac..e85d1f7 100644 --- a/app.js +++ b/app.js @@ -9,7 +9,7 @@ const indexRouter = require('./app/routes/index') const authRouter = require('./app/routes/auth') const usersRouter = require('./app/routes/user') const postRouter = require('./app/routes/post') -const urlRouter = require('./app/routes/url') +const shortUrlRouter = require('./app/routes/urlShortner') const app = express() @@ -27,7 +27,7 @@ app.use('/', indexRouter) app.use('/auth', authRouter) app.use('/user', usersRouter) app.use('/post', postRouter) -app.use('/api', urlRouter) +app.use('/shortUrl', shortUrlRouter) // catch 404 and forward to error handler app.use(function (req, res, next) { diff --git a/app/models/ShortUrl.js b/app/models/UrlShortner.js similarity index 66% rename from app/models/ShortUrl.js rename to app/models/UrlShortner.js index 8d40b97..b41945f 100644 --- a/app/models/ShortUrl.js +++ b/app/models/UrlShortner.js @@ -1,6 +1,6 @@ const mongoose = require('mongoose') -const ShortUrlSchema = new mongoose.Schema({ +const urlShortnerSchema = new mongoose.Schema({ longurl: { type: String, required: true @@ -13,5 +13,5 @@ const ShortUrlSchema = new mongoose.Schema({ } }) -const shortURL = mongoose.model('shortURL',ShortUrlSchema); +const shortURL = mongoose.model('shortURL',urlShortnerSchema); module.exports = shortURL; \ No newline at end of file diff --git a/app/routes/index.js b/app/routes/index.js index ebd0483..8b4a8a7 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -1,25 +1,14 @@ var express = require('express') var router = express.Router() const documentationUrl = 'https://documenter.getpostman.com/view/1159934/SWDze1Rp' -const Url = require('../models/ShortUrl'); + /* GET home page. */ router.get('/', function (req, res, next) { res.redirect(documentationUrl) }) -router.get('/:shorturl',async (req,res)=>{ - try { - const url = await Url.findOne({urlcode: req.params.shorturl}); - - if (url) { - return res.redirect(url.longurl); - } - else{ - return res.json('No url found!'); - } - } catch (error) { - res.json('Server error!') - } -}); +router.get('/:shorturl', (req,res,next)=>{ + res.redirect('/shortUrl/'+ req.params.shorturl); +}) module.exports = router diff --git a/app/routes/url.js b/app/routes/urlShortner.js similarity index 75% rename from app/routes/url.js rename to app/routes/urlShortner.js index 5a4177d..6a4faf3 100644 --- a/app/routes/url.js +++ b/app/routes/urlShortner.js @@ -1,6 +1,6 @@ const express = require('express') const router = express.Router() -const Url = require('../models/ShortUrl'); +const Url = require('../models/UrlShortner'); const regex = '^(https?:\\/\\/)?'+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|'+ '((\\d{1,3}\\.){3}\\d{1,3}))'+ @@ -14,6 +14,21 @@ function validURL(myURL) { return pattern.test(myURL); } +router.get('/:shorturl',async (req,res)=>{ + try { + const url = await Url.findOne({urlcode: req.params.shorturl}); + + if (url) { + return res.redirect(url.longurl); + } + else{ + return res.json('No url found!'); + } + } catch (error) { + res.json('Server error!') + } + }); + router.post('/shorten',async (req,res) => { var longurl = req.body; var baseurl = req.get('host'); From d1710913c74aa38d31b6a527b0836a2da89120e1 Mon Sep 17 00:00:00 2001 From: Devansh Agarwal Date: Mon, 23 Mar 2020 22:40:47 +0530 Subject: [PATCH 04/42] Added the controllers --- app/controllers/urlShortner.js | 60 +++++++++++++++++++++++++++++++ app/routes/urlShortner.js | 65 ++++++---------------------------- 2 files changed, 71 insertions(+), 54 deletions(-) create mode 100644 app/controllers/urlShortner.js diff --git a/app/controllers/urlShortner.js b/app/controllers/urlShortner.js new file mode 100644 index 0000000..03fdc88 --- /dev/null +++ b/app/controllers/urlShortner.js @@ -0,0 +1,60 @@ +const urlModel = require('../models/UrlShortner'); + +const regex = '^(https?:\\/\\/)?'+ +'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|'+ +'((\\d{1,3}\\.){3}\\d{1,3}))'+ +'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ +'(\\?[;&a-z\\d%_.~+=-]*)?'+ +'(\\#[-a-z\\d_]*)?$'; + + +function validURL(myURL) { + var pattern = new RegExp(regex,'i'); + return pattern.test(myURL); + } + + +module.exports = { + redirect: async function (req,res){ + try { + const url = await urlModel.findOne({urlcode: req.params.shorturl}); + + if (url) { + return res.redirect(url.longurl); + } + else{ + return res.json('No url found!'); + } + } catch (error) { + res.json('Server error!') + } + }, + + shorten: async function (req,res){ + var longurl = req.body; + var baseurl = req.get('host'); + var urlcode = Date.now(); + if(validURL(longurl.longurl)){ + try { + var url = await urlModel.findOne(longurl) + if(url){ + res.json(url) + }else{ + var shorturl = baseurl + '/' + urlcode; + url = new urlModel({ + longurl : longurl.longurl, + shorturl, + urlcode + }) + await url.save(); + res.json(url); + } + } catch (error) { + console.log(error); + res.json('Server error'); + } + }else{ + res.json('invalid long url'); + } + } +} diff --git a/app/routes/urlShortner.js b/app/routes/urlShortner.js index 6a4faf3..35d4118 100644 --- a/app/routes/urlShortner.js +++ b/app/routes/urlShortner.js @@ -1,60 +1,17 @@ const express = require('express') const router = express.Router() -const Url = require('../models/UrlShortner'); -const regex = '^(https?:\\/\\/)?'+ -'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|'+ -'((\\d{1,3}\\.){3}\\d{1,3}))'+ -'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ -'(\\?[;&a-z\\d%_.~+=-]*)?'+ -'(\\#[-a-z\\d_]*)?$'; +const shortnerController = require('../controllers/UrlShortner'); +//Redirects the ShortURL back to LongURL +router.get( + '/:shorturl', + shortnerController.redirect + ); -function validURL(myURL) { - var pattern = new RegExp(regex,'i'); - return pattern.test(myURL); - } - -router.get('/:shorturl',async (req,res)=>{ - try { - const url = await Url.findOne({urlcode: req.params.shorturl}); - - if (url) { - return res.redirect(url.longurl); - } - else{ - return res.json('No url found!'); - } - } catch (error) { - res.json('Server error!') - } - }); - -router.post('/shorten',async (req,res) => { - var longurl = req.body; - var baseurl = req.get('host'); - var urlcode = Date.now(); - if(validURL(longurl.longurl)){ - try { - var url = await Url.findOne(longurl) - if(url){ - res.json(url) - }else{ - var shorturl = baseurl + '/' + urlcode; - url = new Url({ - longurl : longurl.longurl, - shorturl, - urlcode - }) - await url.save(); - res.json(url); - } - } catch (error) { - console.log(error); - res.json('Server error'); - } - }else{ - res.json('invalid long url'); - } -}) +//Shorten the LongURL and saves in DB +router.post( + '/shorten', + shortnerController.shorten + ); module.exports = router \ No newline at end of file From da7b2ba462eb3063f33cf0859c49b261ac6c6862 Mon Sep 17 00:00:00 2001 From: Devansh Agarwal Date: Wed, 25 Mar 2020 13:12:32 +0530 Subject: [PATCH 05/42] Formatted the code --- app/controllers/urlShortner.js | 101 ++++++++++++++++----------------- app/models/UrlShortner.js | 24 ++++---- app/routes/index.js | 4 +- app/routes/urlShortner.js | 12 ++-- 4 files changed, 69 insertions(+), 72 deletions(-) diff --git a/app/controllers/urlShortner.js b/app/controllers/urlShortner.js index 03fdc88..c4de56a 100644 --- a/app/controllers/urlShortner.js +++ b/app/controllers/urlShortner.js @@ -1,60 +1,57 @@ -const urlModel = require('../models/UrlShortner'); +const UrlModel = require('../models/UrlShortner') -const regex = '^(https?:\\/\\/)?'+ -'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|'+ -'((\\d{1,3}\\.){3}\\d{1,3}))'+ -'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'+ -'(\\?[;&a-z\\d%_.~+=-]*)?'+ -'(\\#[-a-z\\d_]*)?$'; - - -function validURL(myURL) { - var pattern = new RegExp(regex,'i'); - return pattern.test(myURL); - } +const regex = '^(https?:\\/\\/)?' + + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|' + + '((\\d{1,3}\\.){3}\\d{1,3}))' + + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + + '(\\?[;&a-z\\d%_.~+=-]*)?' + + '(\\#[-a-z\\d_]*)?$' +function validURL (myURL) { + var pattern = new RegExp(regex, 'i') + return pattern.test(myURL) +} module.exports = { - redirect: async function (req,res){ - try { - const url = await urlModel.findOne({urlcode: req.params.shorturl}); - - if (url) { - return res.redirect(url.longurl); - } - else{ - return res.json('No url found!'); - } - } catch (error) { - res.json('Server error!') - } - }, + redirect: async function (req, res) { + try { + const url = await UrlModel.findOne({ urlcode: req.params.shorturl }) + + if (url) { + return res.redirect(url.longurl) + } else { + return res.json('No url found!') + } + } catch (error) { + res.json('Server error!') + } + }, - shorten: async function (req,res){ - var longurl = req.body; - var baseurl = req.get('host'); - var urlcode = Date.now(); - if(validURL(longurl.longurl)){ - try { - var url = await urlModel.findOne(longurl) - if(url){ - res.json(url) - }else{ - var shorturl = baseurl + '/' + urlcode; - url = new urlModel({ - longurl : longurl.longurl, - shorturl, - urlcode - }) - await url.save(); - res.json(url); - } - } catch (error) { - console.log(error); - res.json('Server error'); - } - }else{ - res.json('invalid long url'); + shorten: async function (req, res) { + var longurl = req.body + var baseurl = req.get('host') + var urlcode = Date.now() + if (validURL(longurl.longurl)) { + try { + var url = await UrlModel.findOne(longurl) + if (url) { + res.json(url) + } else { + var shorturl = baseurl + '/' + urlcode + url = new UrlModel({ + longurl: longurl.longurl, + shorturl, + urlcode + }) + await url.save() + res.json(url) } + } catch (error) { + console.log(error) + res.json('Server error') + } + } else { + res.json('invalid long url') } + } } diff --git a/app/models/UrlShortner.js b/app/models/UrlShortner.js index b41945f..b5b60fd 100644 --- a/app/models/UrlShortner.js +++ b/app/models/UrlShortner.js @@ -1,17 +1,17 @@ const mongoose = require('mongoose') const urlShortnerSchema = new mongoose.Schema({ - longurl: { - type: String, - required: true - }, - urlcode: { - type: String - }, - shorturl: { - type: String - } + longurl: { + type: String, + required: true + }, + urlcode: { + type: String + }, + shorturl: { + type: String + } }) -const shortURL = mongoose.model('shortURL',urlShortnerSchema); -module.exports = shortURL; \ No newline at end of file +const shortURL = mongoose.model('shortURL', urlShortnerSchema) +module.exports = shortURL diff --git a/app/routes/index.js b/app/routes/index.js index 8b4a8a7..ebf8bac 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -7,8 +7,8 @@ router.get('/', function (req, res, next) { res.redirect(documentationUrl) }) -router.get('/:shorturl', (req,res,next)=>{ - res.redirect('/shortUrl/'+ req.params.shorturl); +router.get('/:shorturl', (req, res, next) => { + res.redirect('/shortUrl/' + req.params.shorturl) }) module.exports = router diff --git a/app/routes/urlShortner.js b/app/routes/urlShortner.js index 35d4118..2b553ba 100644 --- a/app/routes/urlShortner.js +++ b/app/routes/urlShortner.js @@ -1,17 +1,17 @@ const express = require('express') const router = express.Router() -const shortnerController = require('../controllers/UrlShortner'); +const shortnerController = require('../controllers/UrlShortner') -//Redirects the ShortURL back to LongURL +// Redirects the ShortURL back to LongURL router.get( '/:shorturl', shortnerController.redirect - ); +) -//Shorten the LongURL and saves in DB +// Shorten the LongURL and saves in DB router.post( '/shorten', shortnerController.shorten - ); +) -module.exports = router \ No newline at end of file +module.exports = router From e76a3d47a8aea08b54886fa250310fa972b7af90 Mon Sep 17 00:00:00 2001 From: Rupeshiya Date: Wed, 25 Mar 2020 00:50:19 +0530 Subject: [PATCH 06/42] added functionality to reset password --- app/controllers/user.js | 48 ++++++++++++++++++++++++++++++++++++++++- app/models/User.js | 6 ++++++ app/routes/user.js | 11 ++++++++++ package-lock.json | 5 +++++ package.json | 1 + 5 files changed, 70 insertions(+), 1 deletion(-) diff --git a/app/controllers/user.js b/app/controllers/user.js index a3e9f1e..8313de7 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -1,4 +1,5 @@ const User = require('../models/User') +const crypto = require('crypto') module.exports = { createUser: async (req, res, next) => { @@ -36,10 +37,55 @@ module.exports = { res.status(400).json({ error }) } }, + forgotPasswordRequest: async (req, res) => { + const { email } = req.body + try { + const user = await User.findOne({ email: email }) + if (!user) { + res.status(404).json({ success: false, msg: `User with ${email} is not found!` }) + } + var token = await crypto.randomBytes(32).toString('hex') + if (!token && process.env.NODE_ENV !== 'production') { + console.log('Token not generated in forgotPasswordRequest') + } + user.resetPassToken = token + user.resetPassTokenExpireIn = Date.now() + 3600000 // 1hr + await user.save() + return res.status(200).json({ success: true, token }) + } catch (error) { + if (process.env.NODE_ENV !== 'production' && error) { + console.log('Error in forgotPasswordRequest ', error) + } + res.status(400).json({ success: false, error }) + } + }, + updatePassword: async (req, res) => { + const { newPassword } = req.body + const { token } = req.params + try { + const user = await User.findOne({ resetPassToken: token, resetPassTokenExpireIn: { $gt: Date.now() } }) + if (!user) { + return res.status(400).json({ success: false, msg: 'Reset token is invalid or expired!' }) + } + user.password = newPassword + user.resetPassToken = undefined + user.resetPassTokenExpireIn = undefined + await user.save() + return res.status(204) + } catch (err) { + if (process.env.NODE_ENV !== 'production' && err) { + console.log('Error in updatePassword ', err) + } + res.status(400).json({ success: false, err }) + } + }, + logout: (req, res, next) => { + res.json({ success: 'ok' }) + }, userDelete: async (req, res, next) => { try { await req.user.remove() - res.send({ data: 'user deletetion successful', user: req.user }) + res.send({ data: 'user deletion successful', user: req.user }) } catch (error) { console.log(error) res.status(500).json({ error }) diff --git a/app/models/User.js b/app/models/User.js index a2af919..7ef0a3a 100644 --- a/app/models/User.js +++ b/app/models/User.js @@ -68,6 +68,12 @@ const UserSchema = new mongoose.Schema({ } } }, + resetPassToken: { + type: String + }, + resetPassTokenExpireIn: { + type: Date + }, socialMedia: { youtube: { type: String diff --git a/app/routes/user.js b/app/routes/user.js index 99b8c87..6dc43cb 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -23,6 +23,17 @@ router.patch( userController.userProfileUpdate ) +// user forgot password request +router.post( + '/changepassword', + userController.forgotPasswordRequest +) + +// update password +router.post( + '/reset/:token', + userController.updatePassword +) // delete a user router.delete( '/me', diff --git a/package-lock.json b/package-lock.json index 4547b5b..391f041 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1499,6 +1499,11 @@ "which": "^1.2.9" } }, + "crypto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", + "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==" + }, "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", diff --git a/package.json b/package.json index 6e6cf30..f98dbbc 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "bcrypt": "^3.0.6", "body-parser": "^1.19.0", "cookie-parser": "~1.4.4", + "crypto": "^1.0.1", "debug": "~2.6.9", "dotenv": "^8.2.0", "ejs": "~2.6.1", From 1bd1efa4afe95ed73ab259bb04375e891a88c5ef Mon Sep 17 00:00:00 2001 From: Devansh Agarwal Date: Wed, 25 Mar 2020 13:31:32 +0530 Subject: [PATCH 07/42] factored the code --- app/routes/urlShortner.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/routes/urlShortner.js b/app/routes/urlShortner.js index 2b553ba..779e5d0 100644 --- a/app/routes/urlShortner.js +++ b/app/routes/urlShortner.js @@ -1,6 +1,6 @@ const express = require('express') const router = express.Router() -const shortnerController = require('../controllers/UrlShortner') +const shortnerController = require('../controllers/urlShortner') // Redirects the ShortURL back to LongURL router.get( From 3879cc61e88ed19ec7f2da1217aa5ba5f5b24516 Mon Sep 17 00:00:00 2001 From: Devesh Verma Date: Wed, 25 Mar 2020 23:03:29 +0530 Subject: [PATCH 08/42] improved password reset and handled login error reverted change on test script --- app/controllers/auth.js | 4 +-- app/controllers/user.js | 54 ++++++++++++++++++++++++----------------- app/models/User.js | 11 ++------- app/routes/user.js | 4 +-- config/mongoose.js | 6 +++++ package.json | 3 ++- 6 files changed, 46 insertions(+), 36 deletions(-) diff --git a/app/controllers/auth.js b/app/controllers/auth.js index dd5ea0e..763e599 100644 --- a/app/controllers/auth.js +++ b/app/controllers/auth.js @@ -10,9 +10,9 @@ module.exports = { res.send({ user: user, token: token }) } catch (error) { if (process.env.NODE_ENV !== 'production') { - // console.log('error ', error) + console.log(error.name, '-', error.message) } - res.status(400).send({ error: error }) + res.status(400).json({ error: error.message }) } }, logout: (req, res, next) => { diff --git a/app/controllers/user.js b/app/controllers/user.js index 8313de7..695fe46 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -1,5 +1,5 @@ const User = require('../models/User') -const crypto = require('crypto') +const jwt = require('jsonwebtoken') module.exports = { createUser: async (req, res, next) => { @@ -13,9 +13,11 @@ module.exports = { res.status(400).json({ error: error }) } }, + userProfile: async (req, res, next) => { res.json(req.user) }, + userProfileUpdate: async (req, res, next) => { const updates = Object.keys(req.body) const allowedUpdates = ['name', 'email', 'password', 'company', 'website', 'location', 'about'] @@ -37,51 +39,59 @@ module.exports = { res.status(400).json({ error }) } }, + forgotPasswordRequest: async (req, res) => { const { email } = req.body try { const user = await User.findOne({ email: email }) if (!user) { - res.status(404).json({ success: false, msg: `User with ${email} is not found!` }) - } - var token = await crypto.randomBytes(32).toString('hex') - if (!token && process.env.NODE_ENV !== 'production') { - console.log('Token not generated in forgotPasswordRequest') + res.status(404).json({ msg: 'User not found!' }) } - user.resetPassToken = token - user.resetPassTokenExpireIn = Date.now() + 3600000 // 1hr + const token = jwt.sign({ _id: user._id, expiry: Date.now() + 10800000 }, process.env.JWT_SECRET) await user.save() return res.status(200).json({ success: true, token }) } catch (error) { if (process.env.NODE_ENV !== 'production' && error) { console.log('Error in forgotPasswordRequest ', error) } - res.status(400).json({ success: false, error }) + res.status(400).json({ error }) } }, + updatePassword: async (req, res) => { - const { newPassword } = req.body + const { password, id } = req.body const { token } = req.params try { - const user = await User.findOne({ resetPassToken: token, resetPassTokenExpireIn: { $gt: Date.now() } }) - if (!user) { - return res.status(400).json({ success: false, msg: 'Reset token is invalid or expired!' }) + const decodedtoken = jwt.verify(token, process.env.JWT_SECRET) + + if (Date.now() <= decodedtoken.expiry) { + const user = await User.findById({ + _id: id + }) + if (!user) { + return res.status(400).json({ msg: 'No such user' }) + } + user.password = password + await user.save() + return res.status(200).json({ updated: true }) + } else { + if (process.env.NODE_ENV !== 'production') { + console.log('token expired') + } + res.status(400).json({ error: 'Token expired' }) } - user.password = newPassword - user.resetPassToken = undefined - user.resetPassTokenExpireIn = undefined - await user.save() - return res.status(204) - } catch (err) { - if (process.env.NODE_ENV !== 'production' && err) { - console.log('Error in updatePassword ', err) + } catch (error) { + if (process.env.NODE_ENV !== 'production' && error) { + console.log('Something went wrong ', error) } - res.status(400).json({ success: false, err }) + res.status(400).json({ error }) } }, + logout: (req, res, next) => { res.json({ success: 'ok' }) }, + userDelete: async (req, res, next) => { try { await req.user.remove() diff --git a/app/models/User.js b/app/models/User.js index 7ef0a3a..1f11616 100644 --- a/app/models/User.js +++ b/app/models/User.js @@ -46,7 +46,6 @@ const UserSchema = new mongoose.Schema({ phone: { type: String, trim: true, - unique: true, minlength: 10, validate (phone) { if (!validator.isLength(phone, { min: 10, max: 10 })) { @@ -68,12 +67,6 @@ const UserSchema = new mongoose.Schema({ } } }, - resetPassToken: { - type: String - }, - resetPassTokenExpireIn: { - type: Date - }, socialMedia: { youtube: { type: String @@ -181,11 +174,11 @@ UserSchema.statics.findByCredentials = async (email, password) => { }) if (!user) { - throw new Error('Unable to login') + throw new Error('No such user') } else { const isMatch = await bcrypt.compare(password, user.password) if (!isMatch) { - throw new Error('Unable to login') + throw new Error('Incorrect password provided') } else { return user } diff --git a/app/routes/user.js b/app/routes/user.js index 6dc43cb..645c98e 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -25,13 +25,13 @@ router.patch( // user forgot password request router.post( - '/changepassword', + '/password_reset', userController.forgotPasswordRequest ) // update password router.post( - '/reset/:token', + '/password_reset/:token', userController.updatePassword ) // delete a user diff --git a/config/mongoose.js b/config/mongoose.js index 73bd299..2904b19 100644 --- a/config/mongoose.js +++ b/config/mongoose.js @@ -6,3 +6,9 @@ mongoose.connect(process.env.DATABASE_URL, { useUnifiedTopology: true, useFindAndModify: false }) + .then(() => { + console.log('mongodb connection successful') + }) + .catch((err) => { + console.log('mongodb connection error', err) + }) diff --git a/package.json b/package.json index f98dbbc..710b946 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "jest": { "testEnvironment": "node", "coverageDirectory": "./coverage/", - "collectCoverage": true + "collectCoverage": true, + "testTimeout": 30000 }, "devDependencies": { "codecov": "^3.6.1", From c23131aa5470f4ed0e61d51831e1aa0de93ba18f Mon Sep 17 00:00:00 2001 From: Rupeshiya Date: Tue, 31 Mar 2020 19:34:32 +0530 Subject: [PATCH 09/42] added activate account functionality --- app/controllers/user.js | 28 ++++++++++++++++++++++++++-- app/middleware/activate.js | 18 ++++++++++++++++++ app/models/User.js | 4 ++++ app/routes/auth.js | 1 + app/routes/user.js | 7 +++++++ 5 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 app/middleware/activate.js diff --git a/app/controllers/user.js b/app/controllers/user.js index 695fe46..7a1bb8c 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -7,6 +7,7 @@ module.exports = { try { await user.save() const token = await user.generateAuthToken() + // send email here, to activate the account res.status(201).json({ user: user, token: token }) } catch (error) { console.log(error) @@ -62,9 +63,9 @@ module.exports = { const { password, id } = req.body const { token } = req.params try { - const decodedtoken = jwt.verify(token, process.env.JWT_SECRET) + const decodedToken = jwt.verify(token, process.env.JWT_SECRET) - if (Date.now() <= decodedtoken.expiry) { + if (Date.now() <= decodedToken.expiry) { const user = await User.findById({ _id: id }) @@ -100,5 +101,28 @@ module.exports = { console.log(error) res.status(500).json({ error }) } + }, + + activateAccount: async (req, res, next) => { + try { + const { token } = req.params + const decodedToken = jwt.verify(token, process.env.JWT_SECRET) + const expiryTime = decodedToken.iat + 10800000 // 24 hrs + if (expiryTime <= Date.now()) { + const user = await User.findById(decodedToken._id) + if (!user) { + res.status(404).json({ msg: 'User not found!' }) + } + // if user found activate the account + user.isActivated = true + await user.save() + return res.status(201).json({ user: user }) + } + } catch (Error) { + if (process.env.NODE_ENV !== 'production' && Error) { + console.log('Error in activateAccount ', Error) + } + return res.status(400).json({ Error: Error }) + } } } diff --git a/app/middleware/activate.js b/app/middleware/activate.js new file mode 100644 index 0000000..f24a28c --- /dev/null +++ b/app/middleware/activate.js @@ -0,0 +1,18 @@ +const User = require('../models/User') + +const isActivated = async (req, res, next) => { + const { email } = req.body + try { + const user = await User.findOne({ email: email }) + if (!user) { + next(new Error('No such user is found!')) + } + if (user && !user.isActivated) { + next(new Error('Please activate the account!')) + } + } catch (Error) { + return res.status(400).json({ Error }) + } +} + +module.exports = isActivated diff --git a/app/models/User.js b/app/models/User.js index 1f11616..7344f58 100644 --- a/app/models/User.js +++ b/app/models/User.js @@ -136,6 +136,10 @@ const UserSchema = new mongoose.Schema({ } } }, + isActivated: { + type: Boolean, + default: false + }, createdAt: { type: Date, required: true, diff --git a/app/routes/auth.js b/app/routes/auth.js index 2c78f57..aec4703 100644 --- a/app/routes/auth.js +++ b/app/routes/auth.js @@ -1,6 +1,7 @@ const express = require('express') const router = express.Router() const authController = require('../controllers/auth') +// const isActivated = require('../middleware/activate') // user login router.post( diff --git a/app/routes/user.js b/app/routes/user.js index 645c98e..f027399 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -34,6 +34,13 @@ router.post( '/password_reset/:token', userController.updatePassword ) + +// activate account +router.post( + '/activate/:token', + userController.activateAccount +) + // delete a user router.delete( '/me', From eebeb0348c96f1bbb005f0c10e6b7bd468086a07 Mon Sep 17 00:00:00 2001 From: Rupeshiya Date: Thu, 2 Apr 2020 11:50:17 +0530 Subject: [PATCH 10/42] added API for organisation --- app.js | 2 + app/controllers/organization.js | 123 ++++++++++++++++++++++++++++++++ app/models/Organisation.js | 40 ++++++----- app/models/User.js | 4 ++ app/routes/organisation.js | 58 +++++++++++++++ app/utils/status-codes.js | 13 ++++ app/utils/uploader.js | 45 ++++++++++++ package-lock.json | 120 +++++++++++++++++++++++++++++-- package.json | 1 + 9 files changed, 384 insertions(+), 22 deletions(-) create mode 100644 app/controllers/organization.js create mode 100644 app/routes/organisation.js create mode 100644 app/utils/uploader.js diff --git a/app.js b/app.js index 4c41828..189fc23 100644 --- a/app.js +++ b/app.js @@ -10,6 +10,7 @@ const authRouter = require('./app/routes/auth') const usersRouter = require('./app/routes/user') const postRouter = require('./app/routes/post') const shortUrlRouter = require('./app/routes/urlShortner') +const organizationRouter = require('./app/routes/organisation') const app = express() @@ -27,6 +28,7 @@ app.use('/', indexRouter) app.use('/auth', authRouter) app.use('/user', usersRouter) app.use('/post', postRouter) +app.use('/org', organizationRouter) app.use('/shortUrl', shortUrlRouter) // catch 404 and forward to error handler diff --git a/app/controllers/organization.js b/app/controllers/organization.js new file mode 100644 index 0000000..2d6b1f3 --- /dev/null +++ b/app/controllers/organization.js @@ -0,0 +1,123 @@ +const Organization = require('../models/Organisation') +const handler = require('../utils/response-helper') +const STATUS = require('../utils/status-codes') +const helper = require('../utils/uploader') + +module.exports = { + createOrganization: async (req, res, next) => { + const org = new Organization(req.body) + if (req.file) { + helper.mapToDb(req, org) + } + try { + await org.save() + res.status(STATUS.CREATED).json({ org }) + } catch (error) { + handler.handleError(res, error) + } + }, + + updateOrgDetails: async (req, res, next) => { + const { id } = req.params + const updates = Object.keys(req.body) + const allowedUpdates = ['name', 'description', 'contactInfo', 'logo', 'adminInfo', 'moderatorInfo'] + const isValidOperation = updates.every((update) => { + return allowedUpdates.includes(update) + }) + + if (!isValidOperation) { + return res.status(STATUS.BAD_REQUEST).json({ error: 'invalid update' }) + } + try { + const org = await Organization.findById(id) + updates.forEach(update => { + org[update] = req.body[update] + }) + if (req.file) { + helper.mapToDb(req, org) + } + await org.save() + res.status(STATUS.UPDATED).json({ organization: org }) + } catch (error) { + handler.handleError(res, error) + } + }, + + getOrgDetailsById: async (req, res, next) => { + const { id } = req.params + try { + const orgData = await Organization + .findById(id) + .populate('adminInfo', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) + .populate('moderatorInfo', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) + .sort({ createdAt: -1 }) + .exec() + if (!orgData) { + return res.status(STATUS.NOT_FOUND).json({ error: 'No such organization exists!' }) + } + res.status(STATUS.OK).json({ organization: orgData }) + } catch (error) { + handler.handleError(res, error) + } + }, + + getAllOrg: async (req, res, next) => { + try { + const orgsData = await Organization.find({}) + .populate('adminInfo', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) + .populate('moderatorInfo', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) + .sort({ createdAt: -1 }) + .exec() + if (orgsData.length === 0) { + return res.status(STATUS.OK).json({ msg: 'No organizations listed yet!' }) + } + res.status(STATUS.OK).json({ organizations: orgsData }) + } catch (error) { + handler.handleError(res, error) + } + }, + + deleteOrg: async (req, res, next) => { + const { id } = req.params + try { + const org = await Organization.findByIdAndRemove(id) + if (!org) { + return res.status(STATUS.NOT_FOUND).json({ error: 'No such organization exists!' }) + } + res.status(STATUS.OK).json({ organization: org }) + } catch (error) { + handler.handleError(res, error) + } + }, + + archiveOrg: async (req, res, next) => { + const { id } = req.params + try { + const org = await Organization.findById(id) + if (!org) { + return res.status(STATUS.NOT_FOUND).json({ error: 'No such organization exists!' }) + } + org.isArchived = true + await org.save() + res.status(STATUS.OK).json({ organization: org }) + } catch (error) { + handler.handleError(res, error) + } + }, + + getAllArchived: async (req, res, next) => { + try { + const orgs = await Organization.find({ isArchived: true }) + .populate('adminInfo', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) + .populate('moderatorInfo', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) + .sort({ createdAt: -1 }) + .exec() + if (orgs.length === 0) { + return res.status(STATUS.OK).json({ msg: 'No archived post exists!' }) + } + res.status(STATUS.OK).json({ organization: orgs }) + } catch (error) { + handler.handleError(res, error) + } + } +} diff --git a/app/models/Organisation.js b/app/models/Organisation.js index fa63da8..51eacbf 100644 --- a/app/models/Organisation.js +++ b/app/models/Organisation.js @@ -47,7 +47,7 @@ const orgSchema = new Schema({ } }, logo: { - type: Buffer, + image: Buffer, contentType: String }, logoUrl: { @@ -60,7 +60,7 @@ const orgSchema = new Schema({ } }, contactInfo: { - emailId: { + communityEmail: { type: String, required: true, validate (emailId) { @@ -72,6 +72,19 @@ const orgSchema = new Schema({ } } }, + adminEmail: { + type: String, + trim: true, + required: true, + validate (adminEmail) { + if (validator.isEmpty(adminEmail)) { + throw new Error('Admin emailId is required!') + } + if (!validator.isEmail(adminEmail)) { + throw new Error('Invalid emailId') + } + } + }, website: { type: String, trim: true, @@ -87,6 +100,7 @@ const orgSchema = new Schema({ }, chattingPlatform: [ { + _id: false, link: { type: String } @@ -94,22 +108,16 @@ const orgSchema = new Schema({ ] }, adminInfo: { - type: Object, - required: true, - validate (adminInfo) { - if (validator.isEmpty(adminInfo)) { - throw new Error('Admin info is required!') - } - } + type: mongoose.Schema.Types.ObjectId, + ref: 'User' }, moderatorInfo: { - type: Object, - required: true, - validate (adminInfo) { - if (validator.isEmpty(adminInfo)) { - throw new Error('Admin info is required!') - } - } + type: mongoose.Schema.Types.ObjectId, + ref: 'User' + }, + isArchived: { + type: Boolean, + default: false }, createdAt: { type: Date, diff --git a/app/models/User.js b/app/models/User.js index 1f11616..4fa08af 100644 --- a/app/models/User.js +++ b/app/models/User.js @@ -136,6 +136,10 @@ const UserSchema = new mongoose.Schema({ } } }, + isAdmin: { + type: Boolean, + default: false + }, createdAt: { type: Date, required: true, diff --git a/app/routes/organisation.js b/app/routes/organisation.js new file mode 100644 index 0000000..3c272d9 --- /dev/null +++ b/app/routes/organisation.js @@ -0,0 +1,58 @@ +const express = require('express') +const router = express.Router() +const auth = require('../middleware/auth') +const OrgController = require('../controllers/organization') +const uploader = require('../utils/uploader') + +// CREATE ORG +router.post( + '/', + uploader.upload.single('logo'), + auth, + OrgController.createOrganization +) + +// GET ALL ORG +router.get( + '/all', + auth, + OrgController.getAllOrg +) + +// GET ORG DETAILS BY ID +router.get( + '/:id', + auth, + OrgController.getOrgDetailsById +) + +// GET ARCHIVED ALL ORG +router.get( + '/archive/all', + auth, + OrgController.getAllArchived +) + +// UPDATE ORG DETAILS +router.patch( + '/:id', + uploader.upload.single('logo'), + auth, + OrgController.updateOrgDetails +) + +// DELETE ORG +router.delete( + '/:id', + auth, + OrgController.deleteOrg +) + +// ARCHIVE ORG +router.post( + '/archive/:id', + auth, + OrgController.archiveOrg +) + +module.exports = router diff --git a/app/utils/status-codes.js b/app/utils/status-codes.js index e69de29..9b8b7e6 100644 --- a/app/utils/status-codes.js +++ b/app/utils/status-codes.js @@ -0,0 +1,13 @@ +const HTTP_STATUS = { + OK: 200, + REDIRECT: 302, + BAD_REQUEST: 400, + SERVER_ERROR: 500, + NOT_FOUND: 404, + FORBIDDEN: 403, + CONFLICT: 409, + CREATED: 201, + UPDATED: 204 +} + +module.exports = HTTP_STATUS diff --git a/app/utils/uploader.js b/app/utils/uploader.js new file mode 100644 index 0000000..5e8b5f5 --- /dev/null +++ b/app/utils/uploader.js @@ -0,0 +1,45 @@ +const multer = require('multer') +const fs = require('fs') +const path = require('path') + +const storage = multer.diskStorage({ + destination: (req, file, cb) => { + cb(null, path.join(__dirname, '../uploads/')) + }, + filename: (req, file, cb) => { + console.log('files ', file.originalname) + cb(null, file.originalname) + } +}) + +// type of files allowed +const fileFilter = (req, file, cb) => { + if (file.originalname.match(/\.(jpg|JPG|jpeg|JPEG|png|PNG|gif|GIF)$/)) { + cb(null, true) + } else { + cb(null, false) + } +} + +exports.upload = multer({ + storage: storage, + limits: { + fileSize: 1024 * 1024 * 10 // 10 mb + }, + fileFilter: fileFilter, + upload: (err) => { + if (err instanceof multer.MulterError) { + throw new Error('error in uploading ' + err) + } + } +}) + +exports.mapToDb = (req, db) => { + const img = fs.readFileSync(req.file.path) + const encodedImage = img.toString('base64') + const logo = { + contentType: req.file.mimetype, + image: Buffer.from(encodedImage, 'base64') + } + db.logo = logo +} diff --git a/package-lock.json b/package-lock.json index 391f041..bdcd297 100644 --- a/package-lock.json +++ b/package-lock.json @@ -677,6 +677,11 @@ } } }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -1129,8 +1134,39 @@ "buffer-from": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } }, "bytes": { "version": "3.1.0", @@ -1399,6 +1435,17 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "configstore": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", @@ -1671,6 +1718,38 @@ "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", "dev": true }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "diff-sequences": { "version": "24.9.0", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", @@ -2769,10 +2848,9 @@ } }, "formidable": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", - "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==", - "dev": true + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==" }, "forwarded": { "version": "0.1.2", @@ -5325,6 +5403,21 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, + "multer": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz", + "integrity": "sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, "mute-stream": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", @@ -6866,6 +6959,11 @@ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, "string-length": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", @@ -7284,6 +7382,11 @@ "mime-types": "~2.1.24" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, "uglify-js": { "version": "3.6.8", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.8.tgz", @@ -7733,6 +7836,11 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", diff --git a/package.json b/package.json index 710b946..fd11e92 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "jsonwebtoken": "^8.5.1", "mongoose": "^5.7.7", "morgan": "^1.9.1", + "multer": "^1.4.2", "validator": "^10.11.0" }, "jest": { From 5b4c083eaf391f8bceecea1b649c75bc14c0dcb3 Mon Sep 17 00:00:00 2001 From: Auraofdivinity Date: Fri, 27 Mar 2020 07:14:48 +0530 Subject: [PATCH 11/42] 54 Implements SignUp Email --- app/controllers/user.js | 15 ++++++- app/middleware/email.js | 27 ++++++++++++ app/routes/user.js | 3 ++ package.json | 1 + views/emailTemplate.js | 93 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 app/middleware/email.js create mode 100644 views/emailTemplate.js diff --git a/app/controllers/user.js b/app/controllers/user.js index 695fe46..c562fe0 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -20,7 +20,15 @@ module.exports = { userProfileUpdate: async (req, res, next) => { const updates = Object.keys(req.body) - const allowedUpdates = ['name', 'email', 'password', 'company', 'website', 'location', 'about'] + const allowedUpdates = [ + 'name', + 'email', + 'password', + 'company', + 'website', + 'location', + 'about' + ] const isValidOperation = updates.every((update) => { return allowedUpdates.includes(update) }) @@ -47,7 +55,10 @@ module.exports = { if (!user) { res.status(404).json({ msg: 'User not found!' }) } - const token = jwt.sign({ _id: user._id, expiry: Date.now() + 10800000 }, process.env.JWT_SECRET) + const token = jwt.sign( + { _id: user._id, expiry: Date.now() + 10800000 }, + process.env.JWT_SECRET + ) await user.save() return res.status(200).json({ success: true, token }) } catch (error) { diff --git a/app/middleware/email.js b/app/middleware/email.js new file mode 100644 index 0000000..7bd6442 --- /dev/null +++ b/app/middleware/email.js @@ -0,0 +1,27 @@ +const emailTemplate = require('../../views/emailTemplate') +const sendgridMail = require('@sendgrid/mail') + +sendgridMail.setApiKey(process.env.SENDGRID_API_KEY) + +const email = (req, res, next) => { + const message = { + to: req.body.email, + from: 'services@codeuino.com', + subject: `Welcome to Donut ${req.body.name.firstName}`, + html: emailTemplate + } + sendgridMail.send(message).then( + () => { + next() + }, + (error) => { + res.status(error.code).send({ + error: process.env.SENDGRID_API_KEY + ? error.response.body + : 'Setup SENDGRID_API_KEY environment variable' + }) + } + ) +} + +module.exports = email diff --git a/app/routes/user.js b/app/routes/user.js index 645c98e..791e9b1 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -2,10 +2,12 @@ const express = require('express') const router = express.Router() const userController = require('../controllers/user') const auth = require('../middleware/auth') +const email = require('../middleware/email') // create a user router.post( '/', + email, userController.createUser ) @@ -34,6 +36,7 @@ router.post( '/password_reset/:token', userController.updatePassword ) + // delete a user router.delete( '/me', diff --git a/package.json b/package.json index 710b946..b5d4c22 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "test": "env-cmd -f .env.test jest --detectOpenHandles && codecov -t 2b12ad97-07e0-45b2-a569-8aa2fd3e8c54" }, "dependencies": { + "@sendgrid/mail": "^7.0.0", "bcrypt": "^3.0.6", "body-parser": "^1.19.0", "cookie-parser": "~1.4.4", diff --git a/views/emailTemplate.js b/views/emailTemplate.js new file mode 100644 index 0000000..3be2175 --- /dev/null +++ b/views/emailTemplate.js @@ -0,0 +1,93 @@ +module.exports = ` + + + + + + + + + + + + + + + + + + + +
+ Codeuino org image +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + +
+
+ Welcome to Donut +
+
+
+
+

+
+
+
+

+ Your registration is successful! We warmly welcome you to the donut platform!

+ Donut is an open-source, feature-rich, highly flexible and privacy-friendly, social networking platform built for community-oriented collaboration in a customized way. It has been built on the Node.js framework allowing an essential impetus to provide custom and friendly rich widgets and an expansive library of modules to make communication and collaboration easy and successful. With a powerful module system, you can customize this platform by using third party tools, writing your own or integrating other software.

+ Hope you enjoy your stay at Donut!
+
+
+   +
+
+ +
+
+
+ + + + +
+
+
+ + + + +` From 7c2c5c4bd6250ec9c2d6a06a36ffc8d49250339d Mon Sep 17 00:00:00 2001 From: Rupeshiya Date: Sat, 11 Apr 2020 23:01:21 +0530 Subject: [PATCH 12/42] functionality to trigger maintenance mode --- app/controllers/organization.js | 28 +++++++++++++++------------- app/controllers/user.js | 28 +++++++++++++--------------- app/models/Organisation.js | 26 ++++++++++++++++++-------- app/routes/organisation.js | 14 +++++++------- 4 files changed, 53 insertions(+), 43 deletions(-) diff --git a/app/controllers/organization.js b/app/controllers/organization.js index accab03..b3dc0ae 100644 --- a/app/controllers/organization.js +++ b/app/controllers/organization.js @@ -89,19 +89,21 @@ module.exports = { } }, - getAllArchived: async (req, res, next) => { - try { - const orgs = await Organization.find({ isArchived: true }) - .populate('adminInfo', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) - .populate('moderatorInfo', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) - .sort({ createdAt: -1 }) - .exec() - if (orgs.length === 0) { - return res.status(STATUS.OK).json({ msg: 'No archived post exists!' }) - } - res.status(STATUS.OK).json({ organization: orgs }) - } catch (error) { - HANDLER.handleError(res, error) + triggerMaintenance: async (req, res, next) => { + const { orgId } = req.body + const organization = await Organization.findById(orgId) + const adminIds = organization.adminInfo.map(info => info.adminId) + const isAdmin = adminIds.indexOf(req.user.id) + if (!organization) { + return res.status(STATUS.NOT_FOUND).json({ error: 'No such organization exists!' }) + } + if (isAdmin) { + // toggle maintenance mode + organization.isMaintenance = !organization.isMaintenance + await organization.save() + res.status(STATUS.OK).json({ msg: 'Organization is under the maintenance!!' }) + } else { + res.status(STATUS.BAD_REQUEST).json({ msg: 'You don\'t have access to triggerMaintenance!' }) } } } diff --git a/app/controllers/user.js b/app/controllers/user.js index 7ad71fe..06d9200 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -1,5 +1,6 @@ const User = require('../models/User') const jwt = require('jsonwebtoken') +const STATUS = require('../utils/status-codes') module.exports = { createUser: async (req, res, next) => { @@ -8,10 +9,10 @@ module.exports = { await user.save() const token = await user.generateAuthToken() // send email here, to activate the account - res.status(201).json({ user: user, token: token }) + res.status(STATUS.CREATED).json({ user: user, token: token }) } catch (error) { console.log(error) - res.status(400).json({ error: error }) + res.status(STATUS.NOT_FOUND).json({ error: error }) } }, @@ -35,7 +36,7 @@ module.exports = { }) if (!isValidOperation) { - return res.status(400).json({ error: 'invalid update' }) + return res.status(STATUS.BAD_REQUEST).json({ error: 'invalid update' }) } try { @@ -43,9 +44,9 @@ module.exports = { req.user[update] = req.body[update] }) await req.user.save() - res.status(200).json({ data: req.user }) + res.status(STATUS.UPDATED).json({ data: req.user }) } catch (error) { - res.status(400).json({ error }) + res.status(STATUS.BAD_REQUEST).json({ error }) } }, @@ -54,19 +55,16 @@ module.exports = { try { const user = await User.findOne({ email: email }) if (!user) { - res.status(404).json({ msg: 'User not found!' }) + res.status(STATUS.NOT_FOUND).json({ msg: 'User not found!' }) } - const token = jwt.sign( - { _id: user._id, expiry: Date.now() + 10800000 }, - process.env.JWT_SECRET - ) + const token = jwt.sign({ _id: user._id, expiry: Date.now() + 10800000 }, process.env.JWT_SECRET) await user.save() - return res.status(200).json({ success: true, token }) + return res.status(STATUS.OK).json({ success: true, token }) } catch (error) { if (process.env.NODE_ENV !== 'production' && error) { console.log('Error in forgotPasswordRequest ', error) } - res.status(400).json({ error }) + res.status(STATUS.BAD_REQUEST).json({ error }) } }, @@ -85,18 +83,18 @@ module.exports = { } user.password = password await user.save() - return res.status(200).json({ updated: true }) + return res.status(STATUS.OK).json({ updated: true }) } else { if (process.env.NODE_ENV !== 'production') { console.log('token expired') } - res.status(400).json({ error: 'Token expired' }) + res.status(STATUS.BAD_REQUEST).json({ error: 'Token expired' }) } } catch (error) { if (process.env.NODE_ENV !== 'production' && error) { console.log('Something went wrong ', error) } - res.status(400).json({ error }) + res.status(STATUS.BAD_REQUEST).json({ error }) } }, diff --git a/app/models/Organisation.js b/app/models/Organisation.js index 6f2f5df..4117e6d 100644 --- a/app/models/Organisation.js +++ b/app/models/Organisation.js @@ -98,18 +98,28 @@ const orgSchema = new Schema({ } ] }, - adminInfo: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User' - }, - moderatorInfo: { - type: mongoose.Schema.Types.ObjectId, - ref: 'User' - }, + adminInfo: [{ + _id: false, + adminId: { + type: Schema.Types.ObjectId, + ref: 'User' + } + }], + moderatorInfo: [{ + _id: false, + adminId: { + type: Schema.Types.ObjectId, + ref: 'User' + } + }], isArchived: { type: Boolean, default: false }, + isMaintenance: { + type: Boolean, + default: false + }, createdAt: { type: Date, required: true, diff --git a/app/routes/organisation.js b/app/routes/organisation.js index ccad7da..f9fccd7 100644 --- a/app/routes/organisation.js +++ b/app/routes/organisation.js @@ -19,13 +19,6 @@ router.get( OrgController.getOrgDetailsById ) -// GET ARCHIVED ALL ORG -router.get( - '/archive/all', - auth, - OrgController.getAllArchived -) - // UPDATE ORG DETAILS router.patch( '/:id', @@ -48,4 +41,11 @@ router.patch( OrgController.archiveOrg ) +// TRIGGER MAINTENANCE MODE +router.patch( + '/maintenance', + auth, + OrgController.triggerMaintenance +) + module.exports = router From 4b0e34df1b5260f5db350938070c6abeb68736c5 Mon Sep 17 00:00:00 2001 From: mrkhst Date: Sat, 11 Apr 2020 01:53:27 +0530 Subject: [PATCH 13/42] Adds all the post API's requested changes changes requested changes requested code changes Unit testing Post Module changes update post test added --- app/controllers/post.js | 168 +++++++++++++++++--- app/models/Post.js | 26 ++-- app/routes/post.js | 44 ++++-- app/utils/console-helper.js | 6 + test/post.test.js | 299 ++++++++++++++++++++++++++++++++++++ 5 files changed, 495 insertions(+), 48 deletions(-) create mode 100644 app/utils/console-helper.js create mode 100644 test/post.test.js diff --git a/app/controllers/post.js b/app/controllers/post.js index 36c1ff2..9c0d5ce 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -1,32 +1,154 @@ -const userModel = require('../models/User') -const bcrypt = require('bcrypt') -const jwt = require('jsonwebtoken') +const PostModel = require('../models/Post') +const HANDLER = require('../utils/response-helper') +const STATUS = require('../utils/status-codes') +const imgUploadHelper = require('../utils/uploader') +const consoleHelper = require('../utils/console-helper') module.exports = { - create: function (req, res, next) { - userModel.create({ name: req.body.name, email: req.body.email, password: req.body.password }, function (err, result) { - if (err) { - next(err) - } else { - res.json({ status: 'success', message: 'User added successfully!!!', data: null }) + create: async (req, res, next) => { + const post = new PostModel(req.body) + const userId = req.user.id.toString() + post.userId = userId + if (req.file) { + imgUploadHelper.mapToDb(req, post) + } + try { + await post.save() + res.status(STATUS.CREATED).json({ post }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + delete: async (req, res, next) => { + const { id } = req.params + const userId = req.user.id.toString() + try { + const post = await PostModel.findById(id) + if (!post) { + return res.status(STATUS.NOT_FOUND).json({ message: 'No post exists' }) + } + if (JSON.stringify(userId) !== JSON.stringify(post.userId)) { + return res.status(STATUS.FORBIDDEN).json({ message: 'Bad delete request' }) } + await PostModel.findByIdAndRemove(id) + res.status(STATUS.OK).json({ post: post, message: 'Deleted the post' }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + updatePost: async (req, res, next) => { + const { id } = req.params + consoleHelper(id) + const updates = Object.keys(req.body) + const allowedUpdates = ['content', 'imgUrl'] + const userId = req.user.id.toString() + const isValidOperation = updates.every((update) => { + return allowedUpdates.includes(update) }) + + if (!isValidOperation) { + return res.status(STATUS.BAD_REQUEST).json({ message: 'Invalid Update' }) + } + try { + const post = await PostModel.findById(id) + if (!post) { + return res.status(STATUS.BAD_REQUEST).json({ message: 'No post exists' }) + } + if (JSON.stringify(userId) !== JSON.stringify(post.userId)) { + return res.status(STATUS.FORBIDDEN).json({ message: 'Bad update request' }) + } + consoleHelper(post) + updates.forEach(update => { + post[update] = req.body[update] + }) + if (req.file) { + imgUploadHelper.mapToDb(req, post) + } + await post.save() + res.status(STATUS.UPDATED).json({ post: post }) + } catch (error) { + HANDLER.handleError(res, error) + } }, - authenticate: function (req, res, next) { - userModel.findOne({ email: req.body.email }, function (err, userInfo) { - if (err) { - next(err) - } else { - if (bcrypt.compareSync(req.body.password, userInfo.password)) { - const token = jwt.sign({ id: userInfo._id }, req.app.get('secretKey'), { expiresIn: '1h' }) - res.json({ status: 'success', message: 'user found!!!', data: { user: userInfo, token: token } }) - } else { - res.json({ status: 'error', message: 'Invalid email/password!!!', data: null }) - } + getPostById: async (req, res, next) => { + const { id } = req.params + try { + const post = await PostModel.findById(id) + if (!post) { + return res.status(STATUS.NOT_FOUND).json({ error: 'Post not found' }) } - }) + res.status(STATUS.OK).json({ post: post }) + } catch (error) { + HANDLER.handleError(res, error) + } }, - test: function (req, res, next) { - res.json({ success: 'ulllu' }) + getAllPost: async (req, res, next) => { + try { + const posts = await PostModel.find({}) + if (!posts.length) { + return res.status(STATUS.NOT_FOUND).json({ message: 'No posts found' }) + } + res.status(STATUS.OK).json({ posts: posts }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + upvote: async (req, res, next) => { + const { id } = req.params + const userId = req.user.id.toString() + try { + const post = await PostModel.findById(id) + if (!post) { + return res.status(STATUS.NOT_FOUND).json({ error: 'No post found' }) + } + // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT + post.votes.upVotes.users.filter(user => { + if (JSON.stringify(user) === JSON.stringify(userId)) { + return res.status(STATUS.BAD_REQUEST).json({ error: 'Bad request' }) + } + }) + // CHECKS IF THE USER HAS ALREADY DOWNVOTED THE COMMENT + post.votes.downVotes.users.filter(user => { + if (JSON.stringify(user) === JSON.stringify(userId)) { + post.votes.downVotes.count = post.votes.downVotes.count - 1 + post.votes.downVotes.users.remove(user) + } + }) + post.votes.upVotes.count = post.votes.upVotes.count + 1 + post.votes.upVotes.users.push(userId) + await post.save() + res.status(STATUS.OK).json({ post: post, message: 'Success' }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + downvote: async (req, res, next) => { + const { id } = req.params + const userId = req.user.id.toString() + try { + const post = await PostModel.findById(id) + if (!post) { + return res.status(STATUS.NOT_FOUND).json({ error: 'No post found' }) + } + // CHECKS IF THE USER HAS ALREADY DOWNVOTED THE COMMENT + post.votes.downVotes.users.filter(user => { + if (JSON.stringify(user) === JSON.stringify(userId)) { + return res.status(STATUS.BAD_REQUEST).json({ error: 'Bad request' }) + } + }) + // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT + post.votes.upVotes.users.filter(user => { + if (JSON.stringify(user) === JSON.stringify(userId)) { + post.votes.upVotes.count = post.votes.upVotes.count - 1 + post.votes.upVotes.users.remove(user) + } + }) + post.votes.downVotes.count = post.votes.downVotes.count + 1 + post.votes.downVotes.users.push(userId) + await post.save() + res.status(STATUS.OK).json({ post: post, message: 'Success' }) + } catch (error) { + HANDLER.handleError(res, error) + } } } diff --git a/app/models/Post.js b/app/models/Post.js index ae528b0..572248e 100644 --- a/app/models/Post.js +++ b/app/models/Post.js @@ -13,6 +13,10 @@ const PostSchema = new Schema({ } } }, + userId: { + type: Schema.Types.ObjectId, + ref: 'User' + }, image: { data: Buffer, contentType: String @@ -32,26 +36,20 @@ const PostSchema = new Schema({ type: Number, default: 0 }, - users: { - user: { - type: Schema.Types.ObjectId, - ref: 'User', - required: true - } - } + users: [{ + type: Schema.Types.ObjectId, + ref: 'User' + }] }, downVotes: { count: { type: Number, default: 0 }, - users: { - user: { - type: Schema.Types.ObjectId, - ref: 'User', - required: true - } - } + users: [{ + type: Schema.Types.ObjectId, + ref: 'User' + }] } }, comments: { diff --git a/app/routes/post.js b/app/routes/post.js index 1d6d72a..5f46b42 100644 --- a/app/routes/post.js +++ b/app/routes/post.js @@ -1,35 +1,57 @@ +require('../../config/mongoose') const express = require('express') const router = express.Router() const userController = require('../controllers/post') +const uploader = require('../utils/uploader') +const auth = require('../middleware/auth') // CREATE A POST router.post( '/', + auth, + uploader.upload.single('image'), userController.create ) -// GET ALL POSTS OF A USER +// GET ALL POSTS router.get( - '/', - userController.authenticate -) - -// GET PARTICULAR POST OF A USER -router.get( - '/:id', - userController.test + '/all_posts', + auth, + userController.getAllPost ) // UPDATE A TASK router.patch( '/:id', - userController.test + auth, + uploader.upload.single('image'), + userController.updatePost ) // DELETE A TASK router.delete( '/:id', - userController.test + auth, + userController.delete +) + +// GET TASK BY ID +router.get( + '/:id', + auth, + userController.getPostById +) + +router.put( + '/upvote/:id', + auth, + userController.upvote +) + +router.put( + '/downvote/:id', + auth, + userController.downvote ) module.exports = router diff --git a/app/utils/console-helper.js b/app/utils/console-helper.js new file mode 100644 index 0000000..003b207 --- /dev/null +++ b/app/utils/console-helper.js @@ -0,0 +1,6 @@ +const ConsoleHelper = (data) => { + if (process.env.NODE_ENV === 'production') return + console.log(data) +} + +module.exports = ConsoleHelper diff --git a/test/post.test.js b/test/post.test.js new file mode 100644 index 0000000..f5b4841 --- /dev/null +++ b/test/post.test.js @@ -0,0 +1,299 @@ +const app = require('../app') +const mongoose = require('mongoose') +const jwt = require('jsonwebtoken') +const request = require('supertest') +const Post = require('../app/models/Post') +const User = require('../app/models/User') + +const testUserId = new mongoose.Types.ObjectId() +let token = '' +const demoPost = { + content: 'test post content', + userId: testUserId, + votes: { + upVotes: { + count: 0, + users: [] + }, + downVotes: { + count: 0, + users: [] + } + } +} + +const updatePost = { + content: 'updated post content' +} + +const upvotePost = { + content: 'test post content', + userId: testUserId, + votes: { + upVotes: { + count: 1, + users: [ + testUserId + ] + }, + downVotes: { + count: 0, + users: [] + } + } +} + +const downvotePost = { + content: 'test post content', + userId: testUserId, + votes: { + upVotes: { + count: 0, + users: [] + }, + downVotes: { + count: 1, + users: [ + testUserId + ] + } + } +} + +const testPostId = new mongoose.Types.ObjectId() +const testPost = { + _id: testPostId, + ...demoPost +} + +const demoUser = { + name: { + firstName: 'test', + lastName: 'test' + }, + email: 'test3@mailinator.com', + phone: '1234567890', + password: 'abc12345', + info: { + about: { + shortDescription: 'this is short description', + longDescription: 'this is a very long description', + website: 'https://www.google.com', + designation: 'software engg', + skills: [ + 'c++', + 'java' + ], + education: [{ + school: { + schoolName: 'firstSchoolName', + year: '2017-2021' + } + }, + { + school: { + schoolName: 'secondSchoolName', + year: '2007-2014' + } + } + ], + location: 'location' + } + } +} + +const testUser = { + _id: testUserId, + ...demoUser, + email: 'test@mailinator.com', + phone: '1234567891', + tokens: [{ + token: jwt.sign({ + _id: testUserId + }, process.env.JWT_SECRET) + }] +} +let server +/** + * This will pe performed once at the beginning of the test + */ +beforeAll(async (done) => { + await Post.deleteMany() + await new User(testUser).save() + server = app.listen(4000, () => { + global.agent = request.agent(server) + done() + }) + const response = await request(app) + .post('/auth/login') + .send({ + email: testUser.email, + password: testUser.password + }) + token = response.body.token + done() +}) + +/** + * This deletes all the existing user in database, + * and creates a new user in database with the provided details. + */ +beforeEach(async () => { + await Post.deleteMany() + await new Post(testPost).save() +}) + +/** + * Testing post creation + */ +test('Should create new post', async (done) => { + const response = await request(app) + .post('/post') + .set('Authorization', `Bearer ${token}`) + .send(demoPost) + .expect(201) + + // Assert that db was changed + const post = await Post.findById(response.body.post._id) + expect(post).not.toBeNull() + + const userId = response.body.post.userId + + // Assertions about the response + expect(response.body).toMatchObject({ + post: { + content: demoPost.content, + userId: `${userId}`, + votes: { + upVotes: { + count: demoPost.votes.upVotes.count, + users: demoPost.votes.upVotes.users + }, + downVotes: { + count: demoPost.votes.downVotes.count, + users: demoPost.votes.downVotes.users + } + } + } + }) + done() +}) + +/** + * Testing post deletion + */ + +test('Should delete post', async (done) => { + await request(app) + .delete(`/post/${testPostId}`) + .set('Authorization', `Bearer ${token}`) + .send() + .expect(200) + + // Assert that post was deleted + const post = await Post.findById(testPostId) + expect(post).toBeNull() + done() +}) + +/** + * Testing GET post + */ + +test('Should get post for user', async (done) => { + await request(app) + .get(`/post/${testPostId}`) + .set('Authorization', `Bearer ${token}`) + .send() + .expect(200) + done() +}) + +/** + * Testing upvote post + */ + +test('Should upvote the post', async (done) => { + const response = await request(app) + .put(`/post/upvote/${testPostId}`) + .set('Authorization', `Bearer ${token}`) + .send() + .expect(200) + + const userId = response.body.post.userId + + expect(response.body).toMatchObject({ + post: { + content: upvotePost.content, + userId: `${userId}`, + votes: { + upVotes: { + count: upvotePost.votes.upVotes.count, + users: response.body.post.votes.upVotes.users + }, + downVotes: { + count: upvotePost.votes.downVotes.count, + users: upvotePost.votes.downVotes.users + } + } + } + }) + done() +}) + +/** + * Testing downvote post + */ + +test('Should downvote the post', async (done) => { + const response = await request(app) + .put(`/post/downvote/${testPostId}`) + .set('Authorization', `Bearer ${token}`) + .send() + .expect(200) + + const userId = response.body.post.userId + + expect(response.body).toMatchObject({ + post: { + content: downvotePost.content, + userId: `${userId}`, + votes: { + upVotes: { + count: downvotePost.votes.upVotes.count, + users: downvotePost.votes.upVotes.users + }, + downVotes: { + count: downvotePost.votes.downVotes.count, + users: response.body.post.votes.downVotes.users + } + } + } + }) + done() +}) + +/** + * Testing post update + */ +test('Should update the Post data', async (done) => { + await request(app) + .patch(`/post/${testPostId}`) + .set('Authorization', `Bearer ${token}`) + .send(updatePost) + .expect(204) + done() +}) +/** + * TODO: FIX ERROR + * This is a temporary fix to issue: + * Jest has detected the following 1 open handle potentially keeping Jest from exiting + */ +afterAll(async () => { + // close server + await server.close() + // delete all the posts post testing + await Post.deleteMany() + // Closing the DB connection allows Jest to exit successfully. + await mongoose.connection.close() +}) From 0c3bc48355163ccccb915c7cdb0eaf6b89e82f90 Mon Sep 17 00:00:00 2001 From: mrkhst Date: Sat, 11 Apr 2020 02:27:41 +0530 Subject: [PATCH 14/42] Adds all comment API's requested changes changes requested comment added --- app.js | 2 + app/controllers/comment.js | 137 +++++++++++++++ app/models/Comment.js | 32 +--- app/routes/comment.js | 42 +++++ app/utils/console-helper.js | 6 + test/comment.test.js | 327 ++++++++++++++++++++++++++++++++++++ 6 files changed, 522 insertions(+), 24 deletions(-) create mode 100644 app/controllers/comment.js create mode 100644 app/routes/comment.js create mode 100644 app/utils/console-helper.js create mode 100644 test/comment.test.js diff --git a/app.js b/app.js index 189fc23..c7b6ebe 100644 --- a/app.js +++ b/app.js @@ -11,6 +11,7 @@ const usersRouter = require('./app/routes/user') const postRouter = require('./app/routes/post') const shortUrlRouter = require('./app/routes/urlShortner') const organizationRouter = require('./app/routes/organisation') +const commentRouter = require('./app/routes/comment') const app = express() @@ -30,6 +31,7 @@ app.use('/user', usersRouter) app.use('/post', postRouter) app.use('/org', organizationRouter) app.use('/shortUrl', shortUrlRouter) +app.use('/comment', commentRouter) // catch 404 and forward to error handler app.use(function (req, res, next) { diff --git a/app/controllers/comment.js b/app/controllers/comment.js new file mode 100644 index 0000000..658c6a4 --- /dev/null +++ b/app/controllers/comment.js @@ -0,0 +1,137 @@ +const HANDLER = require('../utils/response-helper') +const STATUS = require('../utils/status-codes') +const CommentModel = require('../models/Comment') +const consoleHelper = require('../utils/console-helper') + +module.exports = { + comment: async (req, res, next) => { + const comment = new CommentModel(req.body) + const userId = req.user.id.toString() + comment.userId = userId + try { + await comment.save() + res.status(STATUS.CREATED).json({ comment: comment }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + delete: async (req, res, next) => { + const { id } = req.params + const userId = req.user.id.toString() + consoleHelper(id) + try { + const comment = await CommentModel.findById(id) + if (!comment) { + return res.status(STATUS.NOT_FOUND).json({ error: 'No comment exixts' }) + } + if (JSON.stringify(comment.userId) !== JSON.stringify(userId)) { + return res.status(STATUS.FORBIDDEN).json({ message: 'Bad delete request' }) + } + await CommentModel.findByIdAndRemove(id) + res.status(STATUS.OK).json({ comment: comment }) + } catch (error) { + consoleHelper(error) + HANDLER.handleError(res, error) + } + }, + update: async (req, res, next) => { + const { id } = req.params + const userId = req.user.id.toString() + const updates = Object.keys(req.body) + const valid = ['content'] + let isValidUpdate = true + if (JSON.stringify(updates) !== JSON.stringify(valid)) { + isValidUpdate = false + } + if (!isValidUpdate) { + return res.status(STATUS.BAD_REQUEST).json({ error: 'Wrong Update Request' }) + } + try { + const comment = await CommentModel.findById(id) + if (!comment) { + return res.status(STATUS.NOT_FOUND).json({ error: 'No comment exixts' }) + } + if (JSON.stringify(comment.userId) !== JSON.stringify(userId)) { + return res.status(STATUS.BAD_REQUEST).json({ error: 'Wrong update' }) + } + updates.forEach(update => { + comment[update] = req.body[update] + }) + await comment.save() + consoleHelper(comment) + res.status(STATUS.UPDATED).json({ comment: comment }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + getCommentByPost: async (req, res, next) => { + const { id } = req.params + try { + const comments = await CommentModel.find({ postId: id }).populate('userId').exec() + if (!comments) { + return res.status(STATUS.NOT_FOUND).json({ error: 'No such post' }) + } + res.status(STATUS.OK).json({ comments: comments, success: true }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + upvote: async (req, res, next) => { + const { id } = req.params + const userId = req.user.id.toString() + try { + const comment = await CommentModel.findById(id) + if (!comment) { + return res.status(STATUS.NOT_FOUND).json({ error: 'No comment found' }) + } + // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT + comment.votes.upVotes.user.filter(user => { + if (JSON.stringify(user) === JSON.stringify(userId)) { + return res.status(STATUS.BAD_REQUEST).json({ error: 'Bad request' }) + } + }) + // CHECKS IF THE USER HAS ALREADY DOWNVOTED THE COMMENT + comment.votes.downVotes.user.filter(user => { + if (JSON.stringify(user) === JSON.stringify(userId)) { + comment.votes.downVotes.count = comment.votes.downVotes.count - 1 + comment.votes.downVotes.user.remove(user) + } + }) + comment.votes.upVotes.count = comment.votes.upVotes.count + 1 + comment.votes.upVotes.user.push(userId) + await comment.save() + res.status(STATUS.OK).json({ comment: comment, message: 'Success' }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + downvote: async (req, res, next) => { + const { id } = req.params + const userId = req.user.id.toString() + try { + const comment = await CommentModel.findById(id) + if (!comment) { + return res.status(STATUS.NOT_FOUND).json({ error: 'No comment found' }) + } + // CHECKS IF THE USER HAS ALREADY DOWNVOTED THE COMMENT + comment.votes.downVotes.user.filter(user => { + if (JSON.stringify(user) === JSON.stringify(userId)) { + return res.status(STATUS.BAD_REQUEST).json({ error: 'Bad request' }) + } + }) + // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT + comment.votes.upVotes.user.filter(user => { + if (JSON.stringify(user) === JSON.stringify(userId)) { + comment.votes.upVotes.count = comment.votes.upVotes.count - 1 + comment.votes.upVotes.user.remove(user) + } + }) + comment.votes.downVotes.count = comment.votes.downVotes.count + 1 + comment.votes.downVotes.user.push(userId) + await comment.save() + res.status(STATUS.OK).json({ comment: comment, message: 'Success' }) + } catch (error) { + HANDLER.handleError(res, error) + } + } +} diff --git a/app/models/Comment.js b/app/models/Comment.js index 0398a53..3dda599 100644 --- a/app/models/Comment.js +++ b/app/models/Comment.js @@ -5,23 +5,11 @@ const Schema = mongoose.Schema const commentSchema = new Schema({ userId: { type: Schema.Types.ObjectId, - required: true, - ref: 'User', - validate (userId) { - if (validator.isEmpty(userId)) { - throw new Error('UserId is required!') - } - } + ref: 'User' }, postId: { type: Schema.Types.ObjectId, - required: true, - ref: 'Post', - validate (userId) { - if (validator.isEmpty(userId)) { - throw new Error('PostID is required!') - } - } + ref: 'Post' }, content: { type: String, @@ -39,24 +27,20 @@ const commentSchema = new Schema({ type: Number, default: 0 }, - user: { - user_id: { - type: Schema.Types.ObjectId, - required: true, - ref: 'User' - } - } + user: [{ + type: Schema.Types.ObjectId, + ref: 'User' + }] }, downVotes: { count: { type: Number, default: 0 }, - user_id: { + user: [{ type: Schema.Types.ObjectId, - required: true, ref: 'User' - } + }] } }, createdAt: { diff --git a/app/routes/comment.js b/app/routes/comment.js new file mode 100644 index 0000000..8144088 --- /dev/null +++ b/app/routes/comment.js @@ -0,0 +1,42 @@ +const express = require('express') +const router = express.Router() +const auth = require('../middleware/auth') +const commentController = require('../controllers/comment') + +router.post( + '/', + auth, + commentController.comment +) + +router.delete( + '/:id', + auth, + commentController.delete +) + +router.patch( + '/:id', + auth, + commentController.update +) + +router.get( + '/:id', + auth, + commentController.getCommentByPost +) + +router.put( + '/upvote/:id', + auth, + commentController.upvote +) + +router.put( + '/downvote/:id', + auth, + commentController.downvote +) + +module.exports = router diff --git a/app/utils/console-helper.js b/app/utils/console-helper.js new file mode 100644 index 0000000..003b207 --- /dev/null +++ b/app/utils/console-helper.js @@ -0,0 +1,6 @@ +const ConsoleHelper = (data) => { + if (process.env.NODE_ENV === 'production') return + console.log(data) +} + +module.exports = ConsoleHelper diff --git a/test/comment.test.js b/test/comment.test.js new file mode 100644 index 0000000..aed8f82 --- /dev/null +++ b/test/comment.test.js @@ -0,0 +1,327 @@ +const app = require('../app') +const mongoose = require('mongoose') +const jwt = require('jsonwebtoken') +const request = require('supertest') +const Post = require('../app/models/Post') +const User = require('../app/models/User') +const Comment = require('../app/models/Comment') + +const testUserId = new mongoose.Types.ObjectId() +const testPostId = new mongoose.Types.ObjectId() +const testCommentId = new mongoose.Types.ObjectId() +let token = '' + +const demoComment = { + content: 'test comment content', + userId: testUserId, + postId: testPostId, + votes: { + upVotes: { + count: 0, + users: [] + }, + downVotes: { + count: 0, + users: [] + } + } +} + +const demoPost = { + _id: testPostId, + content: 'test post content', + userId: testUserId, + votes: { + upVotes: { + count: 0, + users: [] + }, + downVotes: { + count: 0, + users: [] + } + } +} + +const updateComment = { + content: 'updated comment content' +} + +const upvoteComment = { + content: 'test comment content', + userId: testUserId, + votes: { + upVotes: { + count: 1, + users: [ + testUserId + ] + }, + downVotes: { + count: 0, + users: [] + } + } +} + +const downvoteComment = { + content: 'test comment content', + userId: testUserId, + votes: { + upVotes: { + count: 0, + users: [] + }, + downVotes: { + count: 1, + users: [ + testUserId + ] + } + } +} + +const testComment = { + _id: testCommentId, + ...demoComment +} + +const demoUser = { + name: { + firstName: 'test', + lastName: 'test' + }, + email: 'test3@mailinator.com', + phone: '1234567890', + password: 'abc12345', + info: { + about: { + shortDescription: 'this is short description', + longDescription: 'this is a very long description', + website: 'https://www.google.com', + designation: 'software engg', + skills: [ + 'c++', + 'java' + ], + education: [{ + school: { + schoolName: 'firstSchoolName', + year: '2017-2021' + } + }, + { + school: { + schoolName: 'secondSchoolName', + year: '2007-2014' + } + } + ], + location: 'location' + } + } +} + +const testUser = { + _id: testUserId, + ...demoUser, + email: 'test@mailinator.com', + phone: '1234567891', + tokens: [{ + token: jwt.sign({ + _id: testUserId + }, process.env.JWT_SECRET) + }] +} +let server +/** + * This will pe performed once at the beginning of the test + */ +beforeAll(async (done) => { + await Comment.deleteMany() + await new User(testUser).save() + await new Post(demoPost).save() + server = app.listen(4000, () => { + global.agent = request.agent(server) + done() + }) + const response = await request(app) + .post('/auth/login') + .send({ + email: testUser.email, + password: testUser.password + }) + token = response.body.token + done() +}) + +/** + * This deletes all the existing user in database, + * and creates a new user in database with the provided details. + */ +beforeEach(async () => { + await Comment.deleteMany() + await new Comment(testComment).save() +}) + +/** + * Testing post creation + */ +test('Should create new comment', async (done) => { + const response = await request(app) + .post('/comment') + .set('Authorization', `Bearer ${token}`) + .send(demoComment) + .expect(201) + + // Assert that db was changed + const comment = await Comment.findById(response.body.comment._id) + expect(comment).not.toBeNull() + + const userId = response.body.comment.userId + const postId = response.body.comment.postId + + // Assertions about the response + expect(response.body).toMatchObject({ + comment: { + content: demoComment.content, + userId: `${userId}`, + postId: `${postId}`, + votes: { + upVotes: { + count: demoComment.votes.upVotes.count, + users: demoComment.votes.upVotes.users + }, + downVotes: { + count: demoComment.votes.downVotes.count, + users: demoComment.votes.downVotes.users + } + } + } + }) + done() +}) + +/** + * Testing post update + */ +test('Should update the Comment data', async (done) => { + await request(app) + .patch(`/comment/${testCommentId}`) + .set('Authorization', `Bearer ${token}`) + .send(updateComment) + .expect(204) + done() +}) + +/** + * Testing post deletion + */ + +test('Should delete comment', async (done) => { + await request(app) + .delete(`/comment/${testCommentId}`) + .set('Authorization', `Bearer ${token}`) + .send() + .expect(200) + + // Assert that post was deleted + const comment = await Comment.findById(testPostId) + expect(comment).toBeNull() + done() +}) + +/** + * Testing GET comment for Post + */ + +test('Should get comment for post', async (done) => { + await request(app) + .get(`/comment/${testPostId}`) + .set('Authorization', `Bearer ${token}`) + .send() + .expect(200) + done() +}) + +/** + * Testing upvote post + */ + +test('Should upvote the comment', async (done) => { + const response = await request(app) + .put(`/comment/upvote/${testCommentId}`) + .set('Authorization', `Bearer ${token}`) + .send() + .expect(200) + + const userId = response.body.comment.userId + const postId = response.body.comment.postId + + expect(response.body).toMatchObject({ + comment: { + content: upvoteComment.content, + userId: `${userId}`, + postId: `${postId}`, + votes: { + upVotes: { + count: upvoteComment.votes.upVotes.count, + users: response.body.comment.votes.upVotes.users + }, + downVotes: { + count: upvoteComment.votes.downVotes.count, + users: upvoteComment.votes.downVotes.users + } + } + } + }) + done() +}) + +/** + * Testing downvote post + */ + +test('Should downvote the post', async (done) => { + const response = await request(app) + .put(`/comment/downvote/${testCommentId}`) + .set('Authorization', `Bearer ${token}`) + .send() + .expect(200) + + const userId = response.body.comment.userId + const postId = response.body.comment.postId + + expect(response.body).toMatchObject({ + comment: { + content: downvoteComment.content, + userId: `${userId}`, + postId: `${postId}`, + votes: { + upVotes: { + count: downvoteComment.votes.upVotes.count, + users: downvoteComment.votes.upVotes.users + }, + downVotes: { + count: downvoteComment.votes.downVotes.count, + users: response.body.comment.votes.downVotes.users + } + } + } + }) + done() +}) + +/** + * TODO: FIX ERROR + * This is a temporary fix to issue: + * Jest has detected the following 1 open handle potentially keeping Jest from exiting + */ +afterAll(async () => { + // close server + await server.close() + // delete all the posts post testing + await Comment.deleteMany() + // Closing the DB connection allows Jest to exit successfully. + await mongoose.connection.close() +}) From e42774f36c01192fddc19de1687b2b78f3b39794 Mon Sep 17 00:00:00 2001 From: tanuj Date: Tue, 14 Apr 2020 16:52:45 +0530 Subject: [PATCH 15/42] Updated the status handling using npm directory http-status-codes --- app/controllers/auth.js | 8 +++---- app/controllers/organization.js | 24 ++++++++++----------- app/controllers/post.js | 5 +++-- app/controllers/urlShortner.js | 15 ++++++------- app/controllers/user.js | 37 +++++++++++++++++---------------- app/middleware/activate.js | 3 ++- app/middleware/auth.js | 3 ++- app/middleware/email.js | 3 ++- app/utils/status-codes.js | 13 ------------ package.json | 1 + test/user.test.js | 15 ++++++------- 11 files changed, 61 insertions(+), 66 deletions(-) delete mode 100644 app/utils/status-codes.js diff --git a/app/controllers/auth.js b/app/controllers/auth.js index 763e599..744c8dc 100644 --- a/app/controllers/auth.js +++ b/app/controllers/auth.js @@ -1,5 +1,5 @@ const User = require('../models/User') - +const HttpStatus = require('http-status-codes') module.exports = { authenticateUser: async (req, res, next) => { const email = req.body.email @@ -12,13 +12,13 @@ module.exports = { if (process.env.NODE_ENV !== 'production') { console.log(error.name, '-', error.message) } - res.status(400).json({ error: error.message }) + res.status(HttpStatus.BAD_REQUEST).json({ error: error.message }) } }, logout: (req, res, next) => { - res.json({ success: 'ok' }) + res.status(HttpStatus.OK).json({ success: 'ok' }) }, logoutAll: (req, res, next) => { - res.json({ success: 'ok' }) + res.status(HttpStatus.OK).json({ success: 'ok' }) } } diff --git a/app/controllers/organization.js b/app/controllers/organization.js index accab03..b1f86f5 100644 --- a/app/controllers/organization.js +++ b/app/controllers/organization.js @@ -1,6 +1,6 @@ const Organization = require('../models/Organisation') const HANDLER = require('../utils/response-helper') -const STATUS = require('../utils/status-codes') +const HttpStatus = require('http-status-codes') const helper = require('../utils/uploader') module.exports = { @@ -11,7 +11,7 @@ module.exports = { } try { await org.save() - res.status(STATUS.CREATED).json({ org }) + res.status(HttpStatus.CREATED).json({ org }) } catch (error) { HANDLER.handleError(res, error) } @@ -26,7 +26,7 @@ module.exports = { }) if (!isValidOperation) { - return res.status(STATUS.BAD_REQUEST).json({ error: 'invalid update' }) + return res.status(HttpStatus.BAD_REQUEST).json({ error: 'invalid update' }) } try { const org = await Organization.findById(id) @@ -37,7 +37,7 @@ module.exports = { helper.mapToDb(req, org) } await org.save() - res.status(STATUS.UPDATED).json({ organization: org }) + res.status(HttpStatus.OK).json({ organization: org }) } catch (error) { HANDLER.handleError(res, error) } @@ -53,9 +53,9 @@ module.exports = { .sort({ createdAt: -1 }) .exec() if (!orgData) { - return res.status(STATUS.NOT_FOUND).json({ error: 'No such organization exists!' }) + return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such organization exists!' }) } - res.status(STATUS.OK).json({ organization: orgData }) + res.status(HttpStatus.OK).json({ organization: orgData }) } catch (error) { HANDLER.handleError(res, error) } @@ -66,9 +66,9 @@ module.exports = { try { const org = await Organization.findByIdAndRemove(id) if (!org) { - return res.status(STATUS.NOT_FOUND).json({ error: 'No such organization exists!' }) + return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such organization exists!' }) } - res.status(STATUS.OK).json({ organization: org }) + res.status(HttpStatus.OK).json({ organization: org }) } catch (error) { HANDLER.handleError(res, error) } @@ -79,11 +79,11 @@ module.exports = { try { const org = await Organization.findById(id) if (!org) { - return res.status(STATUS.NOT_FOUND).json({ error: 'No such organization exists!' }) + return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such organization exists!' }) } org.isArchived = true await org.save() - res.status(STATUS.OK).json({ organization: org }) + res.status(HttpStatus.OK).json({ organization: org }) } catch (error) { HANDLER.handleError(res, error) } @@ -97,9 +97,9 @@ module.exports = { .sort({ createdAt: -1 }) .exec() if (orgs.length === 0) { - return res.status(STATUS.OK).json({ msg: 'No archived post exists!' }) + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No archived post exists!' }) } - res.status(STATUS.OK).json({ organization: orgs }) + res.status(HttpStatus.OK).json({ organization: orgs }) } catch (error) { HANDLER.handleError(res, error) } diff --git a/app/controllers/post.js b/app/controllers/post.js index 36c1ff2..920b9e3 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -1,6 +1,7 @@ const userModel = require('../models/User') const bcrypt = require('bcrypt') const jwt = require('jsonwebtoken') +const HttpStatus = require('http-status-codes') module.exports = { create: function (req, res, next) { @@ -8,7 +9,7 @@ module.exports = { if (err) { next(err) } else { - res.json({ status: 'success', message: 'User added successfully!!!', data: null }) + res.status(HttpStatus.CREATED).json({ status: 'success', message: 'User added successfully!!!', data: null }) } }) }, @@ -19,7 +20,7 @@ module.exports = { } else { if (bcrypt.compareSync(req.body.password, userInfo.password)) { const token = jwt.sign({ id: userInfo._id }, req.app.get('secretKey'), { expiresIn: '1h' }) - res.json({ status: 'success', message: 'user found!!!', data: { user: userInfo, token: token } }) + res.status(HttpStatus.OK).json({ status: 'success', message: 'user found!!!', data: { user: userInfo, token: token } }) } else { res.json({ status: 'error', message: 'Invalid email/password!!!', data: null }) } diff --git a/app/controllers/urlShortner.js b/app/controllers/urlShortner.js index c4de56a..a640bad 100644 --- a/app/controllers/urlShortner.js +++ b/app/controllers/urlShortner.js @@ -1,4 +1,5 @@ const UrlModel = require('../models/UrlShortner') +const HttpStatus = require('http-status-codes') const regex = '^(https?:\\/\\/)?' + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|' + @@ -18,12 +19,12 @@ module.exports = { const url = await UrlModel.findOne({ urlcode: req.params.shorturl }) if (url) { - return res.redirect(url.longurl) + return res.status(HttpStatus.OK).redirect(url.longurl) } else { - return res.json('No url found!') + return res.status(HttpStatus.NOT_FOUND).json('No url found!') } } catch (error) { - res.json('Server error!') + res.status(HttpStatus.INTERNAL_SERVER_ERROR).json('Server error!') } }, @@ -35,7 +36,7 @@ module.exports = { try { var url = await UrlModel.findOne(longurl) if (url) { - res.json(url) + res.status(HttpStatus.OK).json(url) } else { var shorturl = baseurl + '/' + urlcode url = new UrlModel({ @@ -44,14 +45,14 @@ module.exports = { urlcode }) await url.save() - res.json(url) + res.status(HttpStatus.CREATED).json(url) } } catch (error) { console.log(error) - res.json('Server error') + res.status(HttpStatus.INTERNAL_SERVER_ERROR).json('Server error') } } else { - res.json('invalid long url') + res.status(HttpStatus.NOT_FOUND).json('invalid long url') } } } diff --git a/app/controllers/user.js b/app/controllers/user.js index 7ad71fe..a8f7dc2 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -1,5 +1,6 @@ const User = require('../models/User') const jwt = require('jsonwebtoken') +const HttpStatus = require('http-status-codes') module.exports = { createUser: async (req, res, next) => { @@ -8,15 +9,15 @@ module.exports = { await user.save() const token = await user.generateAuthToken() // send email here, to activate the account - res.status(201).json({ user: user, token: token }) + res.status(HttpStatus.CREATED).json({ user: user, token: token }) } catch (error) { console.log(error) - res.status(400).json({ error: error }) + res.status(HttpStatus.NOT_ACCEPTABLE).json({ error: error }) } }, userProfile: async (req, res, next) => { - res.json(req.user) + res.status(HttpStatus.OK).json(req.user) }, userProfileUpdate: async (req, res, next) => { @@ -35,7 +36,7 @@ module.exports = { }) if (!isValidOperation) { - return res.status(400).json({ error: 'invalid update' }) + return res.status(HttpStatus.BAD_REQUEST).json({ error: 'invalid update' }) } try { @@ -43,9 +44,9 @@ module.exports = { req.user[update] = req.body[update] }) await req.user.save() - res.status(200).json({ data: req.user }) + res.status(HttpStatus.OK).json({ data: req.user }) } catch (error) { - res.status(400).json({ error }) + res.status(HttpStatus.BAD_REQUEST).json({ error }) } }, @@ -54,19 +55,19 @@ module.exports = { try { const user = await User.findOne({ email: email }) if (!user) { - res.status(404).json({ msg: 'User not found!' }) + res.status(HttpStatus.NOT_FOUND).json({ msg: 'User not found!' }) } const token = jwt.sign( { _id: user._id, expiry: Date.now() + 10800000 }, process.env.JWT_SECRET ) await user.save() - return res.status(200).json({ success: true, token }) + return res.status(HttpStatus.OK).json({ success: true, token }) } catch (error) { if (process.env.NODE_ENV !== 'production' && error) { console.log('Error in forgotPasswordRequest ', error) } - res.status(400).json({ error }) + res.status(HttpStatus.BAD_REQUEST).json({ error }) } }, @@ -81,27 +82,27 @@ module.exports = { _id: id }) if (!user) { - return res.status(400).json({ msg: 'No such user' }) + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'No such user' }) } user.password = password await user.save() - return res.status(200).json({ updated: true }) + return res.status(HttpStatus.OK).json({ updated: true }) } else { if (process.env.NODE_ENV !== 'production') { console.log('token expired') } - res.status(400).json({ error: 'Token expired' }) + res.status(HttpStatus.BAD_REQUEST).json({ error: 'Token expired' }) } } catch (error) { if (process.env.NODE_ENV !== 'production' && error) { console.log('Something went wrong ', error) } - res.status(400).json({ error }) + res.status(HttpStatus.BAD_REQUEST).json({ error }) } }, logout: (req, res, next) => { - res.json({ success: 'ok' }) + res.status(HttpStatus.OK).json({ success: 'ok' }) }, userDelete: async (req, res, next) => { @@ -110,7 +111,7 @@ module.exports = { res.send({ data: 'user deletion successful', user: req.user }) } catch (error) { console.log(error) - res.status(500).json({ error }) + res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error }) } }, @@ -122,18 +123,18 @@ module.exports = { if (expiryTime <= Date.now()) { const user = await User.findById(decodedToken._id) if (!user) { - res.status(404).json({ msg: 'User not found!' }) + res.status(HttpStatus.NOT_FOUND).json({ msg: 'User not found!' }) } // if user found activate the account user.isActivated = true await user.save() - return res.status(201).json({ user: user }) + return res.status(HttpStatus.OK).json({ user: user }) } } catch (Error) { if (process.env.NODE_ENV !== 'production' && Error) { console.log('Error in activateAccount ', Error) } - return res.status(400).json({ Error: Error }) + return res.status(HttpStatus.BAD_REQUEST).json({ Error: Error }) } } } diff --git a/app/middleware/activate.js b/app/middleware/activate.js index f24a28c..5104d83 100644 --- a/app/middleware/activate.js +++ b/app/middleware/activate.js @@ -1,4 +1,5 @@ const User = require('../models/User') +const HttpStatus = require('http-status-codes') const isActivated = async (req, res, next) => { const { email } = req.body @@ -11,7 +12,7 @@ const isActivated = async (req, res, next) => { next(new Error('Please activate the account!')) } } catch (Error) { - return res.status(400).json({ Error }) + return res.status(HttpStatus.BAD_REQUEST).json({ Error }) } } diff --git a/app/middleware/auth.js b/app/middleware/auth.js index 7acd520..637ccc8 100644 --- a/app/middleware/auth.js +++ b/app/middleware/auth.js @@ -1,5 +1,6 @@ const jwt = require('jsonwebtoken') const User = require('../models/User') +const HttpStatus = require('http-status-codes') const auth = async (req, res, next) => { try { @@ -18,7 +19,7 @@ const auth = async (req, res, next) => { next() } } catch (error) { - res.status(401).send({ error: 'Please authenticate' }) + res.status(HttpStatus.UNAUTHORIZED).send({ error: 'Please authenticate' }) } } diff --git a/app/middleware/email.js b/app/middleware/email.js index 7bd6442..e46eca2 100644 --- a/app/middleware/email.js +++ b/app/middleware/email.js @@ -1,5 +1,6 @@ const emailTemplate = require('../../views/emailTemplate') const sendgridMail = require('@sendgrid/mail') +const HttpStatus = require('http-status-codes') sendgridMail.setApiKey(process.env.SENDGRID_API_KEY) @@ -15,7 +16,7 @@ const email = (req, res, next) => { next() }, (error) => { - res.status(error.code).send({ + res.status(HttpStatus.BAD_REQUEST).send({ error: process.env.SENDGRID_API_KEY ? error.response.body : 'Setup SENDGRID_API_KEY environment variable' diff --git a/app/utils/status-codes.js b/app/utils/status-codes.js deleted file mode 100644 index 9b8b7e6..0000000 --- a/app/utils/status-codes.js +++ /dev/null @@ -1,13 +0,0 @@ -const HTTP_STATUS = { - OK: 200, - REDIRECT: 302, - BAD_REQUEST: 400, - SERVER_ERROR: 500, - NOT_FOUND: 404, - FORBIDDEN: 403, - CONFLICT: 409, - CREATED: 201, - UPDATED: 204 -} - -module.exports = HTTP_STATUS diff --git a/package.json b/package.json index 43e38b7..4b75bf8 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "dotenv": "^8.2.0", "ejs": "~2.6.1", "express": "^4.16.4", + "http-status-codes": "^1.4.0", "jsonwebtoken": "^8.5.1", "mongoose": "^5.7.7", "morgan": "^1.9.1", diff --git a/test/user.test.js b/test/user.test.js index 0f49355..5147c98 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -3,6 +3,7 @@ const mongoose = require('mongoose') const jwt = require('jsonwebtoken') const request = require('supertest') const User = require('../app/models/User') +const HttpStatus = require('http-status-codes') const demoUser = { name: { @@ -80,7 +81,7 @@ test('Should signup new user', async () => { const response = await request(app) .post('/user') .send(demoUser) - .expect(201) + .expect(HttpStatus.CREATED) // Assert that db was changed const user = await User.findById(response.body.user._id) @@ -122,7 +123,7 @@ test('Login existing user', async () => { email: testUser.email, password: testUser.password }) - .expect(200) + .expect(HttpStatus.OK) const user = await User.findById(testUserId) expect(response.body.token).toBe(user.tokens[1].token) @@ -133,7 +134,7 @@ test('Should not login non-existing user', async () => { await request(app).post('/auth/login').send({ email: 'random@random.com', password: 'random@123' - }).expect(400) + }).expect(HttpStatus.BAD_REQUEST) }) /** Fetch authenticated user profile */ @@ -142,7 +143,7 @@ test('Should get profile for user', async () => { .get('/user/me') .set('Authorization', `Bearer ${testUser.tokens[0].token}`) .send() - .expect(200) + .expect(HttpStatus.OK) }) /** Fail in getting unathenticated user profile */ @@ -150,7 +151,7 @@ test('Should not get profile for unauthenticated user', async () => { await request(app) .get('/user/me') .send() - .expect(401) + .expect(HttpStatus.UNAUTHORIZED) }) /** Delete authenticated user profile */ @@ -159,7 +160,7 @@ test('Should delete profile of authenticated user', async () => { .delete('/user/me') .set('Authorization', `Bearer ${testUser.tokens[0].token}`) .send() - .expect(200) + .expect(HttpStatus.OK) // Assert that user was deleted const user = await User.findById(testUserId) @@ -171,7 +172,7 @@ test('Should not delete profile of unauthenticated user', async () => { await request(app) .delete('/user/me') .send() - .expect(401) + .expect(HttpStatus.UNAUTHORIZED) }) /** From d6e794c6131c5684c7b6d6a70f6a4e4b64b725a6 Mon Sep 17 00:00:00 2001 From: Rupeshiya Date: Tue, 21 Apr 2020 09:19:22 +0530 Subject: [PATCH 16/42] tested organisatin REST API --- .env.test | 3 +- app/routes/user.js | 4 +- test/organisation.test.js | 182 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 3 deletions(-) create mode 100644 test/organisation.test.js diff --git a/.env.test b/.env.test index 42459d6..2b2a729 100644 --- a/.env.test +++ b/.env.test @@ -1,4 +1,5 @@ PORT=3000 NODE_ENV=testing JWT_SECRET=thisismysupersecrettokenjustkidding -DATABASE_URL=mongodb+srv://donut-admin:5cdS2C2g3wRAdQWp@donut-users-hdawt.mongodb.net/donut-testing?retryWrites=true&w=majority \ No newline at end of file +DATABASE_URL=mongodb+srv://donut-admin:5cdS2C2g3wRAdQWp@donut-users-hdawt.mongodb.net/donut-testing?retryWrites=true&w=majority +SENDGRID_API_KEY = 'SG.9RfagHPzTzSLBX5_xRdfw.ZfRhFaE_DPlqrV9_cFQrIYQ-gzNGXhYPvp-oVsQ_N3s' \ No newline at end of file diff --git a/app/routes/user.js b/app/routes/user.js index 6c76d9f..0793889 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -2,12 +2,12 @@ const express = require('express') const router = express.Router() const userController = require('../controllers/user') const auth = require('../middleware/auth') -const email = require('../middleware/email') +// const email = require('../middleware/email') // create a user router.post( '/', - email, + // email, userController.createUser ) diff --git a/test/organisation.test.js b/test/organisation.test.js new file mode 100644 index 0000000..6fe3a42 --- /dev/null +++ b/test/organisation.test.js @@ -0,0 +1,182 @@ +const app = require('../app') +const mongoose = require('mongoose') +const request = require('supertest') +const Organization = require('../app/models/Organisation') +const User = require('../app/models/User') +const jwt = require('jsonwebtoken') +const adminId = new mongoose.Types.ObjectId() +const moderatorId = new mongoose.Types.ObjectId() +const randomDigit = Math.floor(Math.random() * 90 + 10) +let orgId = '' +let token = '' + +const testOrg = { + name: 'test Organization', + description: { + shortDescription: 'this is short description', + longDescription: 'this is long description' + }, + contactInfo: { + email: 'organisation@test.com', + website: 'www.codeuino.org', + adminInfo: `${adminId}`, + moderatorInfo: `${moderatorId}` + } +} + +const updatedTestOrg = { + name: 'Updated test Organization', + description: { + shortDescription: 'this is updated short description', + longDescription: 'this is updated long description' + }, + contactInfo: { + email: 'updated@test.com', + website: 'www.codeuino.org', + adminInfo: `${adminId}`, + moderatorInfo: `${moderatorId}` + } +} + +const testUser = { + name: { + firstName: 'test', + lastName: 'test' + }, + email: `test${randomDigit}@mailinator.com`, + phone: `12345678${randomDigit}`, + password: 'abc12345', + info: { + about: { + shortDescription: 'this is short description', + longDescription: 'this is a very long description', + website: 'https://www.google.com', + designation: 'software engg', + skills: [ + 'c++', + 'java' + ], + education: [{ + school: { + schoolName: 'firstSchoolName', + year: '2017-2021' + } + }, + { + school: { + schoolName: 'secondSchoolName', + year: '2007-2014' + } + } + ], + location: 'location' + } + }, + tokens: [{ + token: jwt.sign({ + _id: `${adminId}` + }, process.env.JWT_SECRET) + }] +} + +let server +/** + * This will pe performed once at the beginning of all the test + */ +beforeAll(async (done) => { + await Organization.deleteMany() + await new User(testUser).save() + server = app.listen(4000, () => { + global.agent = request.agent(server) + }) + const response = await request(app) + .post('/auth/login') + .send({ + email: testUser.email, + password: testUser.password + }) + token = response.body.token + done() +}) + +/** CREATE THE ORG **/ +describe('POST /org/', () => { + test('should create a new Organization', async (done) => { + const response = await request(app) + .post('/org/') + .set('Authorization', `Bearer ${token}`) + .send(testOrg) + .expect(201) + orgId = response.body.org._id + /** DB must be changed **/ + const org = await Organization.findById(response.body.org._id) + expect(org).not.toBeNull() + + /** Check the response **/ + expect(response.body).toMatchObject({ + org: { + isArchived: false, + _id: `${orgId}`, + name: `${testOrg.name}`, + description: { + shortDescription: `${testOrg.description.shortDescription}`, + longDescription: `${testOrg.description.longDescription}` + }, + contactInfo: { + email: `${testOrg.contactInfo.email}`, + website: `${testOrg.contactInfo.website}` + } + } + }) + done() + }) +}) + +/** GET ORG DATA**/ +describe('GET /org/:id', () => { + test('Should fetch the Organization data', async (done) => { + await request(app) + .get(`/org/${orgId}`) + .set('Authorization', `Bearer ${token}`) + .send() + .expect(200) + done() + }) +}) + +/** UPDATE ORG DETAILS **/ +describe('PATCH /org/:id', () => { + test('Should update the Organization data', async (done) => { + await request(app) + .patch(`/org/${orgId}`) + .set('Authorization', `Bearer ${token}`) + .send(updatedTestOrg) + .expect(204) + done() + }) +}) + +/** DELETE ORGANIZATION**/ +describe('DELETE /org/:id', () => { + test('Should delete the organization', async (done) => { + await request(app) + .delete(`/org/${orgId}`) + .set('Authorization', `Bearer ${token}`) + .send() + .expect(200) + + /** Check if deleted or not **/ + const org = await Organization.findById(orgId) + expect(org).toBeNull() + done() + }) +}) + +afterAll(async () => { + // close server + await server.close() + // delete all the organization post testing + await Organization.deleteMany() + // Closing the DB connection allows Jest to exit successfully. + await mongoose.connection.close() +}) From aea0838672164a4567cfc38c56bc743d4baebc63 Mon Sep 17 00:00:00 2001 From: Rupeshiya Date: Mon, 27 Apr 2020 00:17:21 +0530 Subject: [PATCH 17/42] Added test cases for forgot password and activate account --- test/user.test.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/user.test.js b/test/user.test.js index 0f49355..d910f64 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -3,6 +3,8 @@ const mongoose = require('mongoose') const jwt = require('jsonwebtoken') const request = require('supertest') const User = require('../app/models/User') +let token = '' +let passwordToken = '' const demoUser = { name: { @@ -124,6 +126,7 @@ test('Login existing user', async () => { }) .expect(200) + token = response.body.token const user = await User.findById(testUserId) expect(response.body.token).toBe(user.tokens[1].token) }) @@ -174,6 +177,39 @@ test('Should not delete profile of unauthenticated user', async () => { .expect(401) }) +/** Forgot password request **/ +test('Should send the request to change the password ', async () => { + const response = await request(app) + .post('/user/password_reset') + .send({ + email: `${testUser.email}` + }) + .expect(200) + passwordToken = response.body.token + expect(passwordToken).not.toBeNull() +}) + +/* Password update */ +test('Should update the password ', async () => { + await request(app) + .post(`/user/password_reset/${passwordToken}`) + .send({ + password: 'newPassword', + id: testUserId + }) + .expect(200) +}) + +/* Activate account */ +test('Should activate the account ', async () => { + await request(app) + .post(`/user/activate/${token}`) + .send({ + token: `${token}` + }) + .expect(201) +}) + /** * TODO: FIX ERROR * This is a temporary fix to issue: From 92b6a558f3dbddb9825b11b30ffd1ba222cc7d07 Mon Sep 17 00:00:00 2001 From: tanujvyas10 <48439116+tanujvyas10@users.noreply.github.com> Date: Wed, 29 Apr 2020 01:00:46 +0530 Subject: [PATCH 18/42] added the API for events and modified the event database schema (#113) * event-backend-api * enhanced the api * added the status code --- app.js | 2 + app/controllers/event.js | 116 +++++++++++++++++ app/models/Event.js | 44 ++++--- app/routes/event.js | 43 +++++++ test/event.test.js | 261 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 443 insertions(+), 23 deletions(-) create mode 100644 app/controllers/event.js create mode 100644 app/routes/event.js create mode 100644 test/event.test.js diff --git a/app.js b/app.js index c7b6ebe..faf242f 100644 --- a/app.js +++ b/app.js @@ -9,6 +9,7 @@ const indexRouter = require('./app/routes/index') const authRouter = require('./app/routes/auth') const usersRouter = require('./app/routes/user') const postRouter = require('./app/routes/post') +const eventRouter = require('./app/routes/event') const shortUrlRouter = require('./app/routes/urlShortner') const organizationRouter = require('./app/routes/organisation') const commentRouter = require('./app/routes/comment') @@ -30,6 +31,7 @@ app.use('/auth', authRouter) app.use('/user', usersRouter) app.use('/post', postRouter) app.use('/org', organizationRouter) +app.use('/event', eventRouter) app.use('/shortUrl', shortUrlRouter) app.use('/comment', commentRouter) diff --git a/app/controllers/event.js b/app/controllers/event.js new file mode 100644 index 0000000..ae7f02b --- /dev/null +++ b/app/controllers/event.js @@ -0,0 +1,116 @@ +const Event = require('../models/Event') +const HANDLER = require('../utils/response-helper') +const HttpStatus = require('http-status-codes') +module.exports = { + createEvent: async (req, res, next) => { + const event = new Event(req.body) + try { + await event.save() + res.status(HttpStatus.CREATED).json({ event: event }) + } catch (error) { + res.status(HttpStatus.BAD_REQUEST).json({ error: error }) + } + }, + updateEvent: async (req, res) => { + const { id } = req.params + const updates = Object.keys(req.body) + try { + const event = await Event.findById(id) + if (!event) { + return res.status(HttpStatus.BAD_REQUEST).json({ message: 'No post exists' }) + } + updates.forEach(update => { + event[update] = req.body[update] + }) + await event.save() + res.status(HttpStatus.OK).json({ event: event }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + rsvp: async (req, res) => { + const { yes, no, maybe } = req.body + const { id } = req.params + try { + const data = await Event.findById(id) + if (!data) { + res.status(HttpStatus.BAD_REQUEST).json({ error: 'No Event is available' }) + return + } + if (data.rsvpMaybe.includes(req.user.id) || + data.rsvpNo.includes(req.user.id) || + data.rsvpYes.includes(req.user.id)) { + res.status(HttpStatus.OK).json({ msg: 'You have already done the rsvp' }) + return + } + const event = await Event.findByIdAndUpdate(id) + if (yes) { + try { + event.rsvpYes.push(req.user.id) + await event.save() + res.status(HttpStatus.OK).json({ rsvpData: data }) + } catch (error) { + res.status(HttpStatus.BAD_REQUEST).json({ error: error }) + } + } + if (no) { + try { + event.rsvpNo.push(req.user.id) + await event.save() + res.status(HttpStatus.OK).json({ rsvpData: data }) + } catch (error) { + res.status(HttpStatus.BAD_REQUEST).json({ error: error }) + } + } + if (maybe) { + try { + event.rsvpMaybe.push(req.user.id) + await event.save() + res.status(HttpStatus.OK).json({ rsvpData: data }) + } catch (error) { + res.status(HttpStatus.BAD_REQUEST).json({ error: error }) + } + } + } catch (error) { + HANDLER.handleError(res, error) + } + }, + GetEventById: async (req, res, next) => { + const { id } = req.params + try { + const EventData = await Event + .findById(id) + if (!EventData) { + return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such Event is available!' }) + } + res.status(HttpStatus.OK).json({ Event: EventData }) + } catch (error) { + next(error) + } + }, + GetAllEvent: async (req, res, next) => { + try { + const EventData = await Event + .find() + if (!EventData) { + return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such Event is available!' }) + } + res.status(HttpStatus.OK).json({ Event: EventData }) + } catch (error) { + next(error) + } + }, + deleteEvent: async (req, res, next) => { + const { id } = req.params + try { + const deleteEvent = await Event.findById(id) + if (!deleteEvent) { + return res.status(HttpStatus.NOT_FOUND).json({ message: 'No Event exists' }) + } + await Event.findByIdAndRemove(id) + res.status(HttpStatus.OK).json({ deleteEvent: deleteEvent, message: 'Deleted the event' }) + } catch (error) { + HANDLER.handleError(res, error) + } + } +} diff --git a/app/models/Event.js b/app/models/Event.js index 59473e7..c65a917 100644 --- a/app/models/Event.js +++ b/app/models/Event.js @@ -47,27 +47,25 @@ const eventSchema = new Schema({ } } }, - rsvp: { - yes: { - type: Schema.Types.ObjectId, - ref: 'User' - }, - no: { - type: Schema.Types.ObjectId, - ref: 'User' - }, - mayBe: { - type: Schema.Types.ObjectId, - ref: 'User' - } - }, + + rsvpYes: [{ + type: Schema.Types.ObjectId, + ref: 'User' + }], + rsvpMaybe: [{ + type: Schema.Types.ObjectId, + ref: 'User' + }], + rsvpNo: [{ + type: Schema.Types.ObjectId, + ref: 'User' + }], slots: { - type: Number, - required: true, + type: String, default: 0, validate (slots) { - if (validator.isEmpty(slots)) { - throw new Error('Slots is required!') + if (!validator.isNumeric(slots)) { + throw new Error('Slots should be a number') } } }, @@ -84,11 +82,11 @@ const eventSchema = new Schema({ eventDate: { type: Date, required: true, - validate (eventDate) { - if (validator.isEmpty(eventDate)) { - throw new Error('Event date is required!') - } - } + default: Date.now() + }, + isOnline: { + type: Boolean, + default: false }, createdAt: { type: Date, diff --git a/app/routes/event.js b/app/routes/event.js new file mode 100644 index 0000000..5f9e87f --- /dev/null +++ b/app/routes/event.js @@ -0,0 +1,43 @@ +const express = require('express') +const auth = require('../middleware/auth') +const router = express.Router() +const eventController = require('../controllers/event') + +// get all the events +router.get( + '/all', + auth, + eventController.GetAllEvent +) +// create an event +router.post( + '/', + auth, + eventController.createEvent +) +// get event by id +router.get( + '/:id', + auth, + eventController.GetEventById +) +// update an event +router.patch( + '/:id', + auth, + eventController.updateEvent +) +// rsvp by user +router.post( + '/rsvp/:id', + auth, + eventController.rsvp +) +// delete an event +router.delete( + '/:id', + auth, + eventController.deleteEvent +) + +module.exports = router diff --git a/test/event.test.js b/test/event.test.js new file mode 100644 index 0000000..257d14d --- /dev/null +++ b/test/event.test.js @@ -0,0 +1,261 @@ +const app = require('../app') +const mongoose = require('mongoose') +const jwt = require('jsonwebtoken') +const HttpStatus = require('http-status-codes') +const request = require('supertest') +const Event = require('../app/models/Event') +const User = require('../app/models/User') + +const testUserId = new mongoose.Types.ObjectId() +const demoEvent = { + description: { + shortDescription: 'this is short description', + longDescription: 'this is a very long description' + }, + slots: '2345222', + isOnline: false, + createdAt: '2020-04-04T06:53:28.018Z', + eventName: 'Student Developer meeting', + location: 'New delhi', + eventDate: '2020-04-04T06:53:28.018Z' +} + +const demoRsvp = { + yes: true +} +const demoUpdatedEvent = { + description: { + shortDescription: 'this is short description', + longDescription: 'this is a very long description' + }, + slots: '2345222', + isOnline: false, + createdAt: '2020-04-04T06:53:28.018Z', + eventName: 'Student Developer and mentors meeting', + location: 'New York', + eventDate: '2020-04-04T06:53:28.018Z' +} + +const testEventId = new mongoose.Types.ObjectId() +const testEvent = { + _id: testEventId, + ...demoEvent +} + +const demoUser = { + name: { + firstName: 'test', + lastName: 'test' + }, + email: 'test3@mailinator.com', + phone: '1234567890', + password: 'abc12345', + info: { + about: { + shortDescription: 'this is short description', + longDescription: 'this is a very long description', + website: 'https://www.google.com', + designation: 'software engg', + skills: [ + 'c++', + 'java' + ], + education: [{ + school: { + schoolName: 'firstSchoolName', + year: '2017-2021' + } + }, + { + school: { + schoolName: 'secondSchoolName', + year: '2007-2014' + } + } + ], + location: 'location' + } + } +} + +const testUser = { + _id: testUserId, + ...demoUser, + email: 'test@mailinator.com', + phone: '1234567891', + tokens: [{ + token: jwt.sign({ + _id: testUserId + }, process.env.JWT_SECRET) + }] +} +let server +/** + * This will pe performed once at the beginning of the test + */ +beforeAll(async (done) => { + await Event.deleteMany() + await User.deleteMany() + server = app.listen(4000, () => { + global.agent = request.agent(server) + done() + }) +}) + +/** + * This deletes all the existing user in database, + * and creates a new user in database with the provided details. + */ +beforeEach(async () => { + await Event.deleteMany() + await new Event(testEvent).save() + await User.deleteMany() + await new User(testUser).save() +}) + +test('Should signup new user', async () => { + const response = await request(app) + .post('/user') + .send(demoUser) + .expect(HttpStatus.CREATED) + + // Assert that db was changed + const user = await User.findById(response.body.user._id) + expect(user).not.toBeNull() + + // Assertions about the response + // expect(response.body.user.name.firstName).toBe('Rupesh') + // OR + expect(response.body).toMatchObject({ + user: { + name: { + firstName: demoUser.name.firstName, + lastName: demoUser.name.lastName + }, + email: demoUser.email, + phone: demoUser.phone, + info: { + about: { + skills: demoUser.info.about.skills, + shortDescription: demoUser.info.about.shortDescription, + longDescription: demoUser.info.about.longDescription, + website: demoUser.info.about.website, + designation: demoUser.info.about.designation, + education: demoUser.info.about.education, + location: demoUser.info.about.location + } + } + }, + token: user.tokens[0].token + }) + expect(user.password).not.toBe('abc12345') // to check hashing +}) + +/** Testing user login */ +test('Login existing user', async () => { + const response = await request(app) + .post('/auth/login') + .send({ + email: testUser.email, + password: testUser.password + }) + .expect(HttpStatus.OK) + + const user = await User.findById(testUserId) + expect(response.body.token).toBe(user.tokens[1].token) +}) +/** + * Testing event creation + */ +test('Should create new event', async () => { + const response = await request(app) + .post('/event') + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send(demoEvent) + .expect(HttpStatus.CREATED) + + // Assert that db was changed + const event = await Event.findById(response.body.event._id) + expect(event).not.toBeNull() + + // Assertions about the response + expect(response.body).toMatchObject({ + event: { + description: { + shortDescription: demoEvent.description.shortDescription, + longDescription: demoEvent.description.longDescription + }, + slots: demoEvent.slots, + isOnline: demoEvent.isOnline, + createdAt: demoEvent.createdAt, + eventName: demoEvent.eventName, + location: demoEvent.location, + eventDate: demoEvent.eventDate + } + }) +}) +/** + * Testing event updation + */ +test('Should update event', async () => { + const response = await request(app) + .patch(`/event/${testEventId}`) + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send(demoUpdatedEvent) + .expect(HttpStatus.OK) + + // Assert that db was changed + const updatedEvent = await Event.findById(response.body.event._id) + expect(updatedEvent).not.toBeNull() +}) + +// Testing for the RSVP +test('Should submit the RSVP', async () => { + const response = await request(app) + .post(`/event/rsvp/${testEventId}`) + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send(demoRsvp) + .expect(HttpStatus.OK) + const rsvpData = await Event.findById(response.body.rsvpData._id) + expect(rsvpData).not.toBeNull() +}) +test('Should delete event', async () => { + await request(app) + .delete(`/event/${testEventId}`) + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send() + .expect(HttpStatus.OK) + + // Assert that event was deleted + const event = await Event.findById(testEventId) + expect(event).toBeNull() +}) + +test('Should get event by id', async () => { + await request(app) + .get(`/event/${testEventId}`) + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send() + .expect(HttpStatus.OK) +}) + +test('Should get all the event', async () => { + await request(app) + .get('/event/all') + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send() + .expect(HttpStatus.OK) +}) +/** + * TODO: FIX ERROR + * This is a temporary fix to issue: + * Jest has detected the following 1 open handle potentially keeping Jest from exiting + */ +afterAll(async () => { + // close server + await server.close() + // delete all the events post testing + await Event.deleteMany() + // Closing the DB connection allows Jest to exit successfully. + await mongoose.connection.close() +}) From cd5a535d8adccfb2e3c0df5bf14c176dc9832581 Mon Sep 17 00:00:00 2001 From: Rupeshiya Date: Thu, 7 May 2020 01:05:48 +0530 Subject: [PATCH 19/42] updated test cases and api codes --- app/controllers/comment.js | 64 ++++++++++++++++----------- app/controllers/post.js | 67 ++++++++++------------------ app/controllers/user.js | 11 ++--- app/middleware/auth.js | 2 +- app/models/Comment.js | 8 ---- app/models/Post.js | 16 +------ app/models/User.js | 2 +- app/routes/comment.js | 12 +++-- app/routes/post.js | 15 +++---- bin/www | 0 test/comment.test.js | 57 +++++++++--------------- test/event.test.js | 2 +- test/post.test.js | 89 ++++++-------------------------------- test/user.test.js | 2 +- 14 files changed, 119 insertions(+), 228 deletions(-) mode change 100755 => 100644 bin/www diff --git a/app/controllers/comment.js b/app/controllers/comment.js index adaa3fe..b18e274 100644 --- a/app/controllers/comment.js +++ b/app/controllers/comment.js @@ -1,56 +1,61 @@ const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') const CommentModel = require('../models/Comment') -const consoleHelper = require('../utils/console-helper') module.exports = { + // CREATE COMMENT (ISSUE IN CREATE COMMENT ) comment: async (req, res, next) => { - const comment = new CommentModel(req.body) + const { id } = req.params const userId = req.user.id.toString() - comment.userId = userId try { + const comment = new CommentModel(req.body) + comment.userId = userId + comment.postId = id // added postId await comment.save() res.status(HttpStatus.CREATED).json({ comment: comment }) } catch (error) { HANDLER.handleError(res, error) } }, + + // DELETE COMMENT delete: async (req, res, next) => { const { id } = req.params const userId = req.user.id.toString() - consoleHelper(id) try { const comment = await CommentModel.findById(id) if (!comment) { - return res.status(HttpStatus.NOT_FOUND).json({ error: 'No comment exixts' }) + return res.status(HttpStatus.NOT_FOUND).json({ error: 'No comment exist' }) } + // Add rights for admins and moderators as well (TODO) if (JSON.stringify(comment.userId) !== JSON.stringify(userId)) { return res.status(HttpStatus.FORBIDDEN).json({ message: 'Bad delete request' }) } await CommentModel.findByIdAndRemove(id) res.status(HttpStatus.OK).json({ comment: comment }) } catch (error) { - consoleHelper(error) HANDLER.handleError(res, error) } }, + + // UPDATE COMMENT update: async (req, res, next) => { const { id } = req.params const userId = req.user.id.toString() const updates = Object.keys(req.body) const valid = ['content'] - let isValidUpdate = true - if (JSON.stringify(updates) !== JSON.stringify(valid)) { - isValidUpdate = false - } - if (!isValidUpdate) { + const isValidOperation = updates.every((update) => { + return valid.includes(update) + }) + if (!isValidOperation) { return res.status(HttpStatus.BAD_REQUEST).json({ error: 'Wrong Update Request' }) } try { const comment = await CommentModel.findById(id) if (!comment) { - return res.status(HttpStatus.NOT_FOUND).json({ error: 'No comment exixts' }) + return res.status(HttpStatus.NOT_FOUND).json({ error: 'No comment exist' }) } + // also add admin or moderator control (TODO) if (JSON.stringify(comment.userId) !== JSON.stringify(userId)) { return res.status(HttpStatus.BAD_REQUEST).json({ error: 'Wrong update' }) } @@ -58,24 +63,31 @@ module.exports = { comment[update] = req.body[update] }) await comment.save() - consoleHelper(comment) res.status(HttpStatus.OK).json({ comment: comment }) } catch (error) { HANDLER.handleError(res, error) } }, + + // GET ALL COMMENTS OF A POST BY postId getCommentByPost: async (req, res, next) => { const { id } = req.params try { - const comments = await CommentModel.find({ postId: id }).populate('userId').exec() + const comments = await CommentModel.find({ postId: id }) + .populate('userId', ['name.firstName', 'name.lastName']) + .sort({ updatedAt: -1 }) + .lean() + .exec() if (!comments) { return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such post' }) } - res.status(HttpStatus.OK).json({ comments: comments, success: true }) + res.status(HttpStatus.OK).json({ comments: comments }) } catch (error) { HANDLER.handleError(res, error) } }, + + // UPVOTE COMMENT upvote: async (req, res, next) => { const { id } = req.params const userId = req.user.id.toString() @@ -87,24 +99,26 @@ module.exports = { // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT comment.votes.upVotes.user.filter(user => { if (JSON.stringify(user) === JSON.stringify(userId)) { - return res.status(HttpStatus.BAD_REQUEST).json({ error: 'Bad request' }) + return res.status(HttpStatus.BAD_REQUEST).json({ + error: 'Bad request' + }) } }) // CHECKS IF THE USER HAS ALREADY DOWNVOTED THE COMMENT comment.votes.downVotes.user.filter(user => { if (JSON.stringify(user) === JSON.stringify(userId)) { - comment.votes.downVotes.count = comment.votes.downVotes.count - 1 comment.votes.downVotes.user.remove(user) } }) - comment.votes.upVotes.count = comment.votes.upVotes.count + 1 - comment.votes.upVotes.user.push(userId) + comment.votes.upVotes.user.unshift(userId) await comment.save() - res.status(HttpStatus.OK).json({ comment: comment, message: 'Success' }) + res.status(HttpStatus.OK).json({ comment: comment }) } catch (error) { HANDLER.handleError(res, error) } }, + + // DOWNVOTE COMMENT downvote: async (req, res, next) => { const { id } = req.params const userId = req.user.id.toString() @@ -116,20 +130,20 @@ module.exports = { // CHECKS IF THE USER HAS ALREADY DOWNVOTED THE COMMENT comment.votes.downVotes.user.filter(user => { if (JSON.stringify(user) === JSON.stringify(userId)) { - return res.status(HttpStatus.BAD_REQUEST).json({ error: 'Bad request' }) + return res.status(HttpStatus.BAD_REQUEST).json({ + error: 'Bad request' + }) } }) // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT comment.votes.upVotes.user.filter(user => { if (JSON.stringify(user) === JSON.stringify(userId)) { - comment.votes.upVotes.count = comment.votes.upVotes.count - 1 comment.votes.upVotes.user.remove(user) } }) - comment.votes.downVotes.count = comment.votes.downVotes.count + 1 - comment.votes.downVotes.user.push(userId) + comment.votes.downVotes.user.unshift(userId) await comment.save() - res.status(HttpStatus.OK).json({ comment: comment, message: 'Success' }) + res.status(HttpStatus.OK).json({ comment: comment }) } catch (error) { HANDLER.handleError(res, error) } diff --git a/app/controllers/post.js b/app/controllers/post.js index 42696ac..5c6fdda 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -2,9 +2,9 @@ const PostModel = require('../models/Post') const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') const imgUploadHelper = require('../utils/uploader') -const consoleHelper = require('../utils/console-helper') module.exports = { + // CREATE POST create: async (req, res, next) => { const post = new PostModel(req.body) const userId = req.user.id.toString() @@ -19,6 +19,8 @@ module.exports = { HANDLER.handleError(res, error) } }, + + // DELETE POST delete: async (req, res, next) => { const { id } = req.params const userId = req.user.id.toString() @@ -27,18 +29,20 @@ module.exports = { if (!post) { return res.status(HttpStatus.NOT_FOUND).json({ message: 'No post exists' }) } + // TODO ADD ADMIN RIGHTS AS WELL if (JSON.stringify(userId) !== JSON.stringify(post.userId)) { return res.status(HttpStatus.FORBIDDEN).json({ message: 'Bad delete request' }) } await PostModel.findByIdAndRemove(id) - res.status(HttpStatus.OK).json({ post: post, message: 'Deleted the post' }) + res.status(HttpStatus.OK).json({ post: post, msg: 'Deleted!' }) } catch (error) { HANDLER.handleError(res, error) } }, + + // UPDATE POST updatePost: async (req, res, next) => { const { id } = req.params - consoleHelper(id) const updates = Object.keys(req.body) const allowedUpdates = ['content', 'imgUrl'] const userId = req.user.id.toString() @@ -57,7 +61,6 @@ module.exports = { if (JSON.stringify(userId) !== JSON.stringify(post.userId)) { return res.status(HttpStatus.FORBIDDEN).json({ message: 'Bad update request' }) } - consoleHelper(post) updates.forEach(update => { post[update] = req.body[update] }) @@ -70,10 +73,16 @@ module.exports = { HANDLER.handleError(res, error) } }, + + // GET POST BY ID getPostById: async (req, res, next) => { const { id } = req.params try { const post = await PostModel.findById(id) + .populate('comments', ['content', 'votes']) + .populate('userId', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) + .lean() + .exec() if (!post) { return res.status(HttpStatus.NOT_FOUND).json({ error: 'Post not found' }) } @@ -82,9 +91,14 @@ module.exports = { HANDLER.handleError(res, error) } }, + + // GET ALL THE POSTS getAllPost: async (req, res, next) => { try { const posts = await PostModel.find({}) + .populate('userId', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) + .sort({ updatedAt: -1 }) + .exec() if (!posts.length) { return res.status(HttpStatus.NOT_FOUND).json({ message: 'No posts found' }) } @@ -93,6 +107,8 @@ module.exports = { HANDLER.handleError(res, error) } }, + + // UPVOTE POST upvote: async (req, res, next) => { const { id } = req.params const userId = req.user.id.toString() @@ -102,51 +118,14 @@ module.exports = { return res.status(HttpStatus.NOT_FOUND).json({ error: 'No post found' }) } // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT - post.votes.upVotes.users.filter(user => { + post.votes.upVotes.user.filter(user => { if (JSON.stringify(user) === JSON.stringify(userId)) { return res.status(HttpStatus.BAD_REQUEST).json({ error: 'Bad request' }) } }) - // CHECKS IF THE USER HAS ALREADY DOWNVOTED THE COMMENT - post.votes.downVotes.users.filter(user => { - if (JSON.stringify(user) === JSON.stringify(userId)) { - post.votes.downVotes.count = post.votes.downVotes.count - 1 - post.votes.downVotes.users.remove(user) - } - }) - post.votes.upVotes.count = post.votes.upVotes.count + 1 - post.votes.upVotes.users.push(userId) + post.votes.upVotes.user.unshift(userId) await post.save() - res.status(HttpStatus.OK).json({ post: post, message: 'Success' }) - } catch (error) { - HANDLER.handleError(res, error) - } - }, - downvote: async (req, res, next) => { - const { id } = req.params - const userId = req.user.id.toString() - try { - const post = await PostModel.findById(id) - if (!post) { - return res.status(HttpStatus.NOT_FOUND).json({ error: 'No post found' }) - } - // CHECKS IF THE USER HAS ALREADY DOWNVOTED THE COMMENT - post.votes.downVotes.users.filter(user => { - if (JSON.stringify(user) === JSON.stringify(userId)) { - return res.status(HttpStatus.BAD_REQUEST).json({ error: 'Bad request' }) - } - }) - // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT - post.votes.upVotes.users.filter(user => { - if (JSON.stringify(user) === JSON.stringify(userId)) { - post.votes.upVotes.count = post.votes.upVotes.count - 1 - post.votes.upVotes.users.remove(user) - } - }) - post.votes.downVotes.count = post.votes.downVotes.count + 1 - post.votes.downVotes.users.push(userId) - await post.save() - res.status(HttpStatus.OK).json({ post: post, message: 'Success' }) + res.status(HttpStatus.OK).json({ post: post }) } catch (error) { HANDLER.handleError(res, error) } diff --git a/app/controllers/user.js b/app/controllers/user.js index d272a1a..7a5696c 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -57,7 +57,7 @@ module.exports = { if (!user) { res.status(HttpStatus.NOT_FOUND).json({ msg: 'User not found!' }) } - const token = jwt.sign({ _id: user._id, expiry: Date.now() + 10800000 }, process.env.JWT_SECRET) + const token = jwt.sign({ _id: user._id, expiry: Date.now() + 10800000 }, 'process.env.JWT_SECRET') await user.save() return res.status(HttpStatus.OK).json({ success: true, token }) } catch (error) { @@ -72,7 +72,7 @@ module.exports = { const { password, id } = req.body const { token } = req.params try { - const decodedToken = jwt.verify(token, process.env.JWT_SECRET) + const decodedToken = jwt.verify(token, 'process.env.JWT_SECRET') if (Date.now() <= decodedToken.expiry) { const user = await User.findById({ @@ -115,7 +115,7 @@ module.exports = { activateAccount: async (req, res, next) => { try { const { token } = req.params - const decodedToken = jwt.verify(token, process.env.JWT_SECRET) + const decodedToken = jwt.verify(token, 'process.env.JWT_SECRET') const expiryTime = decodedToken.iat + 10800000 // 24 hrs if (expiryTime <= Date.now()) { const user = await User.findById(decodedToken._id) @@ -128,10 +128,7 @@ module.exports = { return res.status(HttpStatus.OK).json({ user: user }) } } catch (Error) { - if (process.env.NODE_ENV !== 'production' && Error) { - console.log('Error in activateAccount ', Error) - } - return res.status(HttpStatus.BAD_REQUEST).json({ Error: Error }) + return res.status(HttpStatus.BAD_REQUEST).json({ Error }) } } } diff --git a/app/middleware/auth.js b/app/middleware/auth.js index 637ccc8..75c1a61 100644 --- a/app/middleware/auth.js +++ b/app/middleware/auth.js @@ -5,7 +5,7 @@ const HttpStatus = require('http-status-codes') const auth = async (req, res, next) => { try { const token = req.header('Authorization').replace('Bearer ', '') - const decoded = jwt.verify(token, process.env.JWT_SECRET) + const decoded = jwt.verify(token, 'process.env.JWT_SECRET') const user = await User.findOne({ _id: decoded._id, 'tokens.token': token diff --git a/app/models/Comment.js b/app/models/Comment.js index 3dda599..7daa41d 100644 --- a/app/models/Comment.js +++ b/app/models/Comment.js @@ -23,20 +23,12 @@ const commentSchema = new Schema({ }, votes: { upVotes: { - count: { - type: Number, - default: 0 - }, user: [{ type: Schema.Types.ObjectId, ref: 'User' }] }, downVotes: { - count: { - type: Number, - default: 0 - }, user: [{ type: Schema.Types.ObjectId, ref: 'User' diff --git a/app/models/Post.js b/app/models/Post.js index 572248e..8e03908 100644 --- a/app/models/Post.js +++ b/app/models/Post.js @@ -32,21 +32,7 @@ const PostSchema = new Schema({ }, votes: { upVotes: { - count: { - type: Number, - default: 0 - }, - users: [{ - type: Schema.Types.ObjectId, - ref: 'User' - }] - }, - downVotes: { - count: { - type: Number, - default: 0 - }, - users: [{ + user: [{ type: Schema.Types.ObjectId, ref: 'User' }] diff --git a/app/models/User.js b/app/models/User.js index 95e2850..d33103f 100644 --- a/app/models/User.js +++ b/app/models/User.js @@ -167,7 +167,7 @@ const UserSchema = new mongoose.Schema({ // Schema Methods, needs to be invoked by an instance of a Mongoose document UserSchema.methods.generateAuthToken = async function () { const user = this - const token = jwt.sign({ _id: user._id.toString() }, process.env.JWT_SECRET) + const token = jwt.sign({ _id: user._id.toString() }, 'process.env.JWT_SECRET') user.tokens = user.tokens.concat({ token: token }) await user.save() diff --git a/app/routes/comment.js b/app/routes/comment.js index 8144088..99e1043 100644 --- a/app/routes/comment.js +++ b/app/routes/comment.js @@ -3,37 +3,43 @@ const router = express.Router() const auth = require('../middleware/auth') const commentController = require('../controllers/comment') +// CREATE COMMENT router.post( - '/', + '/:id', auth, commentController.comment ) +// DELETE COMMENT BY ID router.delete( '/:id', auth, commentController.delete ) +// UPDATE COMMENT BY ID router.patch( '/:id', auth, commentController.update ) +// GET COMMENT BY POST ID router.get( '/:id', auth, commentController.getCommentByPost ) -router.put( +// UPVOTE COMMENT BY COMMENT ID +router.patch( '/upvote/:id', auth, commentController.upvote ) -router.put( +// DOWNVOTE COMMENT BY COMMENT ID +router.patch( '/downvote/:id', auth, commentController.downvote diff --git a/app/routes/post.js b/app/routes/post.js index 5f46b42..05b8277 100644 --- a/app/routes/post.js +++ b/app/routes/post.js @@ -20,7 +20,7 @@ router.get( userController.getAllPost ) -// UPDATE A TASK +// UPDATE POST router.patch( '/:id', auth, @@ -28,30 +28,25 @@ router.patch( userController.updatePost ) -// DELETE A TASK +// DELETE A POST BY ID router.delete( '/:id', auth, userController.delete ) -// GET TASK BY ID +// GET POST BY ID router.get( '/:id', auth, userController.getPostById ) -router.put( +// UPVOTE POST BY POST ID +router.patch( '/upvote/:id', auth, userController.upvote ) -router.put( - '/downvote/:id', - auth, - userController.downvote -) - module.exports = router diff --git a/bin/www b/bin/www old mode 100755 new mode 100644 diff --git a/test/comment.test.js b/test/comment.test.js index 4fa4147..e77e3ca 100644 --- a/test/comment.test.js +++ b/test/comment.test.js @@ -6,6 +6,7 @@ const request = require('supertest') const Post = require('../app/models/Post') const User = require('../app/models/User') const Comment = require('../app/models/Comment') +const randomDigit = Math.floor(Math.random() * 90 + 10) const testUserId = new mongoose.Types.ObjectId() const testPostId = new mongoose.Types.ObjectId() @@ -18,12 +19,10 @@ const demoComment = { postId: testPostId, votes: { upVotes: { - count: 0, - users: [] + user: [] }, downVotes: { - count: 0, - users: [] + user: [] } } } @@ -34,12 +33,10 @@ const demoPost = { userId: testUserId, votes: { upVotes: { - count: 0, - users: [] + user: [] }, downVotes: { - count: 0, - users: [] + user: [] } } } @@ -53,14 +50,12 @@ const upvoteComment = { userId: testUserId, votes: { upVotes: { - count: 1, - users: [ + user: [ testUserId ] }, downVotes: { - count: 0, - users: [] + user: [] } } } @@ -70,12 +65,10 @@ const downvoteComment = { userId: testUserId, votes: { upVotes: { - count: 0, - users: [] + user: [] }, downVotes: { - count: 1, - users: [ + user: [ testUserId ] } @@ -92,8 +85,8 @@ const demoUser = { firstName: 'test', lastName: 'test' }, - email: 'test3@mailinator.com', - phone: '1234567890', + email: `test${randomDigit}@mailinator.com`, + phone: `12345678${randomDigit}`, password: 'abc12345', info: { about: { @@ -126,8 +119,6 @@ const demoUser = { const testUser = { _id: testUserId, ...demoUser, - email: 'test@mailinator.com', - phone: '1234567891', tokens: [{ token: jwt.sign({ _id: testUserId @@ -144,7 +135,6 @@ beforeAll(async (done) => { await new Post(demoPost).save() server = app.listen(4000, () => { global.agent = request.agent(server) - done() }) const response = await request(app) .post('/auth/login') @@ -170,7 +160,7 @@ beforeEach(async () => { */ test('Should create new comment', async (done) => { const response = await request(app) - .post('/comment') + .post(`/comment/${testPostId}`) .set('Authorization', `Bearer ${token}`) .send(demoComment) .expect(HttpStatus.CREATED) @@ -190,12 +180,10 @@ test('Should create new comment', async (done) => { postId: `${postId}`, votes: { upVotes: { - count: demoComment.votes.upVotes.count, - users: demoComment.votes.upVotes.users + user: demoComment.votes.upVotes.user }, downVotes: { - count: demoComment.votes.downVotes.count, - users: demoComment.votes.downVotes.users + user: demoComment.votes.downVotes.user } } } @@ -251,14 +239,13 @@ test('Should get comment for post', async (done) => { test('Should upvote the comment', async (done) => { const response = await request(app) - .put(`/comment/upvote/${testCommentId}`) + .patch(`/comment/upVote/${testCommentId}`) .set('Authorization', `Bearer ${token}`) .send() .expect(HttpStatus.OK) const userId = response.body.comment.userId const postId = response.body.comment.postId - expect(response.body).toMatchObject({ comment: { content: upvoteComment.content, @@ -266,12 +253,10 @@ test('Should upvote the comment', async (done) => { postId: `${postId}`, votes: { upVotes: { - count: upvoteComment.votes.upVotes.count, - users: response.body.comment.votes.upVotes.users + user: response.body.comment.votes.upVotes.user }, downVotes: { - count: upvoteComment.votes.downVotes.count, - users: upvoteComment.votes.downVotes.users + user: response.body.comment.votes.downVotes.user } } } @@ -285,7 +270,7 @@ test('Should upvote the comment', async (done) => { test('Should downvote the post', async (done) => { const response = await request(app) - .put(`/comment/downvote/${testCommentId}`) + .patch(`/comment/downVote/${testCommentId}`) .set('Authorization', `Bearer ${token}`) .send() .expect(HttpStatus.OK) @@ -300,12 +285,10 @@ test('Should downvote the post', async (done) => { postId: `${postId}`, votes: { upVotes: { - count: downvoteComment.votes.upVotes.count, - users: downvoteComment.votes.upVotes.users + user: response.body.comment.votes.upVotes.user }, downVotes: { - count: downvoteComment.votes.downVotes.count, - users: response.body.comment.votes.downVotes.users + user: response.body.comment.votes.downVotes.user } } } diff --git a/test/event.test.js b/test/event.test.js index 257d14d..e73ec8c 100644 --- a/test/event.test.js +++ b/test/event.test.js @@ -86,7 +86,7 @@ const testUser = { tokens: [{ token: jwt.sign({ _id: testUserId - }, process.env.JWT_SECRET) + }, 'process.env.JWT_SECRET') }] } let server diff --git a/test/post.test.js b/test/post.test.js index 1896ec1..5191495 100644 --- a/test/post.test.js +++ b/test/post.test.js @@ -5,6 +5,7 @@ const HttpStatus = require('http-status-codes') const request = require('supertest') const Post = require('../app/models/Post') const User = require('../app/models/User') +const randomDigit = Math.floor(Math.random() * 90 + 10) const testUserId = new mongoose.Types.ObjectId() let token = '' @@ -13,12 +14,10 @@ const demoPost = { userId: testUserId, votes: { upVotes: { - count: 0, - users: [] + user: [] }, downVotes: { - count: 0, - users: [] + user: [] } } } @@ -32,31 +31,12 @@ const upvotePost = { userId: testUserId, votes: { upVotes: { - count: 1, - users: [ + user: [ testUserId ] }, downVotes: { - count: 0, - users: [] - } - } -} - -const downvotePost = { - content: 'test post content', - userId: testUserId, - votes: { - upVotes: { - count: 0, - users: [] - }, - downVotes: { - count: 1, - users: [ - testUserId - ] + user: [] } } } @@ -72,8 +52,8 @@ const demoUser = { firstName: 'test', lastName: 'test' }, - email: 'test3@mailinator.com', - phone: '1234567890', + email: `test${randomDigit}@mailinator.com`, + phone: `12345678${randomDigit}`, password: 'abc12345', info: { about: { @@ -106,8 +86,8 @@ const demoUser = { const testUser = { _id: testUserId, ...demoUser, - email: 'test@mailinator.com', - phone: '1234567891', + email: `test${randomDigit}@mailinator.com`, + phone: `12345678${randomDigit}`, tokens: [{ token: jwt.sign({ _id: testUserId @@ -123,7 +103,6 @@ beforeAll(async (done) => { await new User(testUser).save() server = app.listen(4000, () => { global.agent = request.agent(server) - done() }) const response = await request(app) .post('/auth/login') @@ -156,6 +135,7 @@ test('Should create new post', async (done) => { // Assert that db was changed const post = await Post.findById(response.body.post._id) + expect(post).not.toBeNull() const userId = response.body.post.userId @@ -167,12 +147,7 @@ test('Should create new post', async (done) => { userId: `${userId}`, votes: { upVotes: { - count: demoPost.votes.upVotes.count, - users: demoPost.votes.upVotes.users - }, - downVotes: { - count: demoPost.votes.downVotes.count, - users: demoPost.votes.downVotes.users + user: response.body.post.votes.upVotes.user } } } @@ -216,7 +191,7 @@ test('Should get post for user', async (done) => { test('Should upvote the post', async (done) => { const response = await request(app) - .put(`/post/upvote/${testPostId}`) + .patch(`/post/upvote/${testPostId}`) .set('Authorization', `Bearer ${token}`) .send() .expect(HttpStatus.OK) @@ -229,44 +204,7 @@ test('Should upvote the post', async (done) => { userId: `${userId}`, votes: { upVotes: { - count: upvotePost.votes.upVotes.count, - users: response.body.post.votes.upVotes.users - }, - downVotes: { - count: upvotePost.votes.downVotes.count, - users: upvotePost.votes.downVotes.users - } - } - } - }) - done() -}) - -/** - * Testing downvote post - */ - -test('Should downvote the post', async (done) => { - const response = await request(app) - .put(`/post/downvote/${testPostId}`) - .set('Authorization', `Bearer ${token}`) - .send() - .expect(HttpStatus.OK) - - const userId = response.body.post.userId - - expect(response.body).toMatchObject({ - post: { - content: downvotePost.content, - userId: `${userId}`, - votes: { - upVotes: { - count: downvotePost.votes.upVotes.count, - users: downvotePost.votes.upVotes.users - }, - downVotes: { - count: downvotePost.votes.downVotes.count, - users: response.body.post.votes.downVotes.users + user: response.body.post.votes.upVotes.user } } } @@ -285,6 +223,7 @@ test('Should update the Post data', async (done) => { .expect(HttpStatus.OK) done() }) + /** * TODO: FIX ERROR * This is a temporary fix to issue: diff --git a/test/user.test.js b/test/user.test.js index 0a3c4c7..b749bb3 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -52,7 +52,7 @@ const testUser = { tokens: [{ token: jwt.sign({ _id: testUserId - }, process.env.JWT_SECRET) + }, 'process.env.JWT_SECRET') }] } let server From a2d4ade96cfdccafd3f77e023d03121c55b5c09b Mon Sep 17 00:00:00 2001 From: Rupeshiya Date: Thu, 7 May 2020 11:55:36 +0530 Subject: [PATCH 20/42] added test cases for url shortener --- app/controllers/urlShortner.js | 52 +++++++++++---------------- app/models/UrlShortner.js | 6 ++-- app/routes/urlShortner.js | 2 +- bin/www | 0 test/url.test.js | 65 ++++++++++++++++++++++++++++++++++ 5 files changed, 89 insertions(+), 36 deletions(-) mode change 100755 => 100644 bin/www create mode 100644 test/url.test.js diff --git a/app/controllers/urlShortner.js b/app/controllers/urlShortner.js index a640bad..0235673 100644 --- a/app/controllers/urlShortner.js +++ b/app/controllers/urlShortner.js @@ -1,25 +1,14 @@ const UrlModel = require('../models/UrlShortner') const HttpStatus = require('http-status-codes') - -const regex = '^(https?:\\/\\/)?' + - '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.?)+[a-z]{2,}|' + - '((\\d{1,3}\\.){3}\\d{1,3}))' + - '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + - '(\\?[;&a-z\\d%_.~+=-]*)?' + - '(\\#[-a-z\\d_]*)?$' - -function validURL (myURL) { - var pattern = new RegExp(regex, 'i') - return pattern.test(myURL) -} +const validator = require('validator') module.exports = { - redirect: async function (req, res) { + redirect: async (req, res) => { try { - const url = await UrlModel.findOne({ urlcode: req.params.shorturl }) - + const { urlcode } = req.params + const url = await UrlModel.findOne({ urlCode: urlcode }) if (url) { - return res.status(HttpStatus.OK).redirect(url.longurl) + return res.status(HttpStatus.OK).redirect(url.longUrl) } else { return res.status(HttpStatus.NOT_FOUND).json('No url found!') } @@ -28,28 +17,27 @@ module.exports = { } }, - shorten: async function (req, res) { - var longurl = req.body + shorten: async (req, res) => { + var { longUrl } = req.body var baseurl = req.get('host') - var urlcode = Date.now() - if (validURL(longurl.longurl)) { + var urlCode = Date.now() + if (validator.isURL(longUrl)) { try { - var url = await UrlModel.findOne(longurl) + var url = await UrlModel.findOne({ longUrl }) if (url) { - res.status(HttpStatus.OK).json(url) - } else { - var shorturl = baseurl + '/' + urlcode - url = new UrlModel({ - longurl: longurl.longurl, - shorturl, - urlcode - }) - await url.save() - res.status(HttpStatus.CREATED).json(url) + return res.status(HttpStatus.OK).json(url) } + var shortUrl = baseurl + '/' + urlCode + url = new UrlModel({ + longUrl: longUrl, + shortUrl: shortUrl, + urlCode: urlCode + }) + await url.save() + res.status(HttpStatus.CREATED).json(url) } catch (error) { console.log(error) - res.status(HttpStatus.INTERNAL_SERVER_ERROR).json('Server error') + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json('Server error') } } else { res.status(HttpStatus.NOT_FOUND).json('invalid long url') diff --git a/app/models/UrlShortner.js b/app/models/UrlShortner.js index b5b60fd..e18b4e0 100644 --- a/app/models/UrlShortner.js +++ b/app/models/UrlShortner.js @@ -1,14 +1,14 @@ const mongoose = require('mongoose') const urlShortnerSchema = new mongoose.Schema({ - longurl: { + longUrl: { type: String, required: true }, - urlcode: { + urlCode: { type: String }, - shorturl: { + shortUrl: { type: String } }) diff --git a/app/routes/urlShortner.js b/app/routes/urlShortner.js index 779e5d0..68f2203 100644 --- a/app/routes/urlShortner.js +++ b/app/routes/urlShortner.js @@ -4,7 +4,7 @@ const shortnerController = require('../controllers/urlShortner') // Redirects the ShortURL back to LongURL router.get( - '/:shorturl', + '/:urlcode', shortnerController.redirect ) diff --git a/bin/www b/bin/www old mode 100755 new mode 100644 diff --git a/test/url.test.js b/test/url.test.js new file mode 100644 index 0000000..3d1e0d7 --- /dev/null +++ b/test/url.test.js @@ -0,0 +1,65 @@ +const app = require('../app') +const mongoose = require('mongoose') +const request = require('supertest') +const UrlModel = require('../app/models/UrlShortner') +const HttpStatus = require('http-status-codes') +const testUrl = 'http://codeuino.org/codeofconduct' +// let shortUrl = '' + +let server +/** + * This will pe performed once at the beginning of the test + */ +beforeAll(async (done) => { + await UrlModel.deleteMany() + server = app.listen(4000, () => { + global.agent = request.agent(server) + done() + }) +}) + +/** + * Testing Shorten URL + */ +test('Should short the URL', async (done) => { + const response = await request(app) + .post('/shortUrl/shorten') + .send({ + longUrl: `${testUrl}` + }) + .expect(HttpStatus.CREATED) + + // Assert that db was changed + const url = await UrlModel.findById(response.body._id) + expect(url).not.toBeNull() + // shortUrl = response.body.shortUrl + + // Assertions about the + expect(response.body).toMatchObject({ + longUrl: `${testUrl}`, + shortUrl: `${response.body.shortUrl}`, + urlCode: `${response.body.urlCode}` + }) + done() +}) + +/** + * ShortURL to longUrl + */ + +// test('Should redirect to the longUrl ', async (done) => { +// const param = shortUrl.toString().split('/')[1] +// shortUrl = 'http://localhost:4000' + '/' + param +// console.log('ShortUrl ', shortUrl) +// await request(app) +// .get(`${shortUrl}`) +// .expect(301, `${testUrl}`) +// done() +// }) + +afterAll(async () => { + // close server + await server.close() + // Closing the DB connection allows Jest to exit successfully. + await mongoose.connection.close() +}) From 0dd15aebf6ec58acf5c6bbce1246e4b53a6827ef Mon Sep 17 00:00:00 2001 From: Rupesh Krishna Jha Date: Tue, 12 May 2020 19:37:05 +0530 Subject: [PATCH 21/42] functional activate account --- .env.dev | 1 + .env.test | 2 +- app/controllers/email.js | 35 +++++++++ app/controllers/user.js | 12 ++-- app/middleware/email.js | 28 -------- app/routes/user.js | 2 +- test/event.test.js | 13 ++-- test/user.test.js | 2 +- views/emailTemplate.ejs | 150 +++++++++++++++++++++++++++++++++++++++ views/emailTemplate.js | 93 ------------------------ 10 files changed, 203 insertions(+), 135 deletions(-) create mode 100644 app/controllers/email.js delete mode 100644 app/middleware/email.js create mode 100644 views/emailTemplate.ejs delete mode 100644 views/emailTemplate.js diff --git a/.env.dev b/.env.dev index a4df8b4..9cd8a95 100644 --- a/.env.dev +++ b/.env.dev @@ -2,3 +2,4 @@ PORT=5000 NODE_ENV="development" JWT_SECRET="thisismysupersecrettokenjustkidding" DATABASE_URL="mongodb://localhost:27017/donut-development" +SENDGRID_API_KEY = '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' diff --git a/.env.test b/.env.test index 2b2a729..fa78537 100644 --- a/.env.test +++ b/.env.test @@ -2,4 +2,4 @@ PORT=3000 NODE_ENV=testing JWT_SECRET=thisismysupersecrettokenjustkidding DATABASE_URL=mongodb+srv://donut-admin:5cdS2C2g3wRAdQWp@donut-users-hdawt.mongodb.net/donut-testing?retryWrites=true&w=majority -SENDGRID_API_KEY = 'SG.9RfagHPzTzSLBX5_xRdfw.ZfRhFaE_DPlqrV9_cFQrIYQ-gzNGXhYPvp-oVsQ_N3s' \ No newline at end of file +SENDGRID_API_KEY = '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' \ No newline at end of file diff --git a/app/controllers/email.js b/app/controllers/email.js new file mode 100644 index 0000000..382a5c3 --- /dev/null +++ b/app/controllers/email.js @@ -0,0 +1,35 @@ +const sendgridMail = require('@sendgrid/mail') +const ejs = require('ejs') +const path = require('path') +const sendGridApi = process.env.SENDGRID_API_KEY || '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' + +sendgridMail.setApiKey(sendGridApi) + +module.exports = { + sendEmail: async (req, res, next, token) => { + const filePath = path.join(__dirname, '/../../views/emailTemplate.ejs') + ejs.renderFile(filePath, { token: token }, (err, data) => { + if (err) { + console.log('Error in renderFile ', err) + } else { + const message = { + to: req.body.email, + from: 'services@codeuino.com', + subject: `Welcome to Donut ${req.body.name.firstName}`, + html: data + } + sendgridMail.send(message).then( + () => { + console.log('sending email') + }, + (error) => { + console.log('error in sending email ', error) + if (error.response) { + console.error(error.response.body) + } + } + ) + } + }) + } +} diff --git a/app/controllers/user.js b/app/controllers/user.js index 7a5696c..1cab037 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -1,6 +1,7 @@ const User = require('../models/User') const jwt = require('jsonwebtoken') const HttpStatus = require('http-status-codes') +const emailController = require('./email') module.exports = { createUser: async (req, res, next) => { @@ -8,11 +9,12 @@ module.exports = { try { await user.save() const token = await user.generateAuthToken() - // send email here, to activate the account - res.status(HttpStatus.CREATED).json({ user: user, token: token }) + // Added fn to send email to activate account with warm message + await emailController.sendEmail(req, res, next, token) + return res.status(HttpStatus.CREATED).json({ user: user, token: token }) } catch (error) { console.log(error) - res.status(HttpStatus.NOT_ACCEPTABLE).json({ error: error }) + return res.status(HttpStatus.NOT_ACCEPTABLE).json({ error: error }) } }, @@ -120,12 +122,12 @@ module.exports = { if (expiryTime <= Date.now()) { const user = await User.findById(decodedToken._id) if (!user) { - res.status(HttpStatus.NOT_FOUND).json({ msg: 'User not found!' }) + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'User not found!' }) } // if user found activate the account user.isActivated = true await user.save() - return res.status(HttpStatus.OK).json({ user: user }) + return res.status(HttpStatus.OK).json({ msg: 'Succesfully activated!' }) } } catch (Error) { return res.status(HttpStatus.BAD_REQUEST).json({ Error }) diff --git a/app/middleware/email.js b/app/middleware/email.js deleted file mode 100644 index e46eca2..0000000 --- a/app/middleware/email.js +++ /dev/null @@ -1,28 +0,0 @@ -const emailTemplate = require('../../views/emailTemplate') -const sendgridMail = require('@sendgrid/mail') -const HttpStatus = require('http-status-codes') - -sendgridMail.setApiKey(process.env.SENDGRID_API_KEY) - -const email = (req, res, next) => { - const message = { - to: req.body.email, - from: 'services@codeuino.com', - subject: `Welcome to Donut ${req.body.name.firstName}`, - html: emailTemplate - } - sendgridMail.send(message).then( - () => { - next() - }, - (error) => { - res.status(HttpStatus.BAD_REQUEST).send({ - error: process.env.SENDGRID_API_KEY - ? error.response.body - : 'Setup SENDGRID_API_KEY environment variable' - }) - } - ) -} - -module.exports = email diff --git a/app/routes/user.js b/app/routes/user.js index 0793889..275cfa6 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -38,7 +38,7 @@ router.post( ) // activate account -router.post( +router.get( '/activate/:token', userController.activateAccount ) diff --git a/test/event.test.js b/test/event.test.js index e73ec8c..b54789a 100644 --- a/test/event.test.js +++ b/test/event.test.js @@ -5,6 +5,7 @@ const HttpStatus = require('http-status-codes') const request = require('supertest') const Event = require('../app/models/Event') const User = require('../app/models/User') +const randomDigit = Math.floor(Math.random() * 90 + 10) const testUserId = new mongoose.Types.ObjectId() const demoEvent = { @@ -47,8 +48,8 @@ const demoUser = { firstName: 'test', lastName: 'test' }, - email: 'test3@mailinator.com', - phone: '1234567890', + email: `test${randomDigit}@mailinator.com`, + phone: `12345678${randomDigit}`, password: 'abc12345', info: { about: { @@ -81,14 +82,15 @@ const demoUser = { const testUser = { _id: testUserId, ...demoUser, - email: 'test@mailinator.com', - phone: '1234567891', + email: `test${randomDigit + Math.random() * 10}@mailinator.com`, + phone: `12335678${randomDigit}`, tokens: [{ token: jwt.sign({ _id: testUserId }, 'process.env.JWT_SECRET') }] } + let server /** * This will pe performed once at the beginning of the test @@ -145,8 +147,7 @@ test('Should signup new user', async () => { location: demoUser.info.about.location } } - }, - token: user.tokens[0].token + } }) expect(user.password).not.toBe('abc12345') // to check hashing }) diff --git a/test/user.test.js b/test/user.test.js index b749bb3..6c614d0 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -204,7 +204,7 @@ test('Should update the password ', async () => { /* Activate account */ test('Should activate the account ', async () => { await request(app) - .post(`/user/activate/${token}`) + .get(`/user/activate/${token}`) .send({ token: `${token}` }) diff --git a/views/emailTemplate.ejs b/views/emailTemplate.ejs new file mode 100644 index 0000000..0434d93 --- /dev/null +++ b/views/emailTemplate.ejs @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + +
+ + Codeuino org image + +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + +
+
+ Welcome to Donut +
+
+
+
+
+

+
+
+
+
+
+ Your registration is successful! We warmly + welcome you to the donut platform! +
+
+ Donut is an open-source, feature-rich, + highly flexible and privacy-friendly, social + networking platform built for + community-oriented collaboration in a + customized way. It has been built on the + Node.js framework allowing an essential + impetus to provide custom and friendly rich + widgets and an expansive library of modules + to make communication and collaboration easy + and successful. With a powerful module + system, you can customize this platform by + using third party tools, writing your own or + integrating other software. +
+

Please use the given below link if the button does not work properly.

+

http://localhost:5000/user/activate/<%= token %>

+
+ + Activate account + +
+
+ Hope you enjoy your stay at Donut! +
+
+
+   +
+
+
+
+ + + + +
+
+
+ + + + + diff --git a/views/emailTemplate.js b/views/emailTemplate.js deleted file mode 100644 index 3be2175..0000000 --- a/views/emailTemplate.js +++ /dev/null @@ -1,93 +0,0 @@ -module.exports = ` - - - - - - - - - - - - - - - - - - - -
- Codeuino org image -
- - - - - - - - - - - - - -
- - - - - - - - - - - -
-
- Welcome to Donut -
-
-
-
-

-
-
-
-

- Your registration is successful! We warmly welcome you to the donut platform!

- Donut is an open-source, feature-rich, highly flexible and privacy-friendly, social networking platform built for community-oriented collaboration in a customized way. It has been built on the Node.js framework allowing an essential impetus to provide custom and friendly rich widgets and an expansive library of modules to make communication and collaboration easy and successful. With a powerful module system, you can customize this platform by using third party tools, writing your own or integrating other software.

- Hope you enjoy your stay at Donut!
-
-
-   -
-
- -
-
-
- - - - -
-
-
- - - - -` From d13e12de37fdd9c5b46c6c1befe970b1330de530 Mon Sep 17 00:00:00 2001 From: Rupesh Krishna Jha Date: Thu, 14 May 2020 15:58:21 +0530 Subject: [PATCH 22/42] done with invite link functionality --- app/controllers/user.js | 24 +++++++++++++++++++++--- app/routes/user.js | 13 +++++++++++++ test/user.test.js | 22 ++++++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/app/controllers/user.js b/app/controllers/user.js index 7a5696c..677c77b 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -57,7 +57,7 @@ module.exports = { if (!user) { res.status(HttpStatus.NOT_FOUND).json({ msg: 'User not found!' }) } - const token = jwt.sign({ _id: user._id, expiry: Date.now() + 10800000 }, 'process.env.JWT_SECRET') + const token = jwt.sign({ _id: user._id, expiry: Date.now() + 10800000 }, process.env.JWT_SECRET) await user.save() return res.status(HttpStatus.OK).json({ success: true, token }) } catch (error) { @@ -72,7 +72,7 @@ module.exports = { const { password, id } = req.body const { token } = req.params try { - const decodedToken = jwt.verify(token, 'process.env.JWT_SECRET') + const decodedToken = jwt.verify(token, process.env.JWT_SECRET) if (Date.now() <= decodedToken.expiry) { const user = await User.findById({ @@ -116,7 +116,7 @@ module.exports = { try { const { token } = req.params const decodedToken = jwt.verify(token, 'process.env.JWT_SECRET') - const expiryTime = decodedToken.iat + 10800000 // 24 hrs + const expiryTime = decodedToken.iat + 24 * 3600 * 1000 // 24 hrs if (expiryTime <= Date.now()) { const user = await User.findById(decodedToken._id) if (!user) { @@ -130,5 +130,23 @@ module.exports = { } catch (Error) { return res.status(HttpStatus.BAD_REQUEST).json({ Error }) } + }, + + getInviteLink: async (req, res, next) => { + const token = jwt.sign({ _id: req.user._id, expiry: Date.now() + 24 * 3600 * 1000 }, process.env.JWT_SECRET) + const inviteLink = `${req.protocol}://${req.get('host')}/user/invite/${token}` + return res.status(HttpStatus.OK).json({ inviteLink: inviteLink }) + }, + + processInvite: async (req, res, next) => { + const { token } = req.params + const decodedToken = jwt.verify(token, process.env.JWT_SECRET) + // check if token not expired and sender exist in db then valid request + const user = await User.findById(decodedToken._id) + if (user && Date.now() <= decodedToken.expiry) { + console.log('Valid invite!') + return res.status(HttpStatus.OK).json({ success: true, msg: 'Redirect user to register in client side!' }) + } + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Invalid token!' }) } } diff --git a/app/routes/user.js b/app/routes/user.js index 0793889..2a1a6bc 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -37,6 +37,19 @@ router.post( userController.updatePassword ) +// get invite link (for sender) +router.get( + '/invite', + auth, + userController.getInviteLink +) + +// process invite link (for receiver) +router.get( + '/invite/:token', + userController.processInvite +) + // activate account router.post( '/activate/:token', diff --git a/test/user.test.js b/test/user.test.js index b749bb3..7876585 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -6,6 +6,7 @@ const User = require('../app/models/User') const HttpStatus = require('http-status-codes') let token = '' let passwordToken = '' +let inviteLink = '' const demoUser = { name: { @@ -211,6 +212,27 @@ test('Should activate the account ', async () => { .expect(HttpStatus.OK) }) +/* Get invite link */ +test('Should generate an invite link and send', async () => { + const response = await request(app) + .get('/user/invite') + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send() + .expect(HttpStatus.OK) + inviteLink = response.body.inviteLink + // check the response + expect(response.body.inviteLink).not.toBeNull() +}) + +/* Process invite link */ +test('Should validate the invite link token ', async () => { + const inviteToken = inviteLink.split('/').slice(-1)[0].trim() + await request(app) + .get(`/user/invite/${inviteToken}`) + .send() + .expect(HttpStatus.OK) +}) + /** * TODO: FIX ERROR * This is a temporary fix to issue: From d05feae7bcb8617e74ca8bd828a35b9845435d86 Mon Sep 17 00:00:00 2001 From: Rupesh Krishna Jha Date: Fri, 29 May 2020 18:39:41 +0530 Subject: [PATCH 23/42] working mechanism for paging and follow --- app/controllers/comment.js | 3 +++ app/controllers/event.js | 47 ++++++++++++++++++++++++++------------ app/controllers/post.js | 6 ++++- app/routes/event.js | 10 +++++++- app/routes/index.js | 6 ++--- config/mongoose.js | 2 +- 6 files changed, 54 insertions(+), 20 deletions(-) diff --git a/app/controllers/comment.js b/app/controllers/comment.js index b18e274..bf5f030 100644 --- a/app/controllers/comment.js +++ b/app/controllers/comment.js @@ -71,9 +71,12 @@ module.exports = { // GET ALL COMMENTS OF A POST BY postId getCommentByPost: async (req, res, next) => { + const currentPage = req.query.page ? parseInt(req.query.page) : 1 const { id } = req.params try { const comments = await CommentModel.find({ postId: id }) + .skip((currentPage - 1) * 5) + .limit(5) .populate('userId', ['name.firstName', 'name.lastName']) .sort({ updatedAt: -1 }) .lean() diff --git a/app/controllers/event.js b/app/controllers/event.js index ae7f02b..c6cf405 100644 --- a/app/controllers/event.js +++ b/app/controllers/event.js @@ -40,35 +40,34 @@ module.exports = { if (data.rsvpMaybe.includes(req.user.id) || data.rsvpNo.includes(req.user.id) || data.rsvpYes.includes(req.user.id)) { - res.status(HttpStatus.OK).json({ msg: 'You have already done the rsvp' }) - return + return res.status(HttpStatus.OK).json({ msg: 'You have already done the rsvp' }) } const event = await Event.findByIdAndUpdate(id) if (yes) { try { event.rsvpYes.push(req.user.id) await event.save() - res.status(HttpStatus.OK).json({ rsvpData: data }) + return res.status(HttpStatus.OK).json({ rsvpData: data }) } catch (error) { - res.status(HttpStatus.BAD_REQUEST).json({ error: error }) + return res.status(HttpStatus.BAD_REQUEST).json({ error: error }) } } if (no) { try { event.rsvpNo.push(req.user.id) await event.save() - res.status(HttpStatus.OK).json({ rsvpData: data }) + return res.status(HttpStatus.OK).json({ rsvpData: data }) } catch (error) { - res.status(HttpStatus.BAD_REQUEST).json({ error: error }) + return res.status(HttpStatus.BAD_REQUEST).json({ error: error }) } } if (maybe) { try { event.rsvpMaybe.push(req.user.id) await event.save() - res.status(HttpStatus.OK).json({ rsvpData: data }) + return res.status(HttpStatus.OK).json({ rsvpData: data }) } catch (error) { - res.status(HttpStatus.BAD_REQUEST).json({ error: error }) + return res.status(HttpStatus.BAD_REQUEST).json({ error: error }) } } } catch (error) { @@ -78,8 +77,7 @@ module.exports = { GetEventById: async (req, res, next) => { const { id } = req.params try { - const EventData = await Event - .findById(id) + const EventData = await Event.findById(id) if (!EventData) { return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such Event is available!' }) } @@ -89,15 +87,19 @@ module.exports = { } }, GetAllEvent: async (req, res, next) => { + const pagination = req.query.pagination ? parseInt(req.query.pagination) : 10 + const currentPage = req.query.page ? parseInt(req.query.page) : 1 try { - const EventData = await Event - .find() + const EventData = await Event.find({}) + .lean() + .skip((currentPage - 1) * pagination) + .limit(pagination) if (!EventData) { return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such Event is available!' }) } - res.status(HttpStatus.OK).json({ Event: EventData }) + return res.status(HttpStatus.OK).json({ Event: EventData }) } catch (error) { - next(error) + HANDLER.handleError(res, error) } }, deleteEvent: async (req, res, next) => { @@ -112,5 +114,22 @@ module.exports = { } catch (error) { HANDLER.handleError(res, error) } + }, + UpComingEvents: async (req, res, next) => { + const pageSize = req.query.pagination ? parseInt(req.query.pagination) : 10 + const currentPage = req.query.page ? parseInt(req.query.page) : 1 + try { + const events = await Event.find({ eventDate: { $gt: Date.now() } }) + .skip((currentPage - 1) * pageSize) + .limit(pageSize) + .exec() + console.log('Upcoming events ', events) + if (events.length === 0) { + return res.status(HttpStatus.OK).json({ msg: 'No Upcoming events exists!' }) + } + return res.status(HttpStatus.OK).json({ events }) + } catch (error) { + HANDLER.handleError(res, next) + } } } diff --git a/app/controllers/post.js b/app/controllers/post.js index 5c6fdda..26ab8d6 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -94,15 +94,19 @@ module.exports = { // GET ALL THE POSTS getAllPost: async (req, res, next) => { + const pagination = req.query.pagination ? parseInt(req.query.pagination) : 10 + const currentPage = req.query.page ? parseInt(req.query.page) : 1 try { const posts = await PostModel.find({}) + .skip((currentPage - 1) * pagination) + .limit(pagination) .populate('userId', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) .sort({ updatedAt: -1 }) .exec() if (!posts.length) { return res.status(HttpStatus.NOT_FOUND).json({ message: 'No posts found' }) } - res.status(HttpStatus.OK).json({ posts: posts }) + return res.status(HttpStatus.OK).json({ posts: posts }) } catch (error) { HANDLER.handleError(res, error) } diff --git a/app/routes/event.js b/app/routes/event.js index 5f9e87f..75a9288 100644 --- a/app/routes/event.js +++ b/app/routes/event.js @@ -9,6 +9,14 @@ router.get( auth, eventController.GetAllEvent ) + +// get all the events +router.get( + '/upcoming', + auth, + eventController.UpComingEvents +) + // create an event router.post( '/', @@ -28,7 +36,7 @@ router.patch( eventController.updateEvent ) // rsvp by user -router.post( +router.patch( '/rsvp/:id', auth, eventController.rsvp diff --git a/app/routes/index.js b/app/routes/index.js index ebf8bac..57acc51 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -7,8 +7,8 @@ router.get('/', function (req, res, next) { res.redirect(documentationUrl) }) -router.get('/:shorturl', (req, res, next) => { - res.redirect('/shortUrl/' + req.params.shorturl) -}) +// router.get('/:shorturl', (req, res, next) => { +// res.redirect('/shortUrl/' + req.params.shorturl) +// }) module.exports = router diff --git a/config/mongoose.js b/config/mongoose.js index 2904b19..780c802 100644 --- a/config/mongoose.js +++ b/config/mongoose.js @@ -1,6 +1,6 @@ const mongoose = require('mongoose') -mongoose.connect(process.env.DATABASE_URL, { +mongoose.connect('mongodb://rupesh:abc123@ds125953.mlab.com:25953/testing', { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, From 0a6f20034a294057c309531c659227210669ec77 Mon Sep 17 00:00:00 2001 From: Rupesh Krishna Jha Date: Thu, 28 May 2020 15:55:10 +0530 Subject: [PATCH 24/42] working mechanism for triggering maintenace, updating org settings by admin, pin post --- app/controllers/organization.js | 78 +++++++++++++++++++++++++++------ app/controllers/post.js | 51 +++++++++++++++++++++ app/middleware/maintenance.js | 22 ++++++++++ app/models/Organisation.js | 73 ++++++++++++++++++++++++++---- app/models/Post.js | 4 ++ app/models/User.js | 7 +++ app/routes/auth.js | 2 + app/routes/comment.js | 7 +++ app/routes/event.js | 7 +++ app/routes/organisation.js | 16 ++++++- app/routes/post.js | 37 +++++++++++++--- app/routes/user.js | 10 +++++ 12 files changed, 285 insertions(+), 29 deletions(-) create mode 100644 app/middleware/maintenance.js diff --git a/app/controllers/organization.js b/app/controllers/organization.js index 4131416..3ff2a53 100644 --- a/app/controllers/organization.js +++ b/app/controllers/organization.js @@ -90,20 +90,72 @@ module.exports = { }, triggerMaintenance: async (req, res, next) => { - const { orgId } = req.body - const organization = await Organization.findById(orgId) - const adminIds = organization.adminInfo.map(info => info.adminId) - const isAdmin = adminIds.indexOf(req.user.id) - if (!organization) { - return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such organization exists!' }) + const { id } = req.params + try { + const organization = await Organization.findById(id) + // if org exists or not + if (!organization) { + return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such organization exists!' }) + } + // if user is admin or not + const adminIds = organization.adminInfo.adminId + const isAdmin = adminIds.indexOf(req.user.id) + // user is admin then perform operation + if (isAdmin !== -1) { + // toggle maintenance mode + organization.isMaintenance = !organization.isMaintenance + await organization.save() + if (organization.isMaintenance) { + return res.status(HttpStatus.OK).json({ msg: 'Organization is kept under the maintenance!!' }) + } + return res.status(HttpStatus.OK).json({ msg: 'Organization is recovered from maintenance!!' }) + } else { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have access to triggerMaintenance!' }) + } + } catch (error) { + HANDLER.handleError(res, error) } - if (isAdmin) { - // toggle maintenance mode - organization.isMaintenance = !organization.isMaintenance - await organization.save() - res.status(HttpStatus.OK).json({ msg: 'Organization is under the maintenance!!' }) - } else { - res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have access to triggerMaintenance!' }) + }, + + updateSettings: async (req, res, next) => { + const { id } = req.params + try { + // check if org exists + const organization = await Organization.findById(id) + if (!organization) { + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No Organization found!' }) + } + // check if user is admin or not + const adminIds = organization.adminInfo.adminId + const isAdmin = adminIds.indexOf(req.user.id) + const updates = Object.keys(req.body) + console.log('req.body ', req.body) + console.log('isAdmin ', isAdmin) + const allowedUpdates = [ + 'settings', + 'permissions', + 'authentication' + ] + // if admin then check if valid update + if (isAdmin !== -1) { + const isValidOperation = updates.every((update) => { + return allowedUpdates.includes(update) + }) + // if valid update + if (isValidOperation) { + updates.forEach(update => { + organization.options[update] = req.body[update] + }) + await organization.save() + return res.status(HttpStatus.OK).json({ msg: 'Successfully updated!' }) + } + // invalid update + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Invalid update' }) + } + // else not admin + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have access to perform this operation!' }) + } catch (error) { + HANDLER.handleError(res, error) } } } diff --git a/app/controllers/post.js b/app/controllers/post.js index 5c6fdda..ab00e9d 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -1,4 +1,5 @@ const PostModel = require('../models/Post') +const UserModel = require('../models/User') const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') const imgUploadHelper = require('../utils/uploader') @@ -129,5 +130,55 @@ module.exports = { } catch (error) { HANDLER.handleError(res, error) } + }, + + // PIN THE POST + pinPost: async (req, res, next) => { + const { id } = req.params + try { + const post = await PostModel.findById(id) + const user = await UserModel.findById(req.user._id) + if (!post) { + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such post exists!' }) + } + // toggle pinned post + post.isPinned = !post.isPinned + // if already pinned then remove from their pinned items + const PinnedItems = user.pinned.postId + if (PinnedItems.length > 0) { + const pinnedPostIndex = PinnedItems.indexOf(id) + user.pinned.postId.splice(pinnedPostIndex, 1) + await user.save() + } else { + // save to the user pinned items + user.pinned.postId.unshift(id) + await user.save() + } + await post.save() + return res.status(HttpStatus.OK).json({ post }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + // GET ALL PINNED POST + getPinned: async (req, res, next) => { + try { + const posts = await PostModel.find({}) + if (posts.length === 0) { + return res.status(HttpStatus.OK).json({ msg: 'No post to show!' }) + } + // check for pinned post + const pinnedPost = posts.filter((post) => { + return post.isPinned === true + }) + if (pinnedPost.length === 0) { + return res.status(HttpStatus.OK).json({ msg: 'No post pinned yet!' }) + } + // else return pinned posts + return res.status(HttpStatus.OK).json({ pinnedPost }) + } catch (error) { + HANDLER.handleError(res, error) + } } } diff --git a/app/middleware/maintenance.js b/app/middleware/maintenance.js new file mode 100644 index 0000000..0c2ffda --- /dev/null +++ b/app/middleware/maintenance.js @@ -0,0 +1,22 @@ +const Organization = require('../models/Organisation') +const HttpStatus = require('http-status-codes') + +const isUnderMaintenance = async (req, res, next) => { + try { + const org = await Organization.find({}) + if (!org) { + next(new Error('No org is found!')) + } + if (org[0] && org[0].isMaintenance) { + return res.status(HttpStatus.SERVICE_UNAVAILABLE).json({ + msg: 'Organization is kept under maintenance!' + }) + } else { + next() + } + } catch (Error) { + return res.status(HttpStatus.BAD_REQUEST).json({ Error }) + } +} + +module.exports = isUnderMaintenance diff --git a/app/models/Organisation.js b/app/models/Organisation.js index 4117e6d..5f2ffcd 100644 --- a/app/models/Organisation.js +++ b/app/models/Organisation.js @@ -98,20 +98,77 @@ const orgSchema = new Schema({ } ] }, - adminInfo: [{ + options: { _id: false, - adminId: { + settings: { + enableEmail: { + type: Boolean, + default: true + }, + language: { + type: String, + enum: ['English', 'French', 'German'], + default: 'English' + }, + timeFormat: { + type: String, + enum: ['24', '12'], + default: '12' + } + }, + permissions: { + sendInvite: { + type: String, + enum: ['BOTH', 'ADMINS', 'NONE'], + default: 'BOTH' + }, + canCreateManage: { + type: String, + enum: ['BOTH', 'ADMINS', 'MEMBERS'], + default: 'BOTH' + }, + canChangeEmail: { + type: Boolean, + default: true + }, + canChangeName: { + type: Boolean, + default: true + } + }, + authentication: { + email: { + type: Boolean, + default: true + }, + google: { + type: Boolean, + default: false + }, + github: { + type: Boolean, + default: false + }, + gitlab: { + type: Boolean, + default: false + } + } + }, + adminInfo: { + _id: false, + adminId: [{ type: Schema.Types.ObjectId, ref: 'User' - } - }], - moderatorInfo: [{ + }] + }, + moderatorInfo: { _id: false, - adminId: { + adminId: [{ type: Schema.Types.ObjectId, ref: 'User' - } - }], + }] + }, isArchived: { type: Boolean, default: false diff --git a/app/models/Post.js b/app/models/Post.js index 8e03908..36b2a7d 100644 --- a/app/models/Post.js +++ b/app/models/Post.js @@ -42,6 +42,10 @@ const PostSchema = new Schema({ type: Schema.Types.ObjectId, ref: 'Comment' }, + isPinned: { + type: Boolean, + default: false + }, createdAt: { type: Date, required: true, diff --git a/app/models/User.js b/app/models/User.js index d33103f..8e8b739 100644 --- a/app/models/User.js +++ b/app/models/User.js @@ -136,6 +136,13 @@ const UserSchema = new mongoose.Schema({ } } }, + pinned: { + _id: false, + postId: [{ + type: mongoose.Schema.Types.ObjectId, + ref: 'Post' + }] + }, isAdmin: { type: Boolean, default: false diff --git a/app/routes/auth.js b/app/routes/auth.js index aec4703..f42fd96 100644 --- a/app/routes/auth.js +++ b/app/routes/auth.js @@ -2,10 +2,12 @@ const express = require('express') const router = express.Router() const authController = require('../controllers/auth') // const isActivated = require('../middleware/activate') +const isUnderMaintenance = require('../middleware/maintenance') // user login router.post( '/login', + isUnderMaintenance, authController.authenticateUser ) diff --git a/app/routes/comment.js b/app/routes/comment.js index 99e1043..5d16b55 100644 --- a/app/routes/comment.js +++ b/app/routes/comment.js @@ -2,10 +2,12 @@ const express = require('express') const router = express.Router() const auth = require('../middleware/auth') const commentController = require('../controllers/comment') +const isUnderMaintenance = require('../middleware/maintenance') // CREATE COMMENT router.post( '/:id', + isUnderMaintenance, auth, commentController.comment ) @@ -13,6 +15,7 @@ router.post( // DELETE COMMENT BY ID router.delete( '/:id', + isUnderMaintenance, auth, commentController.delete ) @@ -20,6 +23,7 @@ router.delete( // UPDATE COMMENT BY ID router.patch( '/:id', + isUnderMaintenance, auth, commentController.update ) @@ -27,6 +31,7 @@ router.patch( // GET COMMENT BY POST ID router.get( '/:id', + isUnderMaintenance, auth, commentController.getCommentByPost ) @@ -34,6 +39,7 @@ router.get( // UPVOTE COMMENT BY COMMENT ID router.patch( '/upvote/:id', + isUnderMaintenance, auth, commentController.upvote ) @@ -41,6 +47,7 @@ router.patch( // DOWNVOTE COMMENT BY COMMENT ID router.patch( '/downvote/:id', + isUnderMaintenance, auth, commentController.downvote ) diff --git a/app/routes/event.js b/app/routes/event.js index 5f9e87f..406f0c3 100644 --- a/app/routes/event.js +++ b/app/routes/event.js @@ -2,40 +2,47 @@ const express = require('express') const auth = require('../middleware/auth') const router = express.Router() const eventController = require('../controllers/event') +const isUnderMaintenance = require('../middleware/maintenance') // get all the events router.get( '/all', + isUnderMaintenance, auth, eventController.GetAllEvent ) // create an event router.post( '/', + isUnderMaintenance, auth, eventController.createEvent ) // get event by id router.get( '/:id', + isUnderMaintenance, auth, eventController.GetEventById ) // update an event router.patch( '/:id', + isUnderMaintenance, auth, eventController.updateEvent ) // rsvp by user router.post( '/rsvp/:id', + isUnderMaintenance, auth, eventController.rsvp ) // delete an event router.delete( '/:id', + isUnderMaintenance, auth, eventController.deleteEvent ) diff --git a/app/routes/organisation.js b/app/routes/organisation.js index f9fccd7..e3e1167 100644 --- a/app/routes/organisation.js +++ b/app/routes/organisation.js @@ -3,10 +3,12 @@ const router = express.Router() const auth = require('../middleware/auth') const OrgController = require('../controllers/organization') const uploader = require('../utils/uploader') +const isUnderMaintenance = require('../middleware/maintenance') // CREATE ORG router.post( '/', + isUnderMaintenance, uploader.upload.single('image'), auth, OrgController.createOrganization @@ -15,6 +17,7 @@ router.post( // GET ORG DETAILS BY ID router.get( '/:id', + isUnderMaintenance, auth, OrgController.getOrgDetailsById ) @@ -22,6 +25,7 @@ router.get( // UPDATE ORG DETAILS router.patch( '/:id', + isUnderMaintenance, uploader.upload.single('image'), auth, OrgController.updateOrgDetails @@ -30,6 +34,7 @@ router.patch( // DELETE ORG router.delete( '/:id', + isUnderMaintenance, auth, OrgController.deleteOrg ) @@ -37,15 +42,24 @@ router.delete( // ARCHIVE ORG router.patch( '/archive/:id', + isUnderMaintenance, auth, OrgController.archiveOrg ) // TRIGGER MAINTENANCE MODE router.patch( - '/maintenance', + '/:id/maintenance', auth, OrgController.triggerMaintenance ) +// UPDATE THE ORG SETTINGS +router.patch( + '/:id/settings/update', + isUnderMaintenance, + auth, + OrgController.updateSettings +) + module.exports = router diff --git a/app/routes/post.js b/app/routes/post.js index 05b8277..63fc063 100644 --- a/app/routes/post.js +++ b/app/routes/post.js @@ -1,52 +1,75 @@ require('../../config/mongoose') const express = require('express') const router = express.Router() -const userController = require('../controllers/post') +const postController = require('../controllers/post') const uploader = require('../utils/uploader') const auth = require('../middleware/auth') +const isUnderMaintenance = require('../middleware/maintenance') // CREATE A POST router.post( '/', + isUnderMaintenance, auth, uploader.upload.single('image'), - userController.create + postController.create ) // GET ALL POSTS router.get( '/all_posts', + isUnderMaintenance, auth, - userController.getAllPost + postController.getAllPost ) // UPDATE POST router.patch( '/:id', + isUnderMaintenance, auth, uploader.upload.single('image'), - userController.updatePost + postController.updatePost ) // DELETE A POST BY ID router.delete( '/:id', + isUnderMaintenance, auth, - userController.delete + postController.delete ) // GET POST BY ID router.get( '/:id', + isUnderMaintenance, auth, - userController.getPostById + postController.getPostById ) // UPVOTE POST BY POST ID router.patch( '/upvote/:id', + isUnderMaintenance, auth, - userController.upvote + postController.upvote +) + +// PIN THE POST +router.patch( + '/pin/:id/', + isUnderMaintenance, + auth, + postController.pinPost +) + +// GET ALL PINNED POSTS +router.get( + '/all/pinned/', + isUnderMaintenance, + auth, + postController.getPinned ) module.exports = router diff --git a/app/routes/user.js b/app/routes/user.js index 83c51f9..fc78a60 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -2,11 +2,13 @@ const express = require('express') const router = express.Router() const userController = require('../controllers/user') const auth = require('../middleware/auth') +const isUnderMaintenance = require('../middleware/maintenance') // const email = require('../middleware/email') // create a user router.post( '/', + isUnderMaintenance, // email, userController.createUser ) @@ -14,6 +16,7 @@ router.post( // get user profile router.get( '/me', + isUnderMaintenance, auth, userController.userProfile ) @@ -21,6 +24,7 @@ router.get( // update user info router.patch( '/me', + isUnderMaintenance, auth, userController.userProfileUpdate ) @@ -28,18 +32,21 @@ router.patch( // user forgot password request router.post( '/password_reset', + isUnderMaintenance, userController.forgotPasswordRequest ) // update password router.post( '/password_reset/:token', + isUnderMaintenance, userController.updatePassword ) // get invite link (for sender) router.get( '/invite', + isUnderMaintenance, auth, userController.getInviteLink ) @@ -47,18 +54,21 @@ router.get( // process invite link (for receiver) router.get( '/invite/:token', + isUnderMaintenance, userController.processInvite ) // activate account router.get( '/activate/:token', + isUnderMaintenance, userController.activateAccount ) // delete a user router.delete( '/me', + isUnderMaintenance, auth, userController.userDelete ) From b1f4491d1934ee508fa09a8e1feafbd45df20426 Mon Sep 17 00:00:00 2001 From: Rupesh Krishna Jha Date: Mon, 1 Jun 2020 02:35:50 +0530 Subject: [PATCH 25/42] Implementation of REST API for projects routes and some events, posts routes --- app.js | 2 + app/controllers/event.js | 18 +++++ app/controllers/post.js | 18 +++++ app/controllers/project.js | 116 +++++++++++++++++++++++++++++++ app/controllers/user.js | 135 +++++++++++++++++++++++++++++++++++++ app/models/Event.js | 4 ++ app/models/Project.js | 8 +-- app/models/User.js | 12 ++++ app/routes/event.js | 7 ++ app/routes/post.js | 21 ++++-- app/routes/project.js | 48 +++++++++++++ app/routes/user.js | 30 +++++++++ config/mongoose.js | 2 +- test/event.test.js | 2 +- 14 files changed, 410 insertions(+), 13 deletions(-) create mode 100644 app/controllers/project.js create mode 100644 app/routes/project.js diff --git a/app.js b/app.js index faf242f..f4d03a8 100644 --- a/app.js +++ b/app.js @@ -13,6 +13,7 @@ const eventRouter = require('./app/routes/event') const shortUrlRouter = require('./app/routes/urlShortner') const organizationRouter = require('./app/routes/organisation') const commentRouter = require('./app/routes/comment') +const projectRouter = require('./app/routes/project') const app = express() @@ -34,6 +35,7 @@ app.use('/org', organizationRouter) app.use('/event', eventRouter) app.use('/shortUrl', shortUrlRouter) app.use('/comment', commentRouter) +app.use('/project', projectRouter) // catch 404 and forward to error handler app.use(function (req, res, next) { diff --git a/app/controllers/event.js b/app/controllers/event.js index c6cf405..54f305f 100644 --- a/app/controllers/event.js +++ b/app/controllers/event.js @@ -5,6 +5,7 @@ module.exports = { createEvent: async (req, res, next) => { const event = new Event(req.body) try { + event.createdBy = req.user._id await event.save() res.status(HttpStatus.CREATED).json({ event: event }) } catch (error) { @@ -131,5 +132,22 @@ module.exports = { } catch (error) { HANDLER.handleError(res, next) } + }, + getAllEventByUser: async (req, res, next) => { + const pagination = req.query.pagination ? req.query.pagination : 10 + const currentPage = req.query.page ? req.query.page : 1 + try { + const events = await Event.find({ createdBy: req.user._id }) + .skip((currentPage - 1) * pagination) + .limit(pagination) + .populate('createdBy', '_id name.firstName name.lastName') + .exec() + if (events.length === 0) { + return res.status(HttpStatus.OK).json({ msg: 'No events posted by user!' }) + } + return res.status(HttpStatus.OK).json({ events }) + } catch (error) { + HANDLER.handleError(res, error) + } } } diff --git a/app/controllers/post.js b/app/controllers/post.js index 26ab8d6..5e3ed25 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -133,5 +133,23 @@ module.exports = { } catch (error) { HANDLER.handleError(res, error) } + }, + getPostByUser: async (req, res, next) => { + const pagination = req.query.pagination ? parseInt(req.query.pagination) : 10 + const currentPage = req.query.page ? parseInt(req.query.page) : 1 + try { + const posts = await PostModel.find({ userId: req.user._id }) + .skip((currentPage - 1) * pagination) + .limit(pagination) + .populate('comments', ['content', 'votes']) + .sort({ updatedAt: -1 }) + .exec() + if (posts.length === 0) { + return res.status(HttpStatus.OK).json({ msg: 'No posts found!' }) + } + return res.status(HttpStatus.OK).json({ posts }) + } catch (error) { + HANDLER.handleError(res, error) + } } } diff --git a/app/controllers/project.js b/app/controllers/project.js new file mode 100644 index 0000000..d9c0667 --- /dev/null +++ b/app/controllers/project.js @@ -0,0 +1,116 @@ +const Project = require('../models/Project') +const HANDLER = require('../utils/response-helper') +const HttpStatus = require('http-status-codes') + +module.exports = { + createProject: async (req, res, next) => { + try { + const project = await new Project(req.body) + project.createdBy = req.user._id + await project.save() + return res.status(HttpStatus.CREATED).json({ project }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + getAllProjects: async (req, res, next) => { + const pagination = req.query.pagination ? parseInt(req.query.pagination) : 10 + const page = req.query.page ? parseInt(req.query.page) : 1 + try { + const projects = await Project.find({}) + .skip((page - 1) * pagination) + .limit(pagination) + .populate('createdBy', '_id name.firstName name.lastName email') + .sort({ updatedAt: -1 }) + .exec() + return res.status(HttpStatus.OK).json({ projects }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + getProjectById: async (req, res, next) => { + const { id } = req.params + try { + const project = await Project.findById(id) + .populate('createdBy', '_id name.firstName name.lastName email') + .lean() + .exec() + if (!project) { + return res.status(HttpStatus.OK).json({ msg: 'Post doesn\'t exists!' }) + } + return res.status(HttpStatus.OK).json({ project }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + updateProject: async (req, res, next) => { + const { id } = req.params + const updates = Object.keys(req.body) + const allowedUpdates = [ + 'projectName', + 'description', + 'imgUrl', + 'img', + 'version', + 'links', + 'contributors', + 'maintainers' + ] + const isValidOperation = updates.every((update) => { + return allowedUpdates.includes(update) + }) + try { + const project = await Project.findById(id) + .populate('createdBy', '_id name.firstName name.lastName email') + .exec() + if (!project) { + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such project exits!' }) + } + if (!isValidOperation) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Invalid update!' }) + } + updates.forEach((update) => { + project[update] = req.body[update] + }) + await project.save() + return res.status(HttpStatus.OK).json({ project }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + deleteProject: async (req, res, next) => { + const { id } = req.params + try { + const project = await Project.findById(id) + if (!project) { + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such project exits!' }) + } + // check if admin or user who created this project + if (project.createdBy === req.user._id.toString() || req.user.isAdmin === true) { + await Project.findByIdAndRemove(id) + return res.status(HttpStatus.OK).json({ msg: 'Project deleted!' }) + } + } catch (error) { + HANDLER.handleError(res, error) + } + }, + projectCreatedByUser: async (req, res, next) => { + const pagination = req.query.pagination ? parseInt(req.query.pagination) : 10 + const currentPage = req.query.page ? parseInt(req.query.page) : 1 + try { + const { id } = req.user + const projects = await Project.find({ createdBy: id }) + .skip((currentPage - 1) * pagination) + .limit(pagination) + .populate('createdBy', '_id name.firstName name.lastName email') + .sort({ updatedAt: -1 }) + .exec() + if (projects.length === 0) { + return res.status(HttpStatus.OK).json({ msg: 'No projects created by user yet!' }) + } + return res.status(HttpStatus.OK).json({ projects }) + } catch (error) { + HANDLER.handleError(res, error) + } + } +} diff --git a/app/controllers/user.js b/app/controllers/user.js index 371e296..e6ade7f 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -2,6 +2,7 @@ const User = require('../models/User') const jwt = require('jsonwebtoken') const HttpStatus = require('http-status-codes') const emailController = require('./email') +const HANDLER = require('../utils/response-helper') module.exports = { createUser: async (req, res, next) => { @@ -150,5 +151,139 @@ module.exports = { return res.status(HttpStatus.OK).json({ success: true, msg: 'Redirect user to register in client side!' }) } return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Invalid token!' }) + }, + + // ADD TO THE FOLLOWINGS LIST + addFollowing: async (req, res, next) => { + const { followId } = req.body + try { + if (followId === req.user._id) { + return res.status(HttpStatus.OK).json({ msg: 'You can not follow yourself!' }) + } + const user = await User.findById(req.user.id) + if (!user) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'No such user exists!' }) + } + user.followings.unshift(followId) + await user.save() + next() + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + // ADD TO FOLLOWERS LIST + addFollower: async (req, res, next) => { + const { followId } = req.body + try { + const user = await User.findById(followId) + .populate('followings', ['name.firstName', 'name.lastName', 'email']) + .populate('followers', ['name.firstName', 'name.lastName', 'email']) + .exec() + if (!user) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'No such user exists!' }) + } + // add to the followers list + user.followers.unshift(req.user.id) + await user.save() + return res.status(HttpStatus.OK).json({ user }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + // REMOVE FROM FOLLOWINGS LIST + removeFollowing: async (req, res, next) => { + const { followId } = req.body + try { + const user = await User.findById(req.user._id) + if (!user) { + return res.status(HttpStatus.OK).json({ msg: 'No such user exists!' }) + } + // check if followId is in following list or not + const followingIdArray = user.followings.map(followingId => followingId._id) + const isFollowingIdIndex = followingIdArray.indexOf(followId) + if (isFollowingIdIndex === -1) { + return res.status(HttpStatus.OK).json({ msg: 'You haven\'t followed the user!' }) + } else { + // remove from followings list + user.followings.splice(isFollowingIdIndex, 1) + await user.save() + } + next() + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + // REMOVE FROM FOLLOWERS LIST + removeFollower: async (req, res, next) => { + const { followId } = req.body + try { + const user = await User.findById(followId) + .populate('followings', ['name.firstName', 'name.lastName', 'email']) + .populate('followers', ['name.firstName', 'name.lastName', 'email']) + .exec() + if (!user) { + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such user exists!' }) + } + const followersIdArray = user.followers.map((follower) => follower._id) + const isFollowingIndex = followersIdArray.indexOf(req.user._id) + if (isFollowingIndex === -1) { + return res.status(HttpStatus.OK).json({ msg: 'User is not following!' }) + } + user.followers.splice(isFollowingIndex, 1) + await user.save() + return res.status(HttpStatus.OK).json({ user }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + blockUser: async (req, res, next) => { + const { id } = req.params + try { + const user = await User.findById(req.user._id) + .populate('blocked', ['name.firstName', 'name.lastName', 'email']) + .exec() + if (!user) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Invalid request!' }) + } + // check if admin + if (user.isAdmin === true) { + user.blocked.unshift(id) + await user.save() + return res.status(HttpStatus.OK).json({ user }) + } + // else not permitted + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have permission!' }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + unBlockUser: async (req, res, next) => { + const { id } = req.params + try { + const user = await User.findById(req.user._id) + .populate('blocked', ['name.firstName', 'name.lastName', 'email']) + .exec() + if (!user) { + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such user exists!' }) + } + // if admin + if (user.isAdmin === true) { + const blockedIds = user.blocked.map(item => item._id) + const unblockIndex = blockedIds.indexOf(id) + console.log('UnblockIndex ', unblockIndex) + if (unblockIndex !== -1) { + user.blocked.splice(unblockIndex, 1) + await user.save() + return res.status(HttpStatus.OK).json({ user }) + } + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such user exist!' }) + } + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have permission!' }) + } catch (error) { + HANDLER.handleError(res, error) + } } } diff --git a/app/models/Event.js b/app/models/Event.js index c65a917..6b45638 100644 --- a/app/models/Event.js +++ b/app/models/Event.js @@ -84,6 +84,10 @@ const eventSchema = new Schema({ required: true, default: Date.now() }, + createdBy: { + type: Schema.Types.ObjectId, + ref: 'User' + }, isOnline: { type: Boolean, default: false diff --git a/app/models/Project.js b/app/models/Project.js index cc49ac8..35223c7 100644 --- a/app/models/Project.js +++ b/app/models/Project.js @@ -22,14 +22,10 @@ const projectSchema = new Schema({ type: String, required: true, trim: true, - minlength: 10, validate (short) { if (validator.isEmpty(short)) { throw new Error('Short description for the project is required!') } - if (!validator.isLength(short)) { - throw new Error('Short description should be min 10 characters long!') - } } }, long: { @@ -89,6 +85,10 @@ const projectSchema = new Schema({ type: Schema.Types.ObjectId, ref: 'User' }], + createdBy: { + type: Schema.Types.ObjectId, + ref: 'User' + }, createdAt: { type: Date, default: Date.now() diff --git a/app/models/User.js b/app/models/User.js index d33103f..b936536 100644 --- a/app/models/User.js +++ b/app/models/User.js @@ -136,6 +136,18 @@ const UserSchema = new mongoose.Schema({ } } }, + followers: [{ + type: mongoose.Schema.Types.ObjectId, + ref: 'User' + }], + followings: [{ + type: mongoose.Schema.Types.ObjectId, + ref: 'User' + }], + blocked: [{ + type: mongoose.Schema.Types.ObjectId, + ref: 'User' + }], isAdmin: { type: Boolean, default: false diff --git a/app/routes/event.js b/app/routes/event.js index 75a9288..2f9cda0 100644 --- a/app/routes/event.js +++ b/app/routes/event.js @@ -48,4 +48,11 @@ router.delete( eventController.deleteEvent ) +// GET ALL EVENT POSTED BY A USER +router.get( + '/me/all', + auth, + eventController.getAllEventByUser +) + module.exports = router diff --git a/app/routes/post.js b/app/routes/post.js index 05b8277..0ac2897 100644 --- a/app/routes/post.js +++ b/app/routes/post.js @@ -1,7 +1,7 @@ require('../../config/mongoose') const express = require('express') const router = express.Router() -const userController = require('../controllers/post') +const postController = require('../controllers/post') const uploader = require('../utils/uploader') const auth = require('../middleware/auth') @@ -10,14 +10,14 @@ router.post( '/', auth, uploader.upload.single('image'), - userController.create + postController.create ) // GET ALL POSTS router.get( '/all_posts', auth, - userController.getAllPost + postController.getAllPost ) // UPDATE POST @@ -25,28 +25,35 @@ router.patch( '/:id', auth, uploader.upload.single('image'), - userController.updatePost + postController.updatePost ) // DELETE A POST BY ID router.delete( '/:id', auth, - userController.delete + postController.delete ) // GET POST BY ID router.get( '/:id', auth, - userController.getPostById + postController.getPostById ) // UPVOTE POST BY POST ID router.patch( '/upvote/:id', auth, - userController.upvote + postController.upvote +) + +// GET POST PER USER +router.get( + '/me/all', + auth, + postController.getPostByUser ) module.exports = router diff --git a/app/routes/project.js b/app/routes/project.js new file mode 100644 index 0000000..0874eed --- /dev/null +++ b/app/routes/project.js @@ -0,0 +1,48 @@ +const express = require('express') +const projectController = require('../controllers/project') +const router = express.Router() +const auth = require('../middleware/auth') + +// ADD PROJECT +router.post( + '/', + auth, + projectController.createProject +) + +// GET ALL PROJECTS +router.get( + '/', + auth, + projectController.getAllProjects +) + +// GET PROJECT BY ID +router.get( + '/:id', + auth, + projectController.getProjectById +) + +// UPDATE PROJECT INFO +router.patch( + '/:id', + auth, + projectController.updateProject +) + +// DELETE PROJECT +router.delete( + '/:id', + auth, + projectController.deleteProject +) + +// GET PROJECTS CREATED BY A USER +router.get( + '/me/all', + auth, + projectController.projectCreatedByUser +) + +module.exports = router diff --git a/app/routes/user.js b/app/routes/user.js index 83c51f9..4da9505 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -63,4 +63,34 @@ router.delete( userController.userDelete ) +// follow the user +router.patch( + '/follow', + auth, + userController.addFollowing, + userController.addFollower +) + +// unFollow the user +router.patch( + '/unfollow', + auth, + userController.removeFollowing, + userController.removeFollower +) + +// BLOCK THE USER +router.patch( + '/block/:id', + auth, + userController.blockUser +) + +// UNBLOCK THE USER +router.patch( + '/unblock/:id', + auth, + userController.unBlockUser +) + module.exports = router diff --git a/config/mongoose.js b/config/mongoose.js index 780c802..2904b19 100644 --- a/config/mongoose.js +++ b/config/mongoose.js @@ -1,6 +1,6 @@ const mongoose = require('mongoose') -mongoose.connect('mongodb://rupesh:abc123@ds125953.mlab.com:25953/testing', { +mongoose.connect(process.env.DATABASE_URL, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, diff --git a/test/event.test.js b/test/event.test.js index b54789a..1381c7d 100644 --- a/test/event.test.js +++ b/test/event.test.js @@ -213,7 +213,7 @@ test('Should update event', async () => { // Testing for the RSVP test('Should submit the RSVP', async () => { const response = await request(app) - .post(`/event/rsvp/${testEventId}`) + .patch(`/event/rsvp/${testEventId}`) .set('Authorization', `Bearer ${testUser.tokens[0].token}`) .send(demoRsvp) .expect(HttpStatus.OK) From a21b1d40c560477dcf5b26742fe78f676c60adb0 Mon Sep 17 00:00:00 2001 From: Rupesh Krishna Jha Date: Thu, 4 Jun 2020 18:56:21 +0530 Subject: [PATCH 26/42] implementation of permission mechanism, insight page, refactor of some parts --- app/controllers/comment.js | 7 ++-- app/controllers/event.js | 3 ++ app/controllers/organization.js | 67 ++++++++++++++++++++++++++++++--- app/controllers/post.js | 7 ++-- app/controllers/user.js | 33 +++++++++------- app/models/Organisation.js | 2 +- app/routes/organisation.js | 14 +++++++ app/routes/user.js | 7 ++++ app/utils/permission.js | 23 +++++++++++ app/utils/uploader.js | 8 +--- test/user.test.js | 10 +++++ 11 files changed, 148 insertions(+), 33 deletions(-) create mode 100644 app/utils/permission.js diff --git a/app/controllers/comment.js b/app/controllers/comment.js index b18e274..479e823 100644 --- a/app/controllers/comment.js +++ b/app/controllers/comment.js @@ -1,6 +1,7 @@ const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') const CommentModel = require('../models/Comment') +const permission = require('../utils/permission') module.exports = { // CREATE COMMENT (ISSUE IN CREATE COMMENT ) @@ -21,14 +22,13 @@ module.exports = { // DELETE COMMENT delete: async (req, res, next) => { const { id } = req.params - const userId = req.user.id.toString() try { const comment = await CommentModel.findById(id) if (!comment) { return res.status(HttpStatus.NOT_FOUND).json({ error: 'No comment exist' }) } // Add rights for admins and moderators as well (TODO) - if (JSON.stringify(comment.userId) !== JSON.stringify(userId)) { + if (!permission.check(req, res, comment.userId)) { return res.status(HttpStatus.FORBIDDEN).json({ message: 'Bad delete request' }) } await CommentModel.findByIdAndRemove(id) @@ -41,7 +41,6 @@ module.exports = { // UPDATE COMMENT update: async (req, res, next) => { const { id } = req.params - const userId = req.user.id.toString() const updates = Object.keys(req.body) const valid = ['content'] const isValidOperation = updates.every((update) => { @@ -56,7 +55,7 @@ module.exports = { return res.status(HttpStatus.NOT_FOUND).json({ error: 'No comment exist' }) } // also add admin or moderator control (TODO) - if (JSON.stringify(comment.userId) !== JSON.stringify(userId)) { + if (!permission.check(req, res, comment.userId)) { return res.status(HttpStatus.BAD_REQUEST).json({ error: 'Wrong update' }) } updates.forEach(update => { diff --git a/app/controllers/event.js b/app/controllers/event.js index ae7f02b..0027fb5 100644 --- a/app/controllers/event.js +++ b/app/controllers/event.js @@ -1,6 +1,8 @@ const Event = require('../models/Event') const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') +// const permission = require('../utils/permission') + module.exports = { createEvent: async (req, res, next) => { const event = new Event(req.body) @@ -19,6 +21,7 @@ module.exports = { if (!event) { return res.status(HttpStatus.BAD_REQUEST).json({ message: 'No post exists' }) } + // check for permission (TODO AFTER PREVIOUS PR MERGED) updates.forEach(update => { event[update] = req.body[update] }) diff --git a/app/controllers/organization.js b/app/controllers/organization.js index 4131416..28da574 100644 --- a/app/controllers/organization.js +++ b/app/controllers/organization.js @@ -2,6 +2,10 @@ const Organization = require('../models/Organisation') const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') const helper = require('../utils/uploader') +const User = require('../models/User') +const Project = require('../models/Project') +const Event = require('../models/Event') +const permission = require('../utils/permission') module.exports = { createOrganization: async (req, res, next) => { @@ -20,7 +24,7 @@ module.exports = { updateOrgDetails: async (req, res, next) => { const { id } = req.params const updates = Object.keys(req.body) - const allowedUpdates = ['name', 'description', 'contactInfo', 'image', 'adminInfo', 'moderatorInfo'] + const allowedUpdates = ['name', 'description', 'contactInfo', 'image', 'imgUrl', 'adminInfo', 'moderatorInfo'] const isValidOperation = updates.every((update) => { return allowedUpdates.includes(update) }) @@ -30,6 +34,10 @@ module.exports = { } try { const org = await Organization.findById(id) + // check for permission (ONLY ADMINS CAN UPDATE) + if (!permission.check(req, res)) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have the permission' }) + } updates.forEach(update => { org[update] = req.body[update] }) @@ -46,11 +54,11 @@ module.exports = { getOrgDetailsById: async (req, res, next) => { const { id } = req.params try { - const orgData = await Organization - .findById(id) + const orgData = await Organization.findById(id) .populate('adminInfo', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) .populate('moderatorInfo', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) .sort({ createdAt: -1 }) + .lean() .exec() if (!orgData) { return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such organization exists!' }) @@ -68,7 +76,11 @@ module.exports = { if (!org) { return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such organization exists!' }) } - res.status(HttpStatus.OK).json({ organization: org }) + // check for permission + if (!permission.check(req, res)) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have the permission!' }) + } + return res.status(HttpStatus.OK).json({ organization: org }) } catch (error) { HANDLER.handleError(res, error) } @@ -83,7 +95,7 @@ module.exports = { } org.isArchived = true await org.save() - res.status(HttpStatus.OK).json({ organization: org }) + return res.status(HttpStatus.OK).json({ organization: org }) } catch (error) { HANDLER.handleError(res, error) } @@ -105,5 +117,50 @@ module.exports = { } else { res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have access to triggerMaintenance!' }) } + }, + getOrgOverView: async (req, res, next) => { + const orgOverView = {} + try { + const org = await Organization.find({}) + if (!org) { + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No org exists!' }) + } + orgOverView.admins = org[0].adminInfo.length + orgOverView.members = await User.find({}).lean().count() + orgOverView.projects = await Project.find({}).lean().count() + orgOverView.events = await Event.find({}).lean().count() + return res.status(HttpStatus.OK).json({ orgOverView }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + getMembers: async (req, res, next) => { + try { + const { search } = req.query + if (search) { + const regex = search.split(' ') + const member = await User.find({ $or: [{ 'name.firstName': regex }, { 'name.lastName': regex }] }) + .select('name email isAdmin info.about.designation') + .lean() + .sort({ createdAt: -1 }) + .exec() + if (!member) { + return res.status(HttpStatus.OK).json({ msg: 'Member not found!' }) + } + return res.status(HttpStatus.OK).json({ member }) + } else { + const members = await User.find({}) + .select('name email isAdmin info.about.designation') + .lean() + .sort({ createdAt: -1 }) + .exec() + if (members.length === 0) { + return res.status(HttpStatus.OK).json({ msg: 'No members joined yet!' }) + } + return res.status(HttpStatus.OK).json({ members }) + } + } catch (error) { + HANDLER.handleError(res, error) + } } } diff --git a/app/controllers/post.js b/app/controllers/post.js index 5c6fdda..d5bb633 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -2,6 +2,7 @@ const PostModel = require('../models/Post') const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') const imgUploadHelper = require('../utils/uploader') +const permission = require('../utils/permission') module.exports = { // CREATE POST @@ -23,14 +24,13 @@ module.exports = { // DELETE POST delete: async (req, res, next) => { const { id } = req.params - const userId = req.user.id.toString() try { const post = await PostModel.findById(id) if (!post) { return res.status(HttpStatus.NOT_FOUND).json({ message: 'No post exists' }) } // TODO ADD ADMIN RIGHTS AS WELL - if (JSON.stringify(userId) !== JSON.stringify(post.userId)) { + if (!permission.check(req, res, post.userId)) { return res.status(HttpStatus.FORBIDDEN).json({ message: 'Bad delete request' }) } await PostModel.findByIdAndRemove(id) @@ -45,7 +45,6 @@ module.exports = { const { id } = req.params const updates = Object.keys(req.body) const allowedUpdates = ['content', 'imgUrl'] - const userId = req.user.id.toString() const isValidOperation = updates.every((update) => { return allowedUpdates.includes(update) }) @@ -58,7 +57,7 @@ module.exports = { if (!post) { return res.status(HttpStatus.BAD_REQUEST).json({ message: 'No post exists' }) } - if (JSON.stringify(userId) !== JSON.stringify(post.userId)) { + if (!permission.check(req, res, post.userId)) { return res.status(HttpStatus.FORBIDDEN).json({ message: 'Bad update request' }) } updates.forEach(update => { diff --git a/app/controllers/user.js b/app/controllers/user.js index 371e296..ff43642 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -2,6 +2,8 @@ const User = require('../models/User') const jwt = require('jsonwebtoken') const HttpStatus = require('http-status-codes') const emailController = require('./email') +const permission = require('../utils/permission') +const HANDLER = require('../utils/response-helper') module.exports = { createUser: async (req, res, next) => { @@ -27,11 +29,8 @@ module.exports = { const allowedUpdates = [ 'name', 'email', - 'password', - 'company', - 'website', - 'location', - 'about' + 'phone', + 'info' ] const isValidOperation = updates.every((update) => { return allowedUpdates.includes(update) @@ -46,9 +45,9 @@ module.exports = { req.user[update] = req.body[update] }) await req.user.save() - res.status(HttpStatus.OK).json({ data: req.user }) + return res.status(HttpStatus.OK).json({ data: req.user }) } catch (error) { - res.status(HttpStatus.BAD_REQUEST).json({ error }) + return res.status(HttpStatus.BAD_REQUEST).json({ error }) } }, @@ -100,17 +99,25 @@ module.exports = { } }, - logout: (req, res, next) => { - res.status(HttpStatus.OK).json({ success: 'ok' }) + logout: async (req, res, next) => { + try { + req.user.tokens = [] + await req.user.save() + return res.status(HttpStatus.OK).json({ msg: 'User logged out Successfully!' }) + } catch (error) { + HANDLER.handleError(res, error) + } }, userDelete: async (req, res, next) => { try { - await req.user.remove() - res.send({ data: 'user deletion successful', user: req.user }) + if (permission.check(req, res)) { + await req.user.remove() + return res.send({ data: 'user deletion successful', user: req.user }) + } + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have permission!' }) } catch (error) { - console.log(error) - res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error }) + return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error }) } }, diff --git a/app/models/Organisation.js b/app/models/Organisation.js index 4117e6d..190233f 100644 --- a/app/models/Organisation.js +++ b/app/models/Organisation.js @@ -47,7 +47,7 @@ const orgSchema = new Schema({ } }, image: { - type: Buffer, + data: Buffer, contentType: String }, imgUrl: { diff --git a/app/routes/organisation.js b/app/routes/organisation.js index f9fccd7..c2cdf23 100644 --- a/app/routes/organisation.js +++ b/app/routes/organisation.js @@ -48,4 +48,18 @@ router.patch( OrgController.triggerMaintenance ) +// GET ORG OVERVIEW FOR INSIGHT PAGE +router.get( + '/overview/all', + auth, + OrgController.getOrgOverView +) + +// GET MEMBERS FOR INSIGHT PAGE +router.get( + '/members/all', + auth, + OrgController.getMembers +) + module.exports = router diff --git a/app/routes/user.js b/app/routes/user.js index 83c51f9..c8497d7 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -63,4 +63,11 @@ router.delete( userController.userDelete ) +// LOGOUT USER +router.post( + '/logout', + auth, + userController.logout +) + module.exports = router diff --git a/app/utils/permission.js b/app/utils/permission.js new file mode 100644 index 0000000..f00a933 --- /dev/null +++ b/app/utils/permission.js @@ -0,0 +1,23 @@ +const HANDLER = require('../utils/response-helper') + +module.exports = { + check: async (req, res, creatorId = 0) => { + const userId = req.user.id.toString() + try { + // if user is an admin + if (req.user.isAdmin) { + console.log('user is admin! ') + return true + } + // if user is post/event/project/comment creator + if (JSON.stringify(userId) === JSON.stringify(creatorId)) { + console.log('user is creator!') + return true + } + // else + return false + } catch (error) { + HANDLER.handleError(res, error) + } + } +} diff --git a/app/utils/uploader.js b/app/utils/uploader.js index e892aaa..239abc1 100644 --- a/app/utils/uploader.js +++ b/app/utils/uploader.js @@ -36,10 +36,6 @@ exports.upload = multer({ exports.mapToDb = (req, db) => { const img = fs.readFileSync(req.file.path) - const encodedImage = img.toString('base64') - const image = { - contentType: req.file.mimetype, - image: Buffer.from(encodedImage, 'base64') - } - db.image = image + db.image.data = img + db.image.contentType = 'image/png' } diff --git a/test/user.test.js b/test/user.test.js index 6012536..638b2ae 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -233,6 +233,16 @@ test('Should validate the invite link token ', async () => { .expect(HttpStatus.OK) }) +/* Logout the user */ +test('Should logout the user ', async (done) => { + await request(app) + .post('/user/logout') + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send() + .expect(HttpStatus.OK) + done() +}) + /** * TODO: FIX ERROR * This is a temporary fix to issue: From 1257b9f2252d931de314b9026513139b79260e31 Mon Sep 17 00:00:00 2001 From: Rupesh Krishna Jha Date: Tue, 2 Jun 2020 19:39:59 +0530 Subject: [PATCH 27/42] Implemented test cases for projects and updated existing test cases --- app/controllers/comment.js | 6 +- app/controllers/event.js | 30 ++--- app/controllers/post.js | 13 +-- app/controllers/project.js | 13 +-- app/controllers/user.js | 2 +- app/utils/paginate.js | 10 ++ test/event.test.js | 73 +++++++++++-- test/post.test.js | 17 ++- test/project.test.js | 218 +++++++++++++++++++++++++++++++++++++ test/user.test.js | 80 ++++++++++++++ 10 files changed, 410 insertions(+), 52 deletions(-) create mode 100644 app/utils/paginate.js create mode 100644 test/project.test.js diff --git a/app/controllers/comment.js b/app/controllers/comment.js index bf5f030..2b9aafd 100644 --- a/app/controllers/comment.js +++ b/app/controllers/comment.js @@ -1,6 +1,7 @@ const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') const CommentModel = require('../models/Comment') +const helper = require('../utils/paginate') module.exports = { // CREATE COMMENT (ISSUE IN CREATE COMMENT ) @@ -71,12 +72,9 @@ module.exports = { // GET ALL COMMENTS OF A POST BY postId getCommentByPost: async (req, res, next) => { - const currentPage = req.query.page ? parseInt(req.query.page) : 1 const { id } = req.params try { - const comments = await CommentModel.find({ postId: id }) - .skip((currentPage - 1) * 5) - .limit(5) + const comments = await CommentModel.find({ postId: id }, {}, helper.paginate(req)) .populate('userId', ['name.firstName', 'name.lastName']) .sort({ updatedAt: -1 }) .lean() diff --git a/app/controllers/event.js b/app/controllers/event.js index 54f305f..da16a39 100644 --- a/app/controllers/event.js +++ b/app/controllers/event.js @@ -1,6 +1,8 @@ const Event = require('../models/Event') const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') +const helper = require('../utils/paginate') + module.exports = { createEvent: async (req, res, next) => { const event = new Event(req.body) @@ -12,6 +14,7 @@ module.exports = { res.status(HttpStatus.BAD_REQUEST).json({ error: error }) } }, + updateEvent: async (req, res) => { const { id } = req.params const updates = Object.keys(req.body) @@ -29,6 +32,7 @@ module.exports = { HANDLER.handleError(res, error) } }, + rsvp: async (req, res) => { const { yes, no, maybe } = req.body const { id } = req.params @@ -75,6 +79,7 @@ module.exports = { HANDLER.handleError(res, error) } }, + GetEventById: async (req, res, next) => { const { id } = req.params try { @@ -87,14 +92,12 @@ module.exports = { next(error) } }, + GetAllEvent: async (req, res, next) => { - const pagination = req.query.pagination ? parseInt(req.query.pagination) : 10 - const currentPage = req.query.page ? parseInt(req.query.page) : 1 try { - const EventData = await Event.find({}) + const EventData = await Event.find({}, {}, helper.paginate(req)) + .sort({ eventDate: -1 }) .lean() - .skip((currentPage - 1) * pagination) - .limit(pagination) if (!EventData) { return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such Event is available!' }) } @@ -103,6 +106,7 @@ module.exports = { HANDLER.handleError(res, error) } }, + deleteEvent: async (req, res, next) => { const { id } = req.params try { @@ -116,13 +120,11 @@ module.exports = { HANDLER.handleError(res, error) } }, + UpComingEvents: async (req, res, next) => { - const pageSize = req.query.pagination ? parseInt(req.query.pagination) : 10 - const currentPage = req.query.page ? parseInt(req.query.page) : 1 try { - const events = await Event.find({ eventDate: { $gt: Date.now() } }) - .skip((currentPage - 1) * pageSize) - .limit(pageSize) + const events = await Event.find({ eventDate: { $gt: Date.now() } }, {}, helper.paginate(req)) + .sort({ eventDate: -1 }) .exec() console.log('Upcoming events ', events) if (events.length === 0) { @@ -133,13 +135,11 @@ module.exports = { HANDLER.handleError(res, next) } }, + getAllEventByUser: async (req, res, next) => { - const pagination = req.query.pagination ? req.query.pagination : 10 - const currentPage = req.query.page ? req.query.page : 1 try { - const events = await Event.find({ createdBy: req.user._id }) - .skip((currentPage - 1) * pagination) - .limit(pagination) + const events = await Event.find({ createdBy: req.user._id }, {}, helper.paginate(req)) + .sort({ eventDate: -1 }) .populate('createdBy', '_id name.firstName name.lastName') .exec() if (events.length === 0) { diff --git a/app/controllers/post.js b/app/controllers/post.js index 5e3ed25..0439bf1 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -2,6 +2,7 @@ const PostModel = require('../models/Post') const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') const imgUploadHelper = require('../utils/uploader') +const helper = require('../utils/paginate') module.exports = { // CREATE POST @@ -94,12 +95,8 @@ module.exports = { // GET ALL THE POSTS getAllPost: async (req, res, next) => { - const pagination = req.query.pagination ? parseInt(req.query.pagination) : 10 - const currentPage = req.query.page ? parseInt(req.query.page) : 1 try { - const posts = await PostModel.find({}) - .skip((currentPage - 1) * pagination) - .limit(pagination) + const posts = await PostModel.find({}, {}, helper.paginate(req)) .populate('userId', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) .sort({ updatedAt: -1 }) .exec() @@ -135,12 +132,8 @@ module.exports = { } }, getPostByUser: async (req, res, next) => { - const pagination = req.query.pagination ? parseInt(req.query.pagination) : 10 - const currentPage = req.query.page ? parseInt(req.query.page) : 1 try { - const posts = await PostModel.find({ userId: req.user._id }) - .skip((currentPage - 1) * pagination) - .limit(pagination) + const posts = await PostModel.find({ userId: req.user._id }, {}, helper.paginate(req)) .populate('comments', ['content', 'votes']) .sort({ updatedAt: -1 }) .exec() diff --git a/app/controllers/project.js b/app/controllers/project.js index d9c0667..e354788 100644 --- a/app/controllers/project.js +++ b/app/controllers/project.js @@ -1,6 +1,7 @@ const Project = require('../models/Project') const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') +const helper = require('../utils/paginate') module.exports = { createProject: async (req, res, next) => { @@ -14,12 +15,8 @@ module.exports = { } }, getAllProjects: async (req, res, next) => { - const pagination = req.query.pagination ? parseInt(req.query.pagination) : 10 - const page = req.query.page ? parseInt(req.query.page) : 1 try { - const projects = await Project.find({}) - .skip((page - 1) * pagination) - .limit(pagination) + const projects = await Project.find({}, {}, helper.paginate(req)) .populate('createdBy', '_id name.firstName name.lastName email') .sort({ updatedAt: -1 }) .exec() @@ -95,13 +92,9 @@ module.exports = { } }, projectCreatedByUser: async (req, res, next) => { - const pagination = req.query.pagination ? parseInt(req.query.pagination) : 10 - const currentPage = req.query.page ? parseInt(req.query.page) : 1 try { const { id } = req.user - const projects = await Project.find({ createdBy: id }) - .skip((currentPage - 1) * pagination) - .limit(pagination) + const projects = await Project.find({ createdBy: id }, {}, helper.paginate(req)) .populate('createdBy', '_id name.firstName name.lastName email') .sort({ updatedAt: -1 }) .exec() diff --git a/app/controllers/user.js b/app/controllers/user.js index e6ade7f..eed4310 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -279,7 +279,7 @@ module.exports = { await user.save() return res.status(HttpStatus.OK).json({ user }) } - return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such user exist!' }) + return res.status(HttpStatus.NOT_FOUND).json({ user }) } return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have permission!' }) } catch (error) { diff --git a/app/utils/paginate.js b/app/utils/paginate.js new file mode 100644 index 0000000..69c0954 --- /dev/null +++ b/app/utils/paginate.js @@ -0,0 +1,10 @@ +module.exports = { + paginate: (req) => { + const query = {} + const pagination = req.query.pagination ? parseInt(req.query.pagination) : 10 + const currentPage = req.query.page ? parseInt(req.query.page) : 1 + query.skip = (currentPage - 1) * pagination + query.limit = pagination + return query + } +} diff --git a/test/event.test.js b/test/event.test.js index 1381c7d..53e5a9b 100644 --- a/test/event.test.js +++ b/test/event.test.js @@ -108,14 +108,15 @@ beforeAll(async (done) => { * This deletes all the existing user in database, * and creates a new user in database with the provided details. */ -beforeEach(async () => { +beforeEach(async (done) => { await Event.deleteMany() await new Event(testEvent).save() await User.deleteMany() await new User(testUser).save() + done() }) -test('Should signup new user', async () => { +test('Should signup new user', async (done) => { const response = await request(app) .post('/user') .send(demoUser) @@ -150,10 +151,11 @@ test('Should signup new user', async () => { } }) expect(user.password).not.toBe('abc12345') // to check hashing + done() }) /** Testing user login */ -test('Login existing user', async () => { +test('Login existing user', async (done) => { const response = await request(app) .post('/auth/login') .send({ @@ -164,11 +166,12 @@ test('Login existing user', async () => { const user = await User.findById(testUserId) expect(response.body.token).toBe(user.tokens[1].token) + done() }) /** * Testing event creation */ -test('Should create new event', async () => { +test('Should create new event', async (done) => { const response = await request(app) .post('/event') .set('Authorization', `Bearer ${testUser.tokens[0].token}`) @@ -194,11 +197,14 @@ test('Should create new event', async () => { eventDate: demoEvent.eventDate } }) + done() }) + /** * Testing event updation */ -test('Should update event', async () => { + +test('Should update event', async (done) => { const response = await request(app) .patch(`/event/${testEventId}`) .set('Authorization', `Bearer ${testUser.tokens[0].token}`) @@ -208,10 +214,14 @@ test('Should update event', async () => { // Assert that db was changed const updatedEvent = await Event.findById(response.body.event._id) expect(updatedEvent).not.toBeNull() + done() }) -// Testing for the RSVP -test('Should submit the RSVP', async () => { +/** + * Testing for the RSVP + */ + +test('Should submit the RSVP', async (done) => { const response = await request(app) .patch(`/event/rsvp/${testEventId}`) .set('Authorization', `Bearer ${testUser.tokens[0].token}`) @@ -219,8 +229,14 @@ test('Should submit the RSVP', async () => { .expect(HttpStatus.OK) const rsvpData = await Event.findById(response.body.rsvpData._id) expect(rsvpData).not.toBeNull() + done() }) -test('Should delete event', async () => { + +/** + * Testing for event deletion + */ + +test('Should delete event', async (done) => { await request(app) .delete(`/event/${testEventId}`) .set('Authorization', `Bearer ${testUser.tokens[0].token}`) @@ -230,23 +246,60 @@ test('Should delete event', async () => { // Assert that event was deleted const event = await Event.findById(testEventId) expect(event).toBeNull() + done() }) -test('Should get event by id', async () => { +/** + * Testing for get event by id + */ + +test('Should get event by id', async (done) => { await request(app) .get(`/event/${testEventId}`) .set('Authorization', `Bearer ${testUser.tokens[0].token}`) .send() .expect(HttpStatus.OK) + done() }) -test('Should get all the event', async () => { +/** + * Testing for get all events + */ + +test('Should get all the event', async (done) => { await request(app) .get('/event/all') .set('Authorization', `Bearer ${testUser.tokens[0].token}`) .send() .expect(HttpStatus.OK) + done() }) + +/** + * Testing for the upcoming event + */ +test('Should get all the upcoming event', async (done) => { + await request(app) + .get('/event/upcoming') + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send() + .expect(HttpStatus.OK) + done() +}) + +/** + * Testing for the events created by a particular user + */ + +test('Should get all the events created by user', async (done) => { + await request(app) + .get('/event/me/all') + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send() + .expect(HttpStatus.OK) + done() +}) + /** * TODO: FIX ERROR * This is a temporary fix to issue: diff --git a/test/post.test.js b/test/post.test.js index 5191495..e5cee05 100644 --- a/test/post.test.js +++ b/test/post.test.js @@ -173,10 +173,10 @@ test('Should delete post', async (done) => { }) /** - * Testing GET post + * Testing GET post by id */ -test('Should get post for user', async (done) => { +test('Should get single post by id', async (done) => { await request(app) .get(`/post/${testPostId}`) .set('Authorization', `Bearer ${token}`) @@ -224,6 +224,19 @@ test('Should update the Post data', async (done) => { done() }) +/** + * Testing get post of a particular user + */ + +test('Should retrieve all posts created by a user', async (done) => { + await request(app) + .get('/post/me/all') + .set('Authorization', `Bearer ${token}`) + .send() + .expect(HttpStatus.OK) + done() +}) + /** * TODO: FIX ERROR * This is a temporary fix to issue: diff --git a/test/project.test.js b/test/project.test.js new file mode 100644 index 0000000..1941b58 --- /dev/null +++ b/test/project.test.js @@ -0,0 +1,218 @@ +const app = require('../app') +const mongoose = require('mongoose') +const jwt = require('jsonwebtoken') +const HttpStatus = require('http-status-codes') +const request = require('supertest') +const Project = require('../app/models/Project') +const User = require('../app/models/User') +const randomDigit = Math.floor(Math.random() * 90 + 10) +const pagination = 10 +const page = 1 + +const testUserId = new mongoose.Types.ObjectId() +const testProjectId = new mongoose.Types.ObjectId() +let token = '' + +const demoProject = { + projectName: 'testing project', + description: { + short: 'Short description should be min 10 characters long!', + long: 'this is long description' + }, + version: '1.0.1', + links: [{ + githubLink: 'https://github.com/codeuino' + }] +} + +const testProject = { + _id: testProjectId, + ...demoProject +} + +const updateProject = { + projectName: 'testing project update', + description: { + short: 'Short description should be min 10 characters long!', + long: 'this is long description' + }, + version: '1.0.3', + links: [{ + githubLink: 'https://github.com/codeuino' + }] +} + +const demoUser = { + name: { + firstName: 'test', + lastName: 'test' + }, + email: `test${randomDigit}@mailinator.com`, + phone: `12345678${randomDigit}`, + password: 'abc12345', + info: { + about: { + shortDescription: 'this is short description', + longDescription: 'this is a very long description', + website: 'https://www.google.com', + designation: 'software engg', + skills: [ + 'c++', + 'java' + ], + education: [{ + school: { + schoolName: 'firstSchoolName', + year: '2017-2021' + } + }, + { + school: { + schoolName: 'secondSchoolName', + year: '2007-2014' + } + } + ], + location: 'location' + } + } +} + +const testUser = { + _id: testUserId, + ...demoUser, + email: `test${randomDigit}@mailinator.com`, + phone: `12345678${randomDigit}`, + tokens: [{ + token: jwt.sign({ + _id: testUserId + }, process.env.JWT_SECRET) + }] +} + +let server +/** + * This will pe performed once at the beginning of the test + */ +beforeAll(async (done) => { + await Project.deleteMany() + await new User(testUser).save() + server = app.listen(4000, () => { + global.agent = request.agent(server) + }) + const response = await request(app) + .post('/auth/login') + .send({ + email: testUser.email, + password: testUser.password + }) + token = response.body.token + done() +}) + +/** + * This deletes all the existing project in database, + * and creates a new project in database with the provided details. + */ +beforeEach(async () => { + await Project.deleteMany() + await new Project(testProject).save() +}) + +/** + * Testing project creation + */ +test('Should create new project', async (done) => { + const response = await request(app) + .post('/project') + .set('Authorization', `Bearer ${token}`) + .send(demoProject) + .expect(HttpStatus.CREATED) + + // Assert that db was changed + const project = await Project.findById(response.body.project._id) + expect(project).not.toBeNull() + + const userId = response.body.project.createdBy + + // Assertions about the response + expect(response.body).toMatchObject({ + project: { + projectName: demoProject.projectName, + description: { + short: demoProject.description.short, + long: demoProject.description.long + }, + version: demoProject.version, + links: [{ + githubLink: demoProject.links[0].githubLink + }], + createdBy: userId + } + }) + done() +}) + +/** + * Testing get all the projects + */ +test('Should get all projects', async (done) => { + await request(app) + .get(`/project?pagination=${pagination}&page=${page}`) + .set('Authorization', `Bearer ${token}`) + .send() + .expect(HttpStatus.OK) + done() +}) + +/** + * Testing GET project by id + */ + +test('Should get project by id', async (done) => { + await request(app) + .get(`/project/${testProjectId}`) + .set('Authorization', `Bearer ${token}`) + .send() + .expect(HttpStatus.OK) + done() +}) + +/** + * Get project of a user + */ + +test('Should get all the project created by a user', async (done) => { + await request(app) + .get('/project/me/all') + .set('Authorization', `Bearer ${token}`) + .send() + .expect(HttpStatus.OK) + done() +}) + +/** + * Testing project update + */ +test('Should update the project info', async (done) => { + await request(app) + .patch(`/project/${testProjectId}`) + .set('Authorization', `Bearer ${token}`) + .send(updateProject) + .expect(HttpStatus.OK) + done() +}) + +/** + * TODO: FIX ERROR + * This is a temporary fix to issue: + * Jest has detected the following 1 open handle potentially keeping Jest from exiting + */ +afterAll(async () => { + // close server + await server.close() + // delete all the projects project testing + await Project.deleteMany() + // Closing the DB connection allows Jest to exit successfully. + await mongoose.connection.close() +}) diff --git a/test/user.test.js b/test/user.test.js index 6012536..4430ec1 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -45,21 +45,47 @@ const demoUser = { } const testUserId = new mongoose.Types.ObjectId() +const testFollowUserId = new mongoose.Types.ObjectId() const testUser = { _id: testUserId, ...demoUser, email: 'test@mailinator.com', phone: '1234567891', + isAdmin: true, + followers: [], + followings: [], + blocked: [], tokens: [{ token: jwt.sign({ _id: testUserId }, 'process.env.JWT_SECRET') }] } + +const testFollowUser = { + _id: testFollowUserId, + ...demoUser, + ...demoUser.name.firstName = 'follow_user', + ...demoUser.name.lastName = 'test', + email: 'test43@mailinator.com', + phone: '1274567391', + isAdmin: false, + followers: [], + followings: [], + blocked: [], + tokens: [{ + token: jwt.sign({ + _id: testFollowUserId + }, 'process.env.JWT_SECRET') + }] +} + let server + /** * This will pe performed once at the beginning of the test */ + beforeAll(async (done) => { await User.deleteMany() server = app.listen(4000, () => { @@ -75,6 +101,7 @@ beforeAll(async (done) => { beforeEach(async () => { await User.deleteMany() await new User(testUser).save() + await new User(testFollowUser).save() }) /** @@ -233,6 +260,59 @@ test('Should validate the invite link token ', async () => { .expect(HttpStatus.OK) }) +/* Follow the user */ +test('Should follow the user', async (done) => { + await request(app) + .patch('/user/follow') + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send({ + followId: testFollowUserId + }) + .expect(HttpStatus.OK) + // Assert the db change + const user = await User.findById(testFollowUserId) + expect(user.followers[0] === testUserId) + done() +}) + +/* unFollow the user */ +test('Should unFollow the user', async (done) => { + await request(app) + .patch('/user/unfollow') + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send({ + followId: testFollowUserId + }) + .expect(HttpStatus.OK) + // Assert that db change + const user = await User.findById(testFollowUserId) + expect(user.followers === []) + done() +}) + +/* Block the user */ +test('Should block the user', async (done) => { + const response = await request(app) + .patch(`/user/block/${testFollowUserId}`) + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send() + .expect(HttpStatus.OK) + // Assert the db changed + expect(response.body.user.blocked[0]._id === testFollowUserId) + done() +}) + +/* UnBlock the user */ +test('Should UnBlock the user', async (done) => { + const response = await request(app) + .patch(`/user/unblock/${testFollowUserId}`) + .set('Authorization', `Bearer ${testUser.tokens[0].token}`) + .send() + // Assert the db changed + expect(response.body.user.blocked === []) + done() +}) + /** * TODO: FIX ERROR * This is a temporary fix to issue: From 49b40be4cfb394e87c42bde6277b7e2100f56caa Mon Sep 17 00:00:00 2001 From: Asel Date: Mon, 8 Jun 2020 21:34:18 +0530 Subject: [PATCH 28/42] Implementation of /proposal route and proposal test cases --- app.js | 18 +++- app/controllers/post.js | 64 +++++++++--- app/controllers/proposal.js | 150 ++++++++++++++++++++++++++++ app/models/Proposal.js | 48 +++++++++ app/routes/proposal.js | 27 +++++ package-lock.json | 93 +++++++++++++++++ package.json | 2 + test/proposal.test.js | 194 ++++++++++++++++++++++++++++++++++++ 8 files changed, 580 insertions(+), 16 deletions(-) create mode 100644 app/controllers/proposal.js create mode 100644 app/models/Proposal.js create mode 100644 app/routes/proposal.js create mode 100644 test/proposal.test.js diff --git a/app.js b/app.js index f4d03a8..cb256f9 100644 --- a/app.js +++ b/app.js @@ -4,6 +4,8 @@ const logger = require('morgan') const cookieParser = require('cookie-parser') const createError = require('http-errors') const path = require('path') +const multer = require('multer') +const bodyParser = require('body-parser') const indexRouter = require('./app/routes/index') const authRouter = require('./app/routes/auth') @@ -14,9 +16,22 @@ const shortUrlRouter = require('./app/routes/urlShortner') const organizationRouter = require('./app/routes/organisation') const commentRouter = require('./app/routes/comment') const projectRouter = require('./app/routes/project') +const proposalRouter = require('./app/routes/proposal') const app = express() +app.use(bodyParser.json({ limit: '200mb' })) +app.use( + bodyParser.urlencoded({ + limit: '200mb', + extended: true, + parameterLimit: 1000000 + }) +) + +const memoryStorage = multer.memoryStorage() +app.use(multer({ storage: memoryStorage }).single('file')) + // view engine setup app.set('views', path.join(__dirname, 'views')) app.set('view engine', 'ejs') @@ -36,10 +51,11 @@ app.use('/event', eventRouter) app.use('/shortUrl', shortUrlRouter) app.use('/comment', commentRouter) app.use('/project', projectRouter) +app.use('/proposal', proposalRouter) // catch 404 and forward to error handler app.use(function (req, res, next) { - next(createError(404, 'route doesn\'t exist')) + next(createError(404, "route doesn't exist")) }) // error handler diff --git a/app/controllers/post.js b/app/controllers/post.js index e6f5578..39170ee 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -29,11 +29,15 @@ module.exports = { try { const post = await PostModel.findById(id) if (!post) { - return res.status(HttpStatus.NOT_FOUND).json({ message: 'No post exists' }) + return res + .status(HttpStatus.NOT_FOUND) + .json({ message: 'No post exists' }) } // TODO ADD ADMIN RIGHTS AS WELL if (JSON.stringify(userId) !== JSON.stringify(post.userId)) { - return res.status(HttpStatus.FORBIDDEN).json({ message: 'Bad delete request' }) + return res + .status(HttpStatus.FORBIDDEN) + .json({ message: 'Bad delete request' }) } await PostModel.findByIdAndRemove(id) res.status(HttpStatus.OK).json({ post: post, msg: 'Deleted!' }) @@ -53,17 +57,23 @@ module.exports = { }) if (!isValidOperation) { - return res.status(HttpStatus.BAD_REQUEST).json({ message: 'Invalid Update' }) + return res + .status(HttpStatus.BAD_REQUEST) + .json({ message: 'Invalid Update' }) } try { const post = await PostModel.findById(id) if (!post) { - return res.status(HttpStatus.BAD_REQUEST).json({ message: 'No post exists' }) + return res + .status(HttpStatus.BAD_REQUEST) + .json({ message: 'No post exists' }) } if (JSON.stringify(userId) !== JSON.stringify(post.userId)) { - return res.status(HttpStatus.FORBIDDEN).json({ message: 'Bad update request' }) + return res + .status(HttpStatus.FORBIDDEN) + .json({ message: 'Bad update request' }) } - updates.forEach(update => { + updates.forEach((update) => { post[update] = req.body[update] }) if (req.file) { @@ -82,11 +92,18 @@ module.exports = { try { const post = await PostModel.findById(id) .populate('comments', ['content', 'votes']) - .populate('userId', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) + .populate('userId', [ + 'name.firstName', + 'name.lastName', + 'email', + 'isAdmin' + ]) .lean() .exec() if (!post) { - return res.status(HttpStatus.NOT_FOUND).json({ error: 'Post not found' }) + return res + .status(HttpStatus.NOT_FOUND) + .json({ error: 'Post not found' }) } res.status(HttpStatus.OK).json({ post: post }) } catch (error) { @@ -98,11 +115,18 @@ module.exports = { getAllPost: async (req, res, next) => { try { const posts = await PostModel.find({}, {}, helper.paginate(req)) - .populate('userId', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) + .populate('userId', [ + 'name.firstName', + 'name.lastName', + 'email', + 'isAdmin' + ]) .sort({ updatedAt: -1 }) .exec() if (!posts.length) { - return res.status(HttpStatus.NOT_FOUND).json({ message: 'No posts found' }) + return res + .status(HttpStatus.NOT_FOUND) + .json({ message: 'No posts found' }) } return res.status(HttpStatus.OK).json({ posts: posts }) } catch (error) { @@ -117,12 +141,16 @@ module.exports = { try { const post = await PostModel.findById(id) if (!post) { - return res.status(HttpStatus.NOT_FOUND).json({ error: 'No post found' }) + return res + .status(HttpStatus.NOT_FOUND) + .json({ error: 'No post found' }) } // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT - post.votes.upVotes.user.filter(user => { + post.votes.upVotes.user.filter((user) => { if (JSON.stringify(user) === JSON.stringify(userId)) { - return res.status(HttpStatus.BAD_REQUEST).json({ error: 'Bad request' }) + return res + .status(HttpStatus.BAD_REQUEST) + .json({ error: 'Bad request' }) } }) post.votes.upVotes.user.unshift(userId) @@ -135,7 +163,11 @@ module.exports = { getPostByUser: async (req, res, next) => { try { - const posts = await PostModel.find({ userId: req.user._id }, {}, helper.paginate(req)) + const posts = await PostModel.find( + { userId: req.user._id }, + {}, + helper.paginate(req) + ) .populate('comments', ['content', 'votes']) .sort({ updatedAt: -1 }) .exec() @@ -155,7 +187,9 @@ module.exports = { const post = await PostModel.findById(id) const user = await UserModel.findById(req.user._id) if (!post) { - return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such post exists!' }) + return res + .status(HttpStatus.NOT_FOUND) + .json({ msg: 'No such post exists!' }) } // toggle pinned post post.isPinned = !post.isPinned diff --git a/app/controllers/proposal.js b/app/controllers/proposal.js new file mode 100644 index 0000000..7e78b2b --- /dev/null +++ b/app/controllers/proposal.js @@ -0,0 +1,150 @@ +const ProposalModel = require('../models/Proposal') +const UserModal = require('../models/User') +const HANDLER = require('../utils/response-helper') +const HttpStatus = require('http-status-codes') +const AWS = require('aws-sdk') + +module.exports = { + // Creating a proposal + createProposal: async (req, res, next) => { + const proposal = new ProposalModel(req.body) + + try { + await proposal.save() + res.status(HttpStatus.CREATED).json({ proposal }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + // Updates the content of the proposal + saveProposal: async (req, res, next) => { + const { proposalId } = req.params + const content = req.body.content + try { + const proposal = await ProposalModel.findByIdAndUpdate(proposalId, { + content: content + }) + if (!proposal) { + return res + .status(HttpStatus.NOT_FOUND) + .json({ message: 'No proposal exists under the provided ID' }) + } + res.status(HttpStatus.OK).json({ proposal: proposal }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + // attaches a file to the given proposal + attachFile: (req, res, next) => { + const { proposalId } = req.params + const file = req.file + const s3FileURL = process.env.AWS_UPLOADED_FILE_URL_LINK + + const s3bucket = new AWS.S3({ + accessKeyId: process.env.AWS_ACCESS_KEY_ID, + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, + region: process.env.AWS_REGION + }) + + var params = { + Bucket: process.env.AWS_BUCKET_NAME, + Key: file.originalname, + Body: file.buffer, + ContentType: file.mimetype, + ACL: 'public-read' + } + + s3bucket.upload(params, function (err, data) { + if (err) { + res.status(500).json({ error: true, Message: err }) + } else { + res.send({ data }) + var newFileUploaded = { + description: req.body.description, + fileLink: s3FileURL + file.originalname, + s3_key: params.Key + } + ProposalModel.updateOne( + { _id: proposalId }, + { $push: { attachments: newFileUploaded } } + ).then((proposal) => { + console.log(proposal) + }) + } + }) + }, + + // Get proposals by userId + getByUserId: async (req, res, next) => { + const userId = req.body.userId + + try { + const proposals = await ProposalModel.find({ creator: userId }) + + console.log(proposals) + if (!proposals) { + return res + .status(HttpStatus.NOT_FOUND) + .json({ message: 'No proposals found for the given user ID' }) + } + return res.status(HttpStatus.OK).json({ proposal: proposals }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + // Delete proposal by proposalId + deleteById: async (req, res, next) => { + try { + const proposalId = req.body.proposalId + + const result = await ProposalModel.findByIdAndDelete(proposalId) + return res.status(HttpStatus.OK).json({ result: result }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + // Changes the state of a given proposal + changeState: async (req, res, next) => { + const proposalId = req.body.proposalId + const userId = req.body.userId + const proposalStatus = req.body.proposalStatus + try { + const user = await UserModal.findById(userId) + + if (user.isAdmin === true) { + const proposal = ProposalModel.findByIdAndUpdate(proposalId, { + proposalStatus: proposalStatus + }) + + return res.status(HttpStatus.OK).json({ proposal: proposal }) + } + return res + .status(HttpStatus.BAD_REQUEST) + .json({ msg: "You don't have permission!" }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + + // Obtains the proposal by given proposal ID + getProposalById: async (req, res, next) => { + const { proposalId } = req.params + + try { + const proposal = await ProposalModel.findById(proposalId) + + if (!proposal) { + return res + .status(HttpStatus.NOT_FOUND) + .json({ error: 'Proposal not found' }) + } + return res.status(HttpStatus.OK).json({ proposal: proposal }) + } catch (error) { + HANDLER.handleError(res, error) + } + } +} diff --git a/app/models/Proposal.js b/app/models/Proposal.js new file mode 100644 index 0000000..e4b9fef --- /dev/null +++ b/app/models/Proposal.js @@ -0,0 +1,48 @@ +const mongoose = require('mongoose') +const Schema = mongoose.Schema +const validator = require('validator') + +const proposalSchema = new Schema( + { + title: { + type: String, + required: true, + validate (title) { + if (validator.isEmpty(title)) { + throw new Error('Proposal Title cannot be kept empty') + } + } + }, + organization: { + type: Schema.Types.ObjectId, + ref: 'Organization' + }, + content: { + type: String + }, + proposalStatus: { + type: String, + required: true, + default: 'draft' + }, + creator: { + type: Schema.Types.ObjectId, + ref: 'User' + }, + attachments: [{ description: String, fileLink: String, s3_key: String }], + + createdAt: { + type: Date, + required: true, + default: Date.now() + }, + updatedAt: { + type: Date, + required: true, + default: Date.now() + } + }, + { timestamps: true } +) + +module.exports = mongoose.model('Proposal', proposalSchema) diff --git a/app/routes/proposal.js b/app/routes/proposal.js new file mode 100644 index 0000000..38bc79c --- /dev/null +++ b/app/routes/proposal.js @@ -0,0 +1,27 @@ +const express = require('express') +const router = express.Router() +const auth = require('../middleware/auth') +const proposalController = require('../controllers/proposal') + +// Create a new proposal +router.post('/', auth, proposalController.createProposal) + +// Save the content of a proposal +router.patch('/:proposalId', auth, proposalController.saveProposal) + +// Attach file to the given proposal +router.post('/attach/:proposalId', auth, proposalController.attachFile) + +// Get proposals by userId +router.get('/', auth, proposalController.getByUserId) + +// get proposal by proposalId +router.get('/:proposalId', auth, proposalController.getProposalById) + +// Deletes a proposal by given proposalId +router.delete('/', auth, proposalController.deleteById) + +// Update proposal state +router.patch('/change', auth, proposalController.changeState) + +module.exports = router diff --git a/package-lock.json b/package-lock.json index 7994e04..1c213df 100644 --- a/package-lock.json +++ b/package-lock.json @@ -833,6 +833,34 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "aws-sdk": { + "version": "2.691.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.691.0.tgz", + "integrity": "sha512-HV/iANH5PJvexubWr/oDmWMKtV/n1shtrACrLIUa5vTXIT6O7CzUouExNOvOtFMZw8zJkLmyEpa/0bDpMmo0Zg==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -959,6 +987,11 @@ } } }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, "basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -1160,6 +1193,16 @@ "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.1.tgz", "integrity": "sha512-jCGVYLoYMHDkOsbwJZBCqwMHyH4c+wzgI9hG7Z6SZJRXWr+x58pdIbm2i9a/jFGCkRJqRUr8eoI7lDWa0hTkxg==" }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", @@ -2470,6 +2513,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, "exec-sh": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", @@ -3790,6 +3838,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -4890,6 +4943,11 @@ } } }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6161,6 +6219,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -7591,6 +7654,22 @@ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "dev": true }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", @@ -7893,6 +7972,20 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" + } + }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index 4b75bf8..8208660 100644 --- a/package.json +++ b/package.json @@ -6,10 +6,12 @@ "start": "node ./bin/www", "dev": "env-cmd -f .env.dev nodemon ./bin/www", "pretest": "eslint --ignore-path .gitignore .", + "lint": "eslint .", "test": "env-cmd -f .env.test jest --detectOpenHandles && codecov -t 2b12ad97-07e0-45b2-a569-8aa2fd3e8c54" }, "dependencies": { "@sendgrid/mail": "^7.0.0", + "aws-sdk": "^2.691.0", "bcrypt": "^3.0.6", "body-parser": "^1.19.0", "cookie-parser": "~1.4.4", diff --git a/test/proposal.test.js b/test/proposal.test.js new file mode 100644 index 0000000..92660bd --- /dev/null +++ b/test/proposal.test.js @@ -0,0 +1,194 @@ +const app = require('../app') +const mongoose = require('mongoose') +const jwt = require('jsonwebtoken') +const HttpStatus = require('http-status-codes') +const request = require('supertest') +const User = require('../app/models/User') +const Organization = require('../app/models/Organisation') +const Proposal = require('../app/models/Proposal') +const randomDigit = Math.floor(Math.random() * 90 + 10) + +const testUserId = new mongoose.Types.ObjectId() +const testOrganizationId = new mongoose.Types.ObjectId() +const testProposalId = new mongoose.Types.ObjectId() +let token = '' + +const demoproposal = { + title: 'Test Proposal', + organization: testOrganizationId, + content: 'Content of the example proposal', + proposalStatus: 'DRAFT', + creator: testUserId +} + +const testProposal = { + _id: testProposalId, + ...demoproposal +} + +const demoUser = { + name: { + firstName: 'test', + lastName: 'test' + }, + email: `test${randomDigit}@mailinator.com`, + phone: `12345678${randomDigit}`, + password: 'abc12345', + info: { + about: { + shortDescription: 'this is short description', + longDescription: 'this is a very long description', + website: 'https://www.google.com', + designation: 'software engg', + skills: ['c++', 'java'], + education: [ + { + school: { + schoolName: 'firstSchoolName', + year: '2017-2021' + } + }, + { + school: { + schoolName: 'secondSchoolName', + year: '2007-2014' + } + } + ], + location: 'location' + } + } +} + +const demoOrganization = { + name: 'Codeuino', + description: { + shortDescription: 'short desc', + longDescription: 'long Description included here' + }, + contactInfo: { + email: 'organisation@test.com', + website: 'www.codeuino.org' + } +} + +const testOrganization = { + _id: testOrganizationId, + ...demoOrganization +} + +const updatedProposalContent = { + content: 'updated proposal content' +} + +const testUser = { + _id: testUserId, + ...demoUser, + email: `test${randomDigit}@mailinator.com`, + phone: `12345678${randomDigit}`, + tokens: [ + { + token: jwt.sign( + { + _id: testUserId + }, + process.env.JWT_SECRET + ) + } + ] +} + +let server + +/** + * This will pe performed once at the beginning of the test + */ + +beforeAll(async (done) => { + await Proposal.deleteMany() + await new User(testUser).save() + await new Organization(testOrganization).save() + server = app.listen(4000, () => { + global.agent = request.agent(server) + }) + const response = await request(app).post('/auth/login').send({ + email: testUser.email, + password: testUser.password + }) + token = response.body.token + done() +}) + +/** + * This deletes all the existing user in database, + * and creates a new user in database with the provided details. + */ +beforeEach(async () => { + await Proposal.deleteMany() + await new Proposal(testProposal).save() +}) + +test('Should create new Proposal', async (done) => { + const response = await request(app) + .post('/proposal') + .set('Authorization', `Bearer ${token}`) + .send(demoproposal) + .expect(HttpStatus.CREATED) + + const proposal = await Proposal.findById(response.body.proposal._id) + expect(proposal).not.toBeNull() + + const userId = response.body.proposal.creator + + expect(response.body).toMatchObject({ + proposal: { + title: demoproposal.title, + organization: `${testOrganizationId}`, + content: demoproposal.content, + proposalStatus: demoproposal.proposalStatus, + creator: `${userId}` + } + }) + done() +}) + +// Testing proposal update +test('Should update the content of the proposal', async (done) => { + await request(app) + .patch(`/proposal/${testProposalId}`) + .set('Authorization', `Bearer ${token}`) + .send(updatedProposalContent) + .expect(HttpStatus.OK) + + done() +}) + +// Testing proposal delete +const deleteProposalContent = { + proposalId: testProposalId +} + +test('Should delete the proposal', async (done) => { + await request(app) + .delete('/proposal') + .set('Authorization', `Bearer ${token}`) + .send(deleteProposalContent) + .expect(HttpStatus.OK) + + done() +}) + +// Testing get proposalById +const getByIdContent = { + proposalId: testProposalId +} + +test('Should return the proposal by the given Id', async (done) => { + await request(app) + .get(`/proposal/${testProposalId}`) + .set('Authorization', `Bearer ${token}`) + .send(getByIdContent) + .expect(HttpStatus.OK) + + done() +}) From cf9a8ab8cc6eb2b29aeb7f4caf6de92494275952 Mon Sep 17 00:00:00 2001 From: Rupesh Krishna Jha Date: Mon, 22 Jun 2020 22:27:52 +0530 Subject: [PATCH 29/42] Notification system using socket.io, remove user/admin, case-insensitive functionality (#135) * initial working mechanisam for notification system * modified backend to fix inconsistencies --- .env.dev | 1 + .env.test | 3 +- app.js | 20 +- app/controllers/event.js | 63 ++++-- app/controllers/notification.js | 38 ++++ app/controllers/organization.js | 79 +++++++- app/controllers/post.js | 7 +- app/controllers/project.js | 3 - app/controllers/user.js | 88 ++++++-- app/middleware/auth.js | 4 + app/models/Notifications.js | 19 ++ app/models/User.js | 15 ++ app/routes/notification.js | 23 +++ app/routes/organisation.js | 8 + app/routes/user.js | 8 + app/utils/notif-helper.js | 26 +++ app/utils/notificationTags.js | 10 + bin/www | 2 +- package-lock.json | 345 +++++++++++++++++++++++++++++++- package.json | 1 + socket.js | 53 +++++ test/comment.test.js | 4 +- test/event.test.js | 4 +- test/organisation.test.js | 4 +- test/post.test.js | 4 +- test/project.test.js | 4 +- test/url.test.js | 4 +- test/user.test.js | 7 +- 28 files changed, 790 insertions(+), 57 deletions(-) create mode 100644 app/controllers/notification.js create mode 100644 app/models/Notifications.js create mode 100644 app/routes/notification.js create mode 100644 app/utils/notif-helper.js create mode 100644 app/utils/notificationTags.js create mode 100644 socket.js diff --git a/.env.dev b/.env.dev index 9cd8a95..afc775f 100644 --- a/.env.dev +++ b/.env.dev @@ -3,3 +3,4 @@ NODE_ENV="development" JWT_SECRET="thisismysupersecrettokenjustkidding" DATABASE_URL="mongodb://localhost:27017/donut-development" SENDGRID_API_KEY = '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' +SOCKET_PORT = 8810 \ No newline at end of file diff --git a/.env.test b/.env.test index fa78537..dd4d183 100644 --- a/.env.test +++ b/.env.test @@ -2,4 +2,5 @@ PORT=3000 NODE_ENV=testing JWT_SECRET=thisismysupersecrettokenjustkidding DATABASE_URL=mongodb+srv://donut-admin:5cdS2C2g3wRAdQWp@donut-users-hdawt.mongodb.net/donut-testing?retryWrites=true&w=majority -SENDGRID_API_KEY = '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' \ No newline at end of file +SENDGRID_API_KEY = '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' +SOCKET_PORT = 8810 \ No newline at end of file diff --git a/app.js b/app.js index f4d03a8..62ecf13 100644 --- a/app.js +++ b/app.js @@ -4,6 +4,7 @@ const logger = require('morgan') const cookieParser = require('cookie-parser') const createError = require('http-errors') const path = require('path') +const socket = require('socket.io') const indexRouter = require('./app/routes/index') const authRouter = require('./app/routes/auth') @@ -14,8 +15,20 @@ const shortUrlRouter = require('./app/routes/urlShortner') const organizationRouter = require('./app/routes/organisation') const commentRouter = require('./app/routes/comment') const projectRouter = require('./app/routes/project') +const notificationRouter = require('./app/routes/notification') const app = express() +const server = require('http').Server(app) + +server.listen(process.env.SOCKET_PORT || 8810) +// WARNING: app.listen(80) will NOT work here! + +const io = socket.listen(server) +let count = 0 +io.on('connection', (socket) => { + console.log('socket connected count ', count++) + io.emit('user connected') +}) // view engine setup app.set('views', path.join(__dirname, 'views')) @@ -26,7 +39,12 @@ app.use(express.json()) app.use(express.urlencoded({ extended: false })) app.use(cookieParser()) app.use(express.static(path.join(__dirname, 'public'))) +app.use((req, res, next) => { + req.io = io + next() +}) +app.use('/notification', notificationRouter) app.use('/', indexRouter) app.use('/auth', authRouter) app.use('/user', usersRouter) @@ -53,4 +71,4 @@ app.use(function (err, req, res, next) { res.render('error') }) -module.exports = app +module.exports = { app, io } diff --git a/app/controllers/event.js b/app/controllers/event.js index 9246e63..cf87ada 100644 --- a/app/controllers/event.js +++ b/app/controllers/event.js @@ -3,6 +3,12 @@ const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') const permission = require('../utils/permission') const helper = require('../utils/paginate') +const notificationHelper = require('../utils/notif-helper') +const notification = { + heading: '', + content: '', + tag: '' +} module.exports = { createEvent: async (req, res, next) => { @@ -10,13 +16,18 @@ module.exports = { try { event.createdBy = req.user._id await event.save() + req.io.emit('new event created', { data: event.eventName }) + notification.heading = 'New Event!' + notification.content = `${event.eventName} is added!` + notification.tag = 'New!' + notificationHelper.addToNotificationForAll(req, res, notification, next) res.status(HttpStatus.CREATED).json({ event: event }) } catch (error) { res.status(HttpStatus.BAD_REQUEST).json({ error: error }) } }, - updateEvent: async (req, res) => { + updateEvent: async (req, res, next) => { const { id } = req.params const updates = Object.keys(req.body) try { @@ -29,15 +40,21 @@ module.exports = { event[update] = req.body[update] }) await event.save() + req.io.emit('event update', { data: `Event: ${event.eventName} is updated!` }) + notification.heading = 'Event update!' + notification.content = `${event.eventName} is updated!` + notification.tag = 'Update' + notificationHelper.addToNotificationForAll(req, res, notification, next) res.status(HttpStatus.OK).json({ event: event }) } catch (error) { HANDLER.handleError(res, error) } }, - rsvp: async (req, res) => { + rsvp: async (req, res, next) => { const { yes, no, maybe } = req.body const { id } = req.params + notification.tag = 'RSVP' try { const data = await Event.findById(id) if (!data) { @@ -47,14 +64,23 @@ module.exports = { if (data.rsvpMaybe.includes(req.user.id) || data.rsvpNo.includes(req.user.id) || data.rsvpYes.includes(req.user.id)) { - return res.status(HttpStatus.OK).json({ msg: 'You have already done the rsvp' }) + req.io.emit('already rsvp', { data: 'You have already done the rsvp' }) + notification.heading = 'Already rsvp!' + notification.content = 'You have already done the rsvp' + notificationHelper.addToNotificationForUser(req.user._id, res, notification, next) + res.status(HttpStatus.OK).json({ msg: 'You have already done the rsvp' }) + return } const event = await Event.findByIdAndUpdate(id) if (yes) { try { event.rsvpYes.push(req.user.id) await event.save() - return res.status(HttpStatus.OK).json({ rsvpData: data }) + req.io.emit('rsvp done', { data: 'RSVP successfully done!' }) + notification.heading = 'RSVP done!' + notification.content = 'RSVP successfully done!' + notificationHelper.addToNotificationForUser(req.user._id, res, notification, next) + res.status(HttpStatus.OK).json({ rsvpData: data }) } catch (error) { return res.status(HttpStatus.BAD_REQUEST).json({ error: error }) } @@ -63,7 +89,11 @@ module.exports = { try { event.rsvpNo.push(req.user.id) await event.save() - return res.status(HttpStatus.OK).json({ rsvpData: data }) + req.io.emit('rsvp done', { data: 'RSVP successfully done!' }) + notification.heading = 'RSVP done!' + notification.content = 'RSVP successfully done!' + notificationHelper.addToNotificationForUser(req.user._id, res, notification, next) + res.status(HttpStatus.OK).json({ rsvpData: data }) } catch (error) { return res.status(HttpStatus.BAD_REQUEST).json({ error: error }) } @@ -72,7 +102,11 @@ module.exports = { try { event.rsvpMaybe.push(req.user.id) await event.save() - return res.status(HttpStatus.OK).json({ rsvpData: data }) + req.io.emit('rsvp done', { data: 'RSVP successfully done!' }) + notification.heading = 'RSVP done!' + notification.content = 'RSVP successfully done!' + notificationHelper.addToNotificationForUser(req.user._id, res, notification, next) + res.status(HttpStatus.OK).json({ rsvpData: data }) } catch (error) { return res.status(HttpStatus.BAD_REQUEST).json({ error: error }) } @@ -98,12 +132,10 @@ module.exports = { GetAllEvent: async (req, res, next) => { try { const EventData = await Event.find({}, {}, helper.paginate(req)) + .populate('createdBy', ['name.firstName', 'name.lastName', '_id', 'isAdmin']) .sort({ eventDate: -1 }) .lean() - if (!EventData) { - return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such Event is available!' }) - } - return res.status(HttpStatus.OK).json({ Event: EventData }) + return res.status(HttpStatus.OK).json({ events: EventData }) } catch (error) { HANDLER.handleError(res, error) } @@ -118,6 +150,11 @@ module.exports = { } if (permission.check(req, res, deleteEvent.createdBy)) { await Event.findByIdAndRemove(id) + req.io.emit('event deleted', { data: deleteEvent.eventName }) + notification.heading = 'Event deleted!' + notification.content = `Event ${deleteEvent.eventName} is deleted!` + notification.tag = 'Deleted' + notificationHelper.addToNotificationForAll(req, res, notification, next) return res.status(HttpStatus.OK).json({ deleteEvent: deleteEvent, message: 'Deleted the event' }) } return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Not permitted!' }) @@ -132,9 +169,6 @@ module.exports = { .sort({ eventDate: -1 }) .exec() console.log('Upcoming events ', events) - if (events.length === 0) { - return res.status(HttpStatus.OK).json({ msg: 'No Upcoming events exists!' }) - } return res.status(HttpStatus.OK).json({ events }) } catch (error) { HANDLER.handleError(res, next) @@ -147,9 +181,6 @@ module.exports = { .sort({ eventDate: -1 }) .populate('createdBy', '_id name.firstName name.lastName') .exec() - if (events.length === 0) { - return res.status(HttpStatus.OK).json({ msg: 'No events posted by user!' }) - } return res.status(HttpStatus.OK).json({ events }) } catch (error) { HANDLER.handleError(res, error) diff --git a/app/controllers/notification.js b/app/controllers/notification.js new file mode 100644 index 0000000..5d5bfa8 --- /dev/null +++ b/app/controllers/notification.js @@ -0,0 +1,38 @@ +const HANDLER = require('../utils/response-helper') +const HttpStatus = require('http-status-codes') +const Notifications = require('../models/Notifications') +const helper = require('../utils/paginate') +const User = require('../models/User') + +module.exports = { + // GET ALL THE NOTIFICATIONS FOR ALL + getOrgNotifications: async (req, res, next) => { + try { + const notifications = await Notifications.find({}, {}, helper.paginate(req)) + .lean() + .sort({ createdAt: -1 }) + .exec() + return res.status(HttpStatus.OK).json({ notifications }) + } catch (error) { + HANDLER.handleError(res, error) + } + }, + // GET LOGGED IN USER NOTIFICATIONS + getUserNotification: async (req, res, next) => { + const userId = req.user._id + try { + const user = await User.findById(userId) + if (!user) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'No such user exists!' }) + } + // get all notifications of existing user + const notifications = user.notifications + if (notifications.length === 0) { + return res.status(HttpStatus.OK).json({ msg: 'No new notifications!' }) + } + return res.status(HttpStatus.OK).json({ notifications }) + } catch (error) { + HANDLER.handleError(res, error) + } + } +} diff --git a/app/controllers/organization.js b/app/controllers/organization.js index 128b337..aaf2fb8 100644 --- a/app/controllers/organization.js +++ b/app/controllers/organization.js @@ -2,10 +2,17 @@ const Organization = require('../models/Organisation') const HANDLER = require('../utils/response-helper') const HttpStatus = require('http-status-codes') const helper = require('../utils/uploader') +const notificationHelper = require('../utils/notif-helper') const User = require('../models/User') const Project = require('../models/Project') const Event = require('../models/Event') const permission = require('../utils/permission') +const TAGS = require('../utils/notificationTags') +const notification = { + heading: '', + content: '', + tag: '' +} module.exports = { createOrganization: async (req, res, next) => { @@ -15,7 +22,12 @@ module.exports = { } try { await org.save() - res.status(HttpStatus.CREATED).json({ org }) + req.io.emit('new org created', { data: org.name }) + notification.heading = 'New org!' + notification.content = `${org.name} is created!` + notification.tag = TAGS.NEW + notificationHelper.addToNotificationForAll(req, res, notification, next) + return res.status(HttpStatus.CREATED).json({ org }) } catch (error) { HANDLER.handleError(res, error) } @@ -80,6 +92,11 @@ module.exports = { if (!permission.check(req, res)) { return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have the permission!' }) } + req.io.emit('org deleted', { data: org.name }) + notification.heading = 'Org deleted!' + notification.content = `${org.name} is deleted!` + notification.tag = TAGS.DELETE + notificationHelper.addToNotificationForAll(req, res, notification, next) return res.status(HttpStatus.OK).json({ organization: org }) } catch (error) { HANDLER.handleError(res, error) @@ -111,15 +128,25 @@ module.exports = { } // if user is admin or not const adminIds = organization.adminInfo.adminId - const isAdmin = adminIds.indexOf(req.user.id) + const isAdmin = adminIds.indexOf(req.user.id) || req.user.isAdmin // user is admin then perform operation - if (isAdmin !== -1) { + if (isAdmin !== -1 || req.user.isAdmin) { // toggle maintenance mode organization.isMaintenance = !organization.isMaintenance await organization.save() + notification.tag = TAGS.MAINTENANCE + if (organization.isMaintenance) { + req.io.emit('org under maintenance', { data: organization.name }) + notification.heading = 'Maintenance mode on!' + notification.content = `${organization.name} is kept under maintenance!` + notificationHelper.addToNotificationForAll(req, res, notification, next) return res.status(HttpStatus.OK).json({ msg: 'Organization is kept under the maintenance!!' }) } + + req.io.emit('org revoked maintenance', { data: organization.name }) + notification.heading = 'Maintenance mode off!' + notification.content = `${organization.name} is revoked from maintenance!` return res.status(HttpStatus.OK).json({ msg: 'Organization is recovered from maintenance!!' }) } else { return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have access to triggerMaintenance!' }) @@ -191,9 +218,15 @@ module.exports = { try { const { search } = req.query if (search) { - const regex = search.split(' ') - const member = await User.find({ $or: [{ 'name.firstName': regex }, { 'name.lastName': regex }] }) - .select('name email isAdmin info.about.designation') + const queryTerm = search.split(' ') + const regex = new RegExp('^' + queryTerm + '$', 'i') + const member = await User.find({ + $or: [ + { 'name.firstName': { $regex: regex } }, + { 'name.lastName': { $regex: regex } } + ] + }) + .select('name email isAdmin info.about.designation isRemoved') .lean() .sort({ createdAt: -1 }) .exec() @@ -203,7 +236,7 @@ module.exports = { return res.status(HttpStatus.OK).json({ member }) } else { const members = await User.find({}) - .select('name email isAdmin info.about.designation') + .select('name email isAdmin info.about.designation isRemoved') .lean() .sort({ createdAt: -1 }) .exec() @@ -215,5 +248,37 @@ module.exports = { } catch (error) { HANDLER.handleError(res, error) } + }, + // REMOVE ADMIN + removeAdmin: async (req, res, next) => { + try { + const { userId, orgId } = req.params + const org = await Organization.findById(orgId) + if (!org) { + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No org exists!' }) + } + // only permitted for admins + if (!req.user.isAdmin) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You are not permitted!' }) + } + // console.log('Permitted to removeAdmin') + // REMOVE ADMINS FROM ADMINS LIST + const admins = org.adminInfo.adminId + console.log('adminIds ', admins) + const removableIndex = admins.indexOf(userId) + if (removableIndex === -1) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'User is not an admin!' }) + } + // user is admin so remove + org.adminInfo.adminId.splice(removableIndex, 1) + await org.save() + // also make isAdmin false + const user = await User.findById(userId) + user.isAdmin = false + await user.save() + return res.status(HttpStatus.OK).json({ org }) + } catch (error) { + HANDLER.handleError(res, error) + } } } diff --git a/app/controllers/post.js b/app/controllers/post.js index b174463..cacece8 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -17,7 +17,8 @@ module.exports = { } try { await post.save() - res.status(HttpStatus.CREATED).json({ post }) + // req.io.emit('new post created', { data: post.content }) + return res.status(HttpStatus.CREATED).json({ post }) } catch (error) { HANDLER.handleError(res, error) } @@ -135,11 +136,9 @@ module.exports = { try { const posts = await PostModel.find({ userId: req.user._id }, {}, helper.paginate(req)) .populate('comments', ['content', 'votes']) + .populate('userId', ['name.firstName', 'name.lastName', '_id', 'isAdmin']) .sort({ updatedAt: -1 }) .exec() - if (posts.length === 0) { - return res.status(HttpStatus.OK).json({ msg: 'No posts found!' }) - } return res.status(HttpStatus.OK).json({ posts }) } catch (error) { HANDLER.handleError(res, error) diff --git a/app/controllers/project.js b/app/controllers/project.js index a5beb1a..784f9e4 100644 --- a/app/controllers/project.js +++ b/app/controllers/project.js @@ -100,9 +100,6 @@ module.exports = { .populate('createdBy', '_id name.firstName name.lastName email') .sort({ updatedAt: -1 }) .exec() - if (projects.length === 0) { - return res.status(HttpStatus.OK).json({ msg: 'No projects created by user yet!' }) - } return res.status(HttpStatus.OK).json({ projects }) } catch (error) { HANDLER.handleError(res, error) diff --git a/app/controllers/user.js b/app/controllers/user.js index e035072..b374fb2 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -4,8 +4,15 @@ const HttpStatus = require('http-status-codes') const emailController = require('./email') const permission = require('../utils/permission') const HANDLER = require('../utils/response-helper') +const notificationHelper = require('../utils/notif-helper') const Projects = require('../models/Project') const Events = require('../models/Event') +const TAGS = require('../utils/notificationTags') +const notification = { + heading: '', + content: '', + tag: '' +} module.exports = { // CREATE USER @@ -24,7 +31,15 @@ module.exports = { }, // GET USER PROFILE userProfile: async (req, res, next) => { - res.status(HttpStatus.OK).json(req.user) + try { + const user = req.user + if (!user) { + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such user exist!' }) + } + return res.status(HttpStatus.OK).json({ user }) + } catch (error) { + HANDLER.handleError(res, error) + } }, // USER PROFILE UPDATE @@ -34,7 +49,8 @@ module.exports = { 'name', 'email', 'phone', - 'info' + 'info', + 'about' ] const isValidOperation = updates.every((update) => { return allowedUpdates.includes(update) @@ -74,8 +90,7 @@ module.exports = { } }, - // UPDATE PASSWORD - updatePassword: async (req, res) => { + updatePassword: async (req, res, next) => { const { password, id } = req.body const { token } = req.params try { @@ -90,6 +105,14 @@ module.exports = { } user.password = password await user.save() + const obj = { + userId: user._id + } + req.io.emit('Password update', obj) + notification.heading = 'Forgot password!' + notification.content = 'Password successfully updated!' + notification.tag = TAGS.UPDATE + notificationHelper.addToNotificationForUser(id, res, notification, next) return res.status(HttpStatus.OK).json({ updated: true }) } else { if (process.env.NODE_ENV !== 'production') { @@ -143,6 +166,14 @@ module.exports = { // if user found activate the account user.isActivated = true await user.save() + const obj = { + userId: user._id + } + req.io.emit('Account activate', obj) + notification.heading = 'Account activate!' + notification.content = 'Account successfully activated!' + notification.tag = TAGS.ACTIVATE + notificationHelper.addToNotificationForUser(user._id, res, notification, next) return res.status(HttpStatus.OK).json({ msg: 'Succesfully activated!' }) } } catch (Error) { @@ -194,16 +225,27 @@ module.exports = { const { followId } = req.body try { const user = await User.findById(followId) - .populate('followings', ['name.firstName', 'name.lastName', 'email']) - .populate('followers', ['name.firstName', 'name.lastName', 'email']) - .exec() if (!user) { return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'No such user exists!' }) } // add to the followers list user.followers.unshift(req.user.id) await user.save() - return res.status(HttpStatus.OK).json({ user }) + const obj = { + name: req.user.name.firstName, + followId: user._id + } + req.io.emit('New follower', obj) + notification.heading = 'New follower!' + notification.content = `${req.user.name.firstName} started following you!` + notification.tag = TAGS.FOLLOWER + notificationHelper.addToNotificationForUser(user._id, res, notification, next) + const userData = await User.findById(req.user._id) + .populate('followings', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) + .populate('followers', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) + .populate('blocked', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) + .exec() + return res.status(HttpStatus.OK).json({ user: userData }) } catch (error) { HANDLER.handleError(res, error) } @@ -238,9 +280,6 @@ module.exports = { const { followId } = req.body try { const user = await User.findById(followId) - .populate('followings', ['name.firstName', 'name.lastName', 'email']) - .populate('followers', ['name.firstName', 'name.lastName', 'email']) - .exec() if (!user) { return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such user exists!' }) } @@ -251,7 +290,12 @@ module.exports = { } user.followers.splice(isFollowingIndex, 1) await user.save() - return res.status(HttpStatus.OK).json({ user }) + const userData = await User.findById(req.user._id) + .populate('followings', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) + .populate('followers', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) + .populate('blocked', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) + .exec() + return res.status(HttpStatus.OK).json({ user: userData }) } catch (error) { HANDLER.handleError(res, error) } @@ -319,5 +363,25 @@ module.exports = { } catch (error) { HANDLER.handleError(req, error) } + }, + + // REMOVE USER + removeUser: async (req, res, next) => { + const { id } = req.params + try { + const user = await User.findById(id) + if (!user) { + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No such user exits!' }) + } + // only admins can remove + if (!req.user.isAdmin) { + return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You are not permitted!' }) + } + user.isRemoved = true + await user.save() + return res.status(HttpStatus.OK).json({ user }) + } catch (error) { + HANDLER.handleError(res, error) + } } } diff --git a/app/middleware/auth.js b/app/middleware/auth.js index 75c1a61..9762089 100644 --- a/app/middleware/auth.js +++ b/app/middleware/auth.js @@ -10,6 +10,10 @@ const auth = async (req, res, next) => { _id: decoded._id, 'tokens.token': token }) + .populate('followings', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) + .populate('followers', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) + .populate('blocked', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) + .exec() if (!user) { throw new Error() diff --git a/app/models/Notifications.js b/app/models/Notifications.js new file mode 100644 index 0000000..c44c0b7 --- /dev/null +++ b/app/models/Notifications.js @@ -0,0 +1,19 @@ +const mongoose = require('mongoose') +const Schema = mongoose.Schema + +const NotificationSchema = new Schema({ + heading: { + type: String + }, + content: { + type: String + }, + tag: { + type: String + }, + createdAt: { + type: Date, + default: Date.now() + } +}) +module.exports = mongoose.model('Notification', NotificationSchema) diff --git a/app/models/User.js b/app/models/User.js index 87a4222..5010d1b 100644 --- a/app/models/User.js +++ b/app/models/User.js @@ -136,6 +136,17 @@ const UserSchema = new mongoose.Schema({ } } }, + notifications: [{ + heading: { + type: String + }, + content: { + type: String + }, + tag: { + type: String + } + }], followers: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' @@ -163,6 +174,10 @@ const UserSchema = new mongoose.Schema({ type: Boolean, default: false }, + isRemoved: { + type: Boolean, + default: false + }, createdAt: { type: Date, required: true, diff --git a/app/routes/notification.js b/app/routes/notification.js new file mode 100644 index 0000000..4a4e035 --- /dev/null +++ b/app/routes/notification.js @@ -0,0 +1,23 @@ +const express = require('express') +const router = express.Router() +const auth = require('../middleware/auth') +const isUnderMaintenance = require('../middleware/maintenance') +const notificationController = require('../controllers/notification') + +// GET NOTIFICATIONS FOR ALL +router.get( + '/org/all', + isUnderMaintenance, + auth, + notificationController.getOrgNotifications +) + +// GET NOTIFICATIONS FOR LOGGED IN USER +router.get( + '/user/all', + isUnderMaintenance, + auth, + notificationController.getUserNotification +) + +module.exports = router diff --git a/app/routes/organisation.js b/app/routes/organisation.js index b69e5d9..a69abc9 100644 --- a/app/routes/organisation.js +++ b/app/routes/organisation.js @@ -76,4 +76,12 @@ router.patch( OrgController.updateSettings ) +// REMOVE ADMIN +router.patch( + '/remove/:orgId/:userId', + isUnderMaintenance, + auth, + OrgController.removeAdmin +) + module.exports = router diff --git a/app/routes/user.js b/app/routes/user.js index 2d9073f..03de47f 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -122,4 +122,12 @@ router.get( userController.getPersonalOverview ) +// REMOVE USER +router.patch( + '/remove/:id', + isUnderMaintenance, + auth, + userController.removeUser +) + module.exports = router diff --git a/app/utils/notif-helper.js b/app/utils/notif-helper.js new file mode 100644 index 0000000..19f347b --- /dev/null +++ b/app/utils/notif-helper.js @@ -0,0 +1,26 @@ +const User = require('../models/User') +const Notifications = require('../models/Notifications') + +module.exports = { + // Notifications for a user + addToNotificationForUser: async (userId, res, obj, next) => { + try { + console.log('adding to user\'s notifications') + const user = await User.findById(userId) + user.notifications.unshift(obj) + await user.save() + } catch (error) { + console.log(error) + } + }, + // Notifications for all + addToNotificationForAll: async (req, res, obj, next) => { + const newNotification = new Notifications(obj) + try { + await newNotification.save() + console.log('newNotifications ', newNotification) + } catch (error) { + console.log(error) + } + } +} diff --git a/app/utils/notificationTags.js b/app/utils/notificationTags.js new file mode 100644 index 0000000..0256732 --- /dev/null +++ b/app/utils/notificationTags.js @@ -0,0 +1,10 @@ +const tags = { + RSVP: 'RSVP', + UPDATE: 'Update', + DELETE: 'Delete', + NEW: 'New!', + MAINTENANCE: 'Maintenance', + FOLLOWER: 'Follower', + ACTIVATE: 'Activate' +} +module.exports = tags diff --git a/bin/www b/bin/www index 2b4e4ed..f03de80 100644 --- a/bin/www +++ b/bin/www @@ -5,7 +5,7 @@ */ // for .env file to work require('dotenv').config() -var app = require('../app') +var app = require('../app').app var debug = require('debug')('donut-backend:server') var http = require('http') diff --git a/package-lock.json b/package-lock.json index 7994e04..7e19590 100644 --- a/package-lock.json +++ b/package-lock.json @@ -600,6 +600,11 @@ "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", "dev": true }, + "after": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" + }, "agent-base": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", @@ -782,6 +787,11 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "arraybuffer.slice": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", + "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -818,8 +828,7 @@ "async-limiter": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" }, "asynckit": { "version": "0.4.0", @@ -899,6 +908,11 @@ "babel-plugin-jest-hoist": "^24.9.0" } }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -959,6 +973,16 @@ } } }, + "base64-arraybuffer": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", + "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, "basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -985,12 +1009,25 @@ "tweetnacl": "^0.14.3" } }, + "better-assert": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz", + "integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=", + "requires": { + "callsite": "1.0.0" + } + }, "binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", "dev": true }, + "blob": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", + "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" + }, "bluebird": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", @@ -1224,6 +1261,11 @@ "unset-value": "^1.0.0" } }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1455,11 +1497,20 @@ "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", "dev": true }, + "component-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" + }, "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "component-inherit": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" }, "concat-map": { "version": "0.0.1", @@ -1878,6 +1929,92 @@ "once": "^1.4.0" } }, + "engine.io": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz", + "integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==", + "requires": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "0.3.1", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "ws": "^7.1.2" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "ws": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", + "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==" + } + } + }, + "engine.io-client": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.3.tgz", + "integrity": "sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw==", + "requires": { + "component-emitter": "~1.3.0", + "component-inherit": "0.0.3", + "debug": "~4.1.0", + "engine.io-parser": "~2.2.0", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "ws": "~6.1.0", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "ws": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz", + "integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==", + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "engine.io-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz", + "integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==", + "requires": { + "after": "0.8.2", + "arraybuffer.slice": "~0.0.7", + "base64-arraybuffer": "0.1.5", + "blob": "0.0.5", + "has-binary2": "~1.0.2" + } + }, "env-cmd": { "version": "10.0.1", "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.0.1.tgz", @@ -3665,6 +3802,26 @@ "function-bind": "^1.1.1" } }, + "has-binary2": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", + "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", + "requires": { + "isarray": "2.0.1" + }, + "dependencies": { + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + } + } + }, + "has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", @@ -3850,6 +4007,11 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indexof": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -5720,6 +5882,11 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-component": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz", + "integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE=" + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -5958,6 +6125,22 @@ "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", "dev": true }, + "parseqs": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", + "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", + "requires": { + "better-assert": "~1.0.0" + } + }, + "parseuri": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz", + "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", + "requires": { + "better-assert": "~1.0.0" + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -6863,6 +7046,145 @@ } } }, + "socket.io": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-2.3.0.tgz", + "integrity": "sha512-2A892lrj0GcgR/9Qk81EaY2gYhCBxurV0PfmmESO6p27QPrUK1J3zdns+5QPqvUYK2q657nSj0guoIil9+7eFg==", + "requires": { + "debug": "~4.1.0", + "engine.io": "~3.4.0", + "has-binary2": "~1.0.2", + "socket.io-adapter": "~1.1.0", + "socket.io-client": "2.3.0", + "socket.io-parser": "~3.4.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "socket.io-adapter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", + "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==" + }, + "socket.io-client": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz", + "integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==", + "requires": { + "backo2": "1.0.2", + "base64-arraybuffer": "0.1.5", + "component-bind": "1.0.0", + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "engine.io-client": "~3.4.0", + "has-binary2": "~1.0.2", + "has-cors": "1.1.0", + "indexof": "0.0.1", + "object-component": "0.0.3", + "parseqs": "0.0.5", + "parseuri": "0.0.5", + "socket.io-parser": "~3.3.0", + "to-array": "0.1.4" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "socket.io-parser": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz", + "integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~3.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + } + } + }, + "socket.io-parser": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.4.1.tgz", + "integrity": "sha512-11hMgzL+WCLWf1uFtHSNvliI++tcRUWdoeYuwIl+Axvwy9z2gQM+7nJyN3STj1tLj5JyIUH8/gpDGxzAlDdi0A==", + "requires": { + "component-emitter": "1.2.1", + "debug": "~4.1.0", + "isarray": "2.0.1" + }, + "dependencies": { + "component-emitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "isarray": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -7302,6 +7624,11 @@ "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", "dev": true }, + "to-array": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", + "integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA=" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -7893,6 +8220,11 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, + "xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -7978,6 +8310,11 @@ "dev": true } } + }, + "yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" } } } diff --git a/package.json b/package.json index 4b75bf8..c726386 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "mongoose": "^5.7.7", "morgan": "^1.9.1", "multer": "^1.4.2", + "socket.io": "^2.3.0", "validator": "^10.11.0" }, "jest": { diff --git a/socket.js b/socket.js new file mode 100644 index 0000000..e91787f --- /dev/null +++ b/socket.js @@ -0,0 +1,53 @@ +const webSocker = require('./app').io + +module.exports = { + socketEvents: (io = webSocker) => { + let count = 0 + + io.on('connection', function (socket) { + console.log('Socket conn count: ' + count++) + io.emit('user connected') + + socket.on('test', (data) => { + console.log('test invoked') + io.emit('test response', { data: data }) + }) + + // PROJECT RELATED NOTIFICATIONS + socket.on('new project added', (data) => { + console.log('New project data ->', data) + io.emit('new project', { data: data }) + }) + + // EVENTS RELATED NOTIFICATIONS + socket.on('new event addeed', (data) => { + io.emit('new event', { data: data }) + }) + + // POST RELATED NOTIFICATIONS + socket.on('create post event', (data) => { + console.log('create post event invoked') + io.emit('new post', { + data: data + }) + }) + + // INTERNET RELATED ISSUE NOTIFICATIONS + socket.on('internet issue emit', (data) => { + console.log('Internet issue in ') + io.emit('internet issue', { data: data }) + }) + + socket.on('internet issue resolved emit', (data) => { + io.emit('internet issue resolved', { data: data }) + }) + + socket.on('disconnect', function () { + io.emit('user disconnected') + }) + socket.on('test', () => { + io.emit('test response') + }) + }) + } +} diff --git a/test/comment.test.js b/test/comment.test.js index e77e3ca..cb8e64b 100644 --- a/test/comment.test.js +++ b/test/comment.test.js @@ -1,4 +1,4 @@ -const app = require('../app') +const app = require('../app').app const mongoose = require('mongoose') const jwt = require('jsonwebtoken') const HttpStatus = require('http-status-codes') @@ -302,6 +302,8 @@ test('Should downvote the post', async (done) => { * Jest has detected the following 1 open handle potentially keeping Jest from exiting */ afterAll(async () => { + // avoid jest open handle error + await new Promise((resolve) => setTimeout(() => resolve(), 500)) // close server await server.close() // delete all the posts post testing diff --git a/test/event.test.js b/test/event.test.js index 53e5a9b..672168a 100644 --- a/test/event.test.js +++ b/test/event.test.js @@ -1,4 +1,4 @@ -const app = require('../app') +const app = require('../app').app const mongoose = require('mongoose') const jwt = require('jsonwebtoken') const HttpStatus = require('http-status-codes') @@ -306,6 +306,8 @@ test('Should get all the events created by user', async (done) => { * Jest has detected the following 1 open handle potentially keeping Jest from exiting */ afterAll(async () => { + // avoid jest open handle error + await new Promise((resolve) => setTimeout(() => resolve(), 500)) // close server await server.close() // delete all the events post testing diff --git a/test/organisation.test.js b/test/organisation.test.js index db8fc69..12f966b 100644 --- a/test/organisation.test.js +++ b/test/organisation.test.js @@ -1,4 +1,4 @@ -const app = require('../app') +const app = require('../app').app const mongoose = require('mongoose') const request = require('supertest') const HttpStatus = require('http-status-codes') @@ -174,6 +174,8 @@ describe('DELETE /org/:id', () => { }) afterAll(async () => { + // avoid jest open handle error + await new Promise((resolve) => setTimeout(() => resolve(), 500)) // close server await server.close() // delete all the organization post testing diff --git a/test/post.test.js b/test/post.test.js index e5cee05..3095604 100644 --- a/test/post.test.js +++ b/test/post.test.js @@ -1,4 +1,4 @@ -const app = require('../app') +const app = require('../app').app const mongoose = require('mongoose') const jwt = require('jsonwebtoken') const HttpStatus = require('http-status-codes') @@ -243,6 +243,8 @@ test('Should retrieve all posts created by a user', async (done) => { * Jest has detected the following 1 open handle potentially keeping Jest from exiting */ afterAll(async () => { + // avoid jest open handle error + await new Promise((resolve) => setTimeout(() => resolve(), 500)) // close server await server.close() // delete all the posts post testing diff --git a/test/project.test.js b/test/project.test.js index 1941b58..dd22655 100644 --- a/test/project.test.js +++ b/test/project.test.js @@ -1,4 +1,4 @@ -const app = require('../app') +const app = require('../app').app const mongoose = require('mongoose') const jwt = require('jsonwebtoken') const HttpStatus = require('http-status-codes') @@ -209,6 +209,8 @@ test('Should update the project info', async (done) => { * Jest has detected the following 1 open handle potentially keeping Jest from exiting */ afterAll(async () => { + // avoid jest open handle error + await new Promise((resolve) => setTimeout(() => resolve(), 500)) // close server await server.close() // delete all the projects project testing diff --git a/test/url.test.js b/test/url.test.js index 3d1e0d7..1105634 100644 --- a/test/url.test.js +++ b/test/url.test.js @@ -1,4 +1,4 @@ -const app = require('../app') +const app = require('../app').app const mongoose = require('mongoose') const request = require('supertest') const UrlModel = require('../app/models/UrlShortner') @@ -58,6 +58,8 @@ test('Should short the URL', async (done) => { // }) afterAll(async () => { + // avoid jest open handle error + await new Promise((resolve) => setTimeout(() => resolve(), 500)) // close server await server.close() // Closing the DB connection allows Jest to exit successfully. diff --git a/test/user.test.js b/test/user.test.js index 9846440..04534ec 100644 --- a/test/user.test.js +++ b/test/user.test.js @@ -1,4 +1,4 @@ -const app = require('../app') +const app = require('../app').app const mongoose = require('mongoose') const jwt = require('jsonwebtoken') const request = require('supertest') @@ -230,13 +230,14 @@ test('Should update the password ', async () => { }) /* Activate account */ -test('Should activate the account ', async () => { +test('Should activate the account ', async (done) => { await request(app) .get(`/user/activate/${token}`) .send({ token: `${token}` }) .expect(HttpStatus.OK) + done() }) /* Get invite link */ @@ -329,6 +330,8 @@ test('Should UnBlock the user', async (done) => { * Jest has detected the following 1 open handle potentially keeping Jest from exiting */ afterAll(async () => { + // avoid jest open handle error + await new Promise((resolve) => setTimeout(() => resolve(), 500)) // close server await server.close() // delete all the users post testing From 52b4fd002c93580b271702b057ea50cedadf901d Mon Sep 17 00:00:00 2001 From: Asel Date: Wed, 24 Jun 2020 12:41:37 +0530 Subject: [PATCH 30/42] minor changes --- app.js | 106 ++++++++++--------- app/controllers/proposal.js | 175 ++++++++++++++++++++------------ app/models/Proposal.js | 40 ++++---- app/routes/proposal.js | 30 +++--- config/fileHandlingConstants.js | 5 + config/mongoose.js | 21 ++-- package-lock.json | 9 ++ package.json | 1 + 8 files changed, 225 insertions(+), 162 deletions(-) create mode 100644 config/fileHandlingConstants.js diff --git a/app.js b/app.js index cb256f9..fcc9876 100644 --- a/app.js +++ b/app.js @@ -1,72 +1,70 @@ -require('./config/mongoose') -const express = require('express') -const logger = require('morgan') -const cookieParser = require('cookie-parser') -const createError = require('http-errors') -const path = require('path') -const multer = require('multer') -const bodyParser = require('body-parser') +require("./config/mongoose"); +const express = require("express"); +const logger = require("morgan"); +const cookieParser = require("cookie-parser"); +const createError = require("http-errors"); +const path = require("path"); +const multer = require("multer"); +const bodyParser = require("body-parser"); +const cors = require("cors"); +const fileConstants = require("./config/fileHandlingConstants"); -const indexRouter = require('./app/routes/index') -const authRouter = require('./app/routes/auth') -const usersRouter = require('./app/routes/user') -const postRouter = require('./app/routes/post') -const eventRouter = require('./app/routes/event') -const shortUrlRouter = require('./app/routes/urlShortner') -const organizationRouter = require('./app/routes/organisation') -const commentRouter = require('./app/routes/comment') -const projectRouter = require('./app/routes/project') -const proposalRouter = require('./app/routes/proposal') +const indexRouter = require("./app/routes/index"); +const authRouter = require("./app/routes/auth"); +const usersRouter = require("./app/routes/user"); +const postRouter = require("./app/routes/post"); +const eventRouter = require("./app/routes/event"); +const shortUrlRouter = require("./app/routes/urlShortner"); +const organizationRouter = require("./app/routes/organisation"); +const commentRouter = require("./app/routes/comment"); +const projectRouter = require("./app/routes/project"); +const proposalRouter = require("./app/routes/proposal"); -const app = express() +const app = express(); -app.use(bodyParser.json({ limit: '200mb' })) -app.use( - bodyParser.urlencoded({ - limit: '200mb', - extended: true, - parameterLimit: 1000000 - }) -) +app.use(cors()); -const memoryStorage = multer.memoryStorage() -app.use(multer({ storage: memoryStorage }).single('file')) +app.use(bodyParser.json({ limit: "200mb" })); +app.use(bodyParser.urlencoded(fileConstants.fileParameters)); + +const memoryStorage = multer.memoryStorage(); +app.use(multer({ storage: memoryStorage }).single("file")); // view engine setup -app.set('views', path.join(__dirname, 'views')) -app.set('view engine', 'ejs') +app.set("views", path.join(__dirname, "views")); +app.set("view engine", "ejs"); -app.use(logger('tiny')) -app.use(express.json()) -app.use(express.urlencoded({ extended: false })) -app.use(cookieParser()) -app.use(express.static(path.join(__dirname, 'public'))) +app.use(logger("tiny")); +app.use(express.json()); +app.use(express.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, "public"))); -app.use('/', indexRouter) -app.use('/auth', authRouter) -app.use('/user', usersRouter) -app.use('/post', postRouter) -app.use('/org', organizationRouter) -app.use('/event', eventRouter) -app.use('/shortUrl', shortUrlRouter) -app.use('/comment', commentRouter) -app.use('/project', projectRouter) -app.use('/proposal', proposalRouter) +app.use("/", indexRouter); +app.use("/auth", authRouter); +app.use("/user", usersRouter); +app.use("/post", postRouter); +app.use("/org", organizationRouter); +app.use("/event", eventRouter); +app.use("/shortUrl", shortUrlRouter); +app.use("/comment", commentRouter); +app.use("/project", projectRouter); +app.use("/proposal", proposalRouter); // catch 404 and forward to error handler app.use(function (req, res, next) { - next(createError(404, "route doesn't exist")) -}) + next(createError(404, "route doesn't exist")); +}); // error handler app.use(function (err, req, res, next) { // set locals, only providing error in development - res.locals.message = err.message - res.locals.error = req.app.get('env') === 'development' ? err : {} + res.locals.message = err.message; + res.locals.error = req.app.get("env") === "development" ? err : {}; // render the error page - res.status(err.status || 500) - res.render('error') -}) + res.status(err.status || 500); + res.render("error"); +}); -module.exports = app +module.exports = app; diff --git a/app/controllers/proposal.js b/app/controllers/proposal.js index 7e78b2b..5f4b21d 100644 --- a/app/controllers/proposal.js +++ b/app/controllers/proposal.js @@ -1,150 +1,199 @@ -const ProposalModel = require('../models/Proposal') -const UserModal = require('../models/User') -const HANDLER = require('../utils/response-helper') -const HttpStatus = require('http-status-codes') -const AWS = require('aws-sdk') +const ProposalModel = require("../models/Proposal"); +const UserModal = require("../models/User"); +const HANDLER = require("../utils/response-helper"); +const HttpStatus = require("http-status-codes"); +const AWS = require("aws-sdk"); module.exports = { // Creating a proposal createProposal: async (req, res, next) => { - const proposal = new ProposalModel(req.body) + const proposal = new ProposalModel(req.body); try { - await proposal.save() - res.status(HttpStatus.CREATED).json({ proposal }) + await proposal.save(); + res.status(HttpStatus.CREATED).json({ proposal }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // Updates the content of the proposal saveProposal: async (req, res, next) => { - const { proposalId } = req.params - const content = req.body.content + const { proposalId } = req.params; + const content = req.body.content; + const title = req.body.title; + const description = req.body.description; try { const proposal = await ProposalModel.findByIdAndUpdate(proposalId, { - content: content - }) + content: content, + title: title, + proposalDescription: description, + }); if (!proposal) { return res .status(HttpStatus.NOT_FOUND) - .json({ message: 'No proposal exists under the provided ID' }) + .json({ message: "No proposal exists under the provided ID" }); } - res.status(HttpStatus.OK).json({ proposal: proposal }) + res.status(HttpStatus.OK).json({ proposal: proposal }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // attaches a file to the given proposal attachFile: (req, res, next) => { - const { proposalId } = req.params - const file = req.file - const s3FileURL = process.env.AWS_UPLOADED_FILE_URL_LINK + const { proposalId } = req.params; + const file = req.file; + const s3FileURL = process.env.AWS_UPLOADED_FILE_URL_LINK; const s3bucket = new AWS.S3({ accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, - region: process.env.AWS_REGION - }) + region: process.env.AWS_REGION, + }); var params = { Bucket: process.env.AWS_BUCKET_NAME, Key: file.originalname, Body: file.buffer, ContentType: file.mimetype, - ACL: 'public-read' - } + ACL: "public-read", + }; s3bucket.upload(params, function (err, data) { if (err) { - res.status(500).json({ error: true, Message: err }) + res.status(500).json({ error: true, Message: err }); } else { - res.send({ data }) var newFileUploaded = { - description: req.body.description, fileLink: s3FileURL + file.originalname, - s3_key: params.Key - } + s3_key: params.Key, + }; + + console.log(proposalId); ProposalModel.updateOne( { _id: proposalId }, { $push: { attachments: newFileUploaded } } - ).then((proposal) => { - console.log(proposal) - }) + ); + res.send({ data }); } - }) + }); }, // Get proposals by userId getByUserId: async (req, res, next) => { - const userId = req.body.userId + const { userId } = req.params; try { - const proposals = await ProposalModel.find({ creator: userId }) + const proposals = await ProposalModel.find({ creator: userId }); - console.log(proposals) if (!proposals) { return res .status(HttpStatus.NOT_FOUND) - .json({ message: 'No proposals found for the given user ID' }) + .json({ message: "No proposals found for the given user ID" }); } - return res.status(HttpStatus.OK).json({ proposal: proposals }) + return res.status(HttpStatus.OK).json({ proposal: proposals }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // Delete proposal by proposalId deleteById: async (req, res, next) => { try { - const proposalId = req.body.proposalId + const proposalId = req.body.proposalId; - const result = await ProposalModel.findByIdAndDelete(proposalId) - return res.status(HttpStatus.OK).json({ result: result }) + const result = await ProposalModel.findByIdAndDelete(proposalId); + return res.status(HttpStatus.OK).json({ result: result }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // Changes the state of a given proposal changeState: async (req, res, next) => { - const proposalId = req.body.proposalId - const userId = req.body.userId - const proposalStatus = req.body.proposalStatus + const { proposalId } = req.params; + const proposalStatus = req.body.proposalStatus; try { - const user = await UserModal.findById(userId) + const proposal = await ProposalModel.findByIdAndUpdate(proposalId, { + proposalStatus: proposalStatus, + }); + if (!proposal) { + return res + .status(HttpStatus.NOT_FOUND) + .json({ message: "No proposal exists under the provided ID" }); + } + res.status(HttpStatus.OK).json({ proposal: proposal }); + } catch (error) { + HANDLER.handleError(res, error); + } + }, - if (user.isAdmin === true) { - const proposal = ProposalModel.findByIdAndUpdate(proposalId, { - proposalStatus: proposalStatus - }) + // Obtains the proposal by given proposal ID + getProposalById: async (req, res, next) => { + const { proposalId } = req.params; - return res.status(HttpStatus.OK).json({ proposal: proposal }) + try { + const proposal = await ProposalModel.findById(proposalId); + + if (!proposal) { + return res + .status(HttpStatus.NOT_FOUND) + .json({ error: "Proposal not found" }); + } + return res.status(HttpStatus.OK).json({ proposal: proposal }); + } catch (error) { + HANDLER.handleError(res, error); + } + }, + + getAllProposals: async (req, res, next) => { + try { + const user = await UserModal.findById(req.body.userId); + + if (user.isAdmin == true) { + const proposals = await ProposalModel.find({}); + + if (!proposals.length) { + return res + .status(HttpStatus.NOT_FOUND) + .json({ message: "No posts found" }); + } + return res.status(HttpStatus.OK).json({ proposals: proposals }); } return res .status(HttpStatus.BAD_REQUEST) - .json({ msg: "You don't have permission!" }) + .json({ msg: "You don't have permission for this request" }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, - // Obtains the proposal by given proposal ID - getProposalById: async (req, res, next) => { - const { proposalId } = req.params + commentOnProposal: async (req, res, next) => { + const { proposalId, comment, userId } = req.body; try { - const proposal = await ProposalModel.findById(proposalId) + const user = await UserModal.findById(userId); + if (!user) { + return res + .status(HttpStatus.BAD_REQUEST) + .json({ message: "No user exists" }); + } + const name = `${user.name.firstName} ${user.name.lastName}`; + + const proposal = await ProposalModel.updateOne( + { _id: proposalId }, + { $push: { comments: { userName: name, comment: comment } } } + ); if (!proposal) { return res - .status(HttpStatus.NOT_FOUND) - .json({ error: 'Proposal not found' }) + .status(HttpStatus.BAD_REQUEST) + .json({ message: "Proposal could not be found!" }); } - return res.status(HttpStatus.OK).json({ proposal: proposal }) + + return res.status(HttpStatus.OK).json({ proposal: proposal }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } - } -} + }, +}; diff --git a/app/models/Proposal.js b/app/models/Proposal.js index e4b9fef..f1e412c 100644 --- a/app/models/Proposal.js +++ b/app/models/Proposal.js @@ -1,48 +1,42 @@ -const mongoose = require('mongoose') -const Schema = mongoose.Schema -const validator = require('validator') +const mongoose = require("mongoose"); +const Schema = mongoose.Schema; +const validator = require("validator"); const proposalSchema = new Schema( { title: { type: String, - required: true, - validate (title) { - if (validator.isEmpty(title)) { - throw new Error('Proposal Title cannot be kept empty') - } - } - }, - organization: { - type: Schema.Types.ObjectId, - ref: 'Organization' }, content: { - type: String + type: String, }, proposalStatus: { type: String, - required: true, - default: 'draft' + default: "draft", }, creator: { type: Schema.Types.ObjectId, - ref: 'User' + ref: "User", + required: true, + }, + proposalDescription: { + type: String, }, - attachments: [{ description: String, fileLink: String, s3_key: String }], + attachments: [{ fileLink: String, s3_key: String }], createdAt: { type: Date, required: true, - default: Date.now() + default: Date.now(), }, updatedAt: { type: Date, required: true, - default: Date.now() - } + default: Date.now(), + }, + comments: [{ userName: String, comment: String }], }, { timestamps: true } -) +); -module.exports = mongoose.model('Proposal', proposalSchema) +module.exports = mongoose.model("Proposal", proposalSchema); diff --git a/app/routes/proposal.js b/app/routes/proposal.js index 38bc79c..e9011e3 100644 --- a/app/routes/proposal.js +++ b/app/routes/proposal.js @@ -1,27 +1,33 @@ -const express = require('express') -const router = express.Router() -const auth = require('../middleware/auth') -const proposalController = require('../controllers/proposal') +const express = require("express"); +const router = express.Router(); +const auth = require("../middleware/auth"); +const proposalController = require("../controllers/proposal"); // Create a new proposal -router.post('/', auth, proposalController.createProposal) +router.post("/", auth, proposalController.createProposal); // Save the content of a proposal -router.patch('/:proposalId', auth, proposalController.saveProposal) +router.patch("/:proposalId", auth, proposalController.saveProposal); // Attach file to the given proposal -router.post('/attach/:proposalId', auth, proposalController.attachFile) +router.post("/attach/:proposalId", auth, proposalController.attachFile); // Get proposals by userId -router.get('/', auth, proposalController.getByUserId) +router.get("/user/:userId", auth, proposalController.getByUserId); // get proposal by proposalId -router.get('/:proposalId', auth, proposalController.getProposalById) +router.get("/:proposalId", auth, proposalController.getProposalById); // Deletes a proposal by given proposalId -router.delete('/', auth, proposalController.deleteById) +router.delete("/", auth, proposalController.deleteById); // Update proposal state -router.patch('/change', auth, proposalController.changeState) +router.patch("/change/:proposalId", auth, proposalController.changeState); -module.exports = router +// Get all the proposals +router.post("/all", auth, proposalController.getAllProposals); + +// Comment on the given proposal +router.post("/comment", auth, proposalController.commentOnProposal); + +module.exports = router; diff --git a/config/fileHandlingConstants.js b/config/fileHandlingConstants.js new file mode 100644 index 0000000..f10cb63 --- /dev/null +++ b/config/fileHandlingConstants.js @@ -0,0 +1,5 @@ +module.exports.fileParameters = { + limit: "200mb", + extended: true, + parameterLimit: 1000000, +}; diff --git a/config/mongoose.js b/config/mongoose.js index 2904b19..8e59c2a 100644 --- a/config/mongoose.js +++ b/config/mongoose.js @@ -1,14 +1,15 @@ -const mongoose = require('mongoose') +const mongoose = require("mongoose"); -mongoose.connect(process.env.DATABASE_URL, { - useNewUrlParser: true, - useCreateIndex: true, - useUnifiedTopology: true, - useFindAndModify: false -}) +mongoose + .connect(process.env.DATABASE_URL, { + useNewUrlParser: true, + useCreateIndex: true, + useUnifiedTopology: true, + useFindAndModify: false, + }) .then(() => { - console.log('mongodb connection successful') + console.log("mongodb connection successful"); }) .catch((err) => { - console.log('mongodb connection error', err) - }) + console.log("mongodb connection error", err); + }); diff --git a/package-lock.json b/package-lock.json index 1c213df..cd5a2b6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1600,6 +1600,15 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", diff --git a/package.json b/package.json index 8208660..7840ac5 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "bcrypt": "^3.0.6", "body-parser": "^1.19.0", "cookie-parser": "~1.4.4", + "cors": "^2.8.5", "crypto": "^1.0.1", "debug": "~2.6.9", "dotenv": "^8.2.0", From fe1aa8a43e25fadaac690ee31ab2965451d9da83 Mon Sep 17 00:00:00 2001 From: Asel Date: Mon, 29 Jun 2020 17:56:50 +0530 Subject: [PATCH 31/42] Improvements to proposal routes, Handling state changes --- .env.dev | 8 +- app.js | 83 ++----- app/controllers/notification.js | 53 +++-- app/controllers/organization.js | 347 +++++++++++++++++------------ app/controllers/post.js | 210 +++++++++-------- app/controllers/proposal.js | 98 +++++++- app/models/ProposalNotification.js | 22 ++ app/models/User.js | 244 +++++++++++--------- app/routes/notification.js | 27 ++- app/routes/proposal.js | 6 + app/utils/notificationTags.js | 19 +- app/utils/proposal-notif-helper.js | 26 +++ package-lock.json | 16 +- 13 files changed, 681 insertions(+), 478 deletions(-) create mode 100644 app/models/ProposalNotification.js create mode 100644 app/utils/proposal-notif-helper.js diff --git a/.env.dev b/.env.dev index afc775f..1f302c1 100644 --- a/.env.dev +++ b/.env.dev @@ -1,6 +1,10 @@ PORT=5000 NODE_ENV="development" JWT_SECRET="thisismysupersecrettokenjustkidding" -DATABASE_URL="mongodb://localhost:27017/donut-development" +DATABASE_URL="mongodb+srv://asel123:asel123@socialmediaappcluster-leoir.mongodb.net/example" SENDGRID_API_KEY = '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' -SOCKET_PORT = 8810 \ No newline at end of file +AWS_BUCKET_NAME=donutproposals +AWS_ACCESS_KEY_ID=AKIASECEF4XZS5OLND4S +AWS_SECRET_ACCESS_KEY=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 +AWS_REGION=ap-south-1 +AWS_UPLOADED_FILE_URL_LINK=https://donutproposals.s3.ap-south-1.amazonaws.com/ diff --git a/app.js b/app.js index f6b14d8..f2b7662 100644 --- a/app.js +++ b/app.js @@ -1,10 +1,10 @@ -<<<<<<< HEAD require("./config/mongoose"); const express = require("express"); const logger = require("morgan"); const cookieParser = require("cookie-parser"); const createError = require("http-errors"); const path = require("path"); +const socket = require("socket.io"); const multer = require("multer"); const bodyParser = require("body-parser"); const cors = require("cors"); @@ -19,9 +19,11 @@ const shortUrlRouter = require("./app/routes/urlShortner"); const organizationRouter = require("./app/routes/organisation"); const commentRouter = require("./app/routes/comment"); const projectRouter = require("./app/routes/project"); +const notificationRouter = require("./app/routes/notification"); const proposalRouter = require("./app/routes/proposal"); const app = express(); +const server = require("http").Server(app); app.use(cors()); @@ -31,6 +33,16 @@ app.use(bodyParser.urlencoded(fileConstants.fileParameters)); const memoryStorage = multer.memoryStorage(); app.use(multer({ storage: memoryStorage }).single("file")); +server.listen(process.env.SOCKET_PORT || 8810); +// WARNING: app.listen(80) will NOT work here! + +const io = socket.listen(server); +let count = 0; +io.on("connection", (socket) => { + console.log("socket connected count ", count++); + io.emit("user connected"); +}); + // view engine setup app.set("views", path.join(__dirname, "views")); app.set("view engine", "ejs"); @@ -40,7 +52,12 @@ app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, "public"))); +app.use((req, res, next) => { + req.io = io; + next(); +}); +app.use("/notification", notificationRouter); app.use("/", indexRouter); app.use("/auth", authRouter); app.use("/user", usersRouter); @@ -51,64 +68,6 @@ app.use("/shortUrl", shortUrlRouter); app.use("/comment", commentRouter); app.use("/project", projectRouter); app.use("/proposal", proposalRouter); -======= -require('./config/mongoose') -const express = require('express') -const logger = require('morgan') -const cookieParser = require('cookie-parser') -const createError = require('http-errors') -const path = require('path') -const socket = require('socket.io') - -const indexRouter = require('./app/routes/index') -const authRouter = require('./app/routes/auth') -const usersRouter = require('./app/routes/user') -const postRouter = require('./app/routes/post') -const eventRouter = require('./app/routes/event') -const shortUrlRouter = require('./app/routes/urlShortner') -const organizationRouter = require('./app/routes/organisation') -const commentRouter = require('./app/routes/comment') -const projectRouter = require('./app/routes/project') -const notificationRouter = require('./app/routes/notification') - -const app = express() -const server = require('http').Server(app) - -server.listen(process.env.SOCKET_PORT || 8810) -// WARNING: app.listen(80) will NOT work here! - -const io = socket.listen(server) -let count = 0 -io.on('connection', (socket) => { - console.log('socket connected count ', count++) - io.emit('user connected') -}) - -// view engine setup -app.set('views', path.join(__dirname, 'views')) -app.set('view engine', 'ejs') - -app.use(logger('tiny')) -app.use(express.json()) -app.use(express.urlencoded({ extended: false })) -app.use(cookieParser()) -app.use(express.static(path.join(__dirname, 'public'))) -app.use((req, res, next) => { - req.io = io - next() -}) - -app.use('/notification', notificationRouter) -app.use('/', indexRouter) -app.use('/auth', authRouter) -app.use('/user', usersRouter) -app.use('/post', postRouter) -app.use('/org', organizationRouter) -app.use('/event', eventRouter) -app.use('/shortUrl', shortUrlRouter) -app.use('/comment', commentRouter) -app.use('/project', projectRouter) ->>>>>>> cf9a8ab8cc6eb2b29aeb7f4caf6de92494275952 // catch 404 and forward to error handler app.use(function (req, res, next) { @@ -126,8 +85,4 @@ app.use(function (err, req, res, next) { res.render("error"); }); -<<<<<<< HEAD -module.exports = app; -======= -module.exports = { app, io } ->>>>>>> cf9a8ab8cc6eb2b29aeb7f4caf6de92494275952 +module.exports = { app, io }; diff --git a/app/controllers/notification.js b/app/controllers/notification.js index 5d5bfa8..440b940 100644 --- a/app/controllers/notification.js +++ b/app/controllers/notification.js @@ -1,38 +1,55 @@ -const HANDLER = require('../utils/response-helper') -const HttpStatus = require('http-status-codes') -const Notifications = require('../models/Notifications') -const helper = require('../utils/paginate') -const User = require('../models/User') +const HANDLER = require("../utils/response-helper"); +const HttpStatus = require("http-status-codes"); +const Notifications = require("../models/Notifications"); +const helper = require("../utils/paginate"); +const User = require("../models/User"); +const ProposalNotifications = require("../models/ProposalNotification"); module.exports = { // GET ALL THE NOTIFICATIONS FOR ALL getOrgNotifications: async (req, res, next) => { try { - const notifications = await Notifications.find({}, {}, helper.paginate(req)) + const notifications = await Notifications.find( + {}, + {}, + helper.paginate(req) + ) .lean() .sort({ createdAt: -1 }) - .exec() - return res.status(HttpStatus.OK).json({ notifications }) + .exec(); + return res.status(HttpStatus.OK).json({ notifications }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // GET LOGGED IN USER NOTIFICATIONS getUserNotification: async (req, res, next) => { - const userId = req.user._id + const userId = req.user._id; try { - const user = await User.findById(userId) + const user = await User.findById(userId); if (!user) { - return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'No such user exists!' }) + return res + .status(HttpStatus.BAD_REQUEST) + .json({ msg: "No such user exists!" }); } // get all notifications of existing user - const notifications = user.notifications + const notifications = user.notifications; if (notifications.length === 0) { - return res.status(HttpStatus.OK).json({ msg: 'No new notifications!' }) + return res.status(HttpStatus.OK).json({ msg: "No new notifications!" }); } - return res.status(HttpStatus.OK).json({ notifications }) + return res.status(HttpStatus.OK).json({ notifications }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } - } -} + }, + + getProposalNotifications: async (req, res, next) => { + try { + const notifications = await ProposalNotifications.find({}); + console.log(notifications); + return res.status(HttpStatus.OK).json({ notifications }); + } catch (error) { + HANDLER.handleError(res, error); + } + }, +}; diff --git a/app/controllers/organization.js b/app/controllers/organization.js index aaf2fb8..63fde5c 100644 --- a/app/controllers/organization.js +++ b/app/controllers/organization.js @@ -1,284 +1,337 @@ -const Organization = require('../models/Organisation') -const HANDLER = require('../utils/response-helper') -const HttpStatus = require('http-status-codes') -const helper = require('../utils/uploader') -const notificationHelper = require('../utils/notif-helper') -const User = require('../models/User') -const Project = require('../models/Project') -const Event = require('../models/Event') -const permission = require('../utils/permission') -const TAGS = require('../utils/notificationTags') +const Organization = require("../models/Organisation"); +const HANDLER = require("../utils/response-helper"); +const HttpStatus = require("http-status-codes"); +const helper = require("../utils/uploader"); +const notificationHelper = require("../utils/notif-helper"); +const User = require("../models/User"); +const Project = require("../models/Project"); +const Event = require("../models/Event"); +const permission = require("../utils/permission"); +const TAGS = require("../utils/notificationTags"); const notification = { - heading: '', - content: '', - tag: '' -} + heading: "", + content: "", + tag: "", +}; module.exports = { createOrganization: async (req, res, next) => { - const org = new Organization(req.body) + const org = new Organization(req.body); if (req.file) { - helper.mapToDb(req, org) + helper.mapToDb(req, org); } try { - await org.save() - req.io.emit('new org created', { data: org.name }) - notification.heading = 'New org!' - notification.content = `${org.name} is created!` - notification.tag = TAGS.NEW - notificationHelper.addToNotificationForAll(req, res, notification, next) - return res.status(HttpStatus.CREATED).json({ org }) + await org.save(); + req.io.emit("new org created", { data: org.name }); + notification.heading = "New org!"; + notification.content = `${org.name} is created!`; + notification.tag = TAGS.NEW; + notificationHelper.addToNotificationForAll(req, res, notification, next); + return res.status(HttpStatus.CREATED).json({ org }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, updateOrgDetails: async (req, res, next) => { - const { id } = req.params - const updates = Object.keys(req.body) - const allowedUpdates = ['name', 'description', 'contactInfo', 'image', 'imgUrl', 'adminInfo', 'moderatorInfo'] + const { id } = req.params; + const updates = Object.keys(req.body); + const allowedUpdates = [ + "name", + "description", + "contactInfo", + "image", + "imgUrl", + "adminInfo", + "moderatorInfo", + ]; const isValidOperation = updates.every((update) => { - return allowedUpdates.includes(update) - }) + return allowedUpdates.includes(update); + }); if (!isValidOperation) { - return res.status(HttpStatus.BAD_REQUEST).json({ error: 'invalid update' }) + return res + .status(HttpStatus.BAD_REQUEST) + .json({ error: "invalid update" }); } try { - const org = await Organization.findById(id) + const org = await Organization.findById(id); // check for permission (ONLY ADMINS CAN UPDATE) if (!permission.check(req, res)) { - return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have the permission' }) + return res + .status(HttpStatus.BAD_REQUEST) + .json({ msg: "You don't have the permission" }); } - updates.forEach(update => { - org[update] = req.body[update] - }) + updates.forEach((update) => { + org[update] = req.body[update]; + }); if (req.file) { - helper.mapToDb(req, org) + helper.mapToDb(req, org); } - await org.save() - res.status(HttpStatus.OK).json({ organization: org }) + await org.save(); + res.status(HttpStatus.OK).json({ organization: org }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, getOrgDetailsById: async (req, res, next) => { - const { id } = req.params + const { id } = req.params; try { const orgData = await Organization.findById(id) - .populate('adminInfo', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) - .populate('moderatorInfo', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) + .populate("adminInfo", [ + "name.firstName", + "name.lastName", + "email", + "isAdmin", + ]) + .populate("moderatorInfo", [ + "name.firstName", + "name.lastName", + "email", + "isAdmin", + ]) .sort({ createdAt: -1 }) .lean() - .exec() + .exec(); if (!orgData) { - return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such organization exists!' }) + return res + .status(HttpStatus.NOT_FOUND) + .json({ error: "No such organization exists!" }); } - res.status(HttpStatus.OK).json({ organization: orgData }) + res.status(HttpStatus.OK).json({ organization: orgData }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, deleteOrg: async (req, res, next) => { - const { id } = req.params + const { id } = req.params; try { - const org = await Organization.findByIdAndRemove(id) + const org = await Organization.findByIdAndRemove(id); if (!org) { - return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such organization exists!' }) + return res + .status(HttpStatus.NOT_FOUND) + .json({ error: "No such organization exists!" }); } // check for permission if (!permission.check(req, res)) { - return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have the permission!' }) + return res + .status(HttpStatus.BAD_REQUEST) + .json({ msg: "You don't have the permission!" }); } - req.io.emit('org deleted', { data: org.name }) - notification.heading = 'Org deleted!' - notification.content = `${org.name} is deleted!` - notification.tag = TAGS.DELETE - notificationHelper.addToNotificationForAll(req, res, notification, next) - return res.status(HttpStatus.OK).json({ organization: org }) + req.io.emit("org deleted", { data: org.name }); + notification.heading = "Org deleted!"; + notification.content = `${org.name} is deleted!`; + notification.tag = TAGS.DELETE; + notificationHelper.addToNotificationForAll(req, res, notification, next); + return res.status(HttpStatus.OK).json({ organization: org }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, archiveOrg: async (req, res, next) => { - const { id } = req.params + const { id } = req.params; try { - const org = await Organization.findById(id) + const org = await Organization.findById(id); if (!org) { - return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such organization exists!' }) + return res + .status(HttpStatus.NOT_FOUND) + .json({ error: "No such organization exists!" }); } - org.isArchived = true - await org.save() - return res.status(HttpStatus.OK).json({ organization: org }) + org.isArchived = true; + await org.save(); + return res.status(HttpStatus.OK).json({ organization: org }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, triggerMaintenance: async (req, res, next) => { - const { id } = req.params + const { id } = req.params; try { - const organization = await Organization.findById(id) + const organization = await Organization.findById(id); // if org exists or not if (!organization) { - return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such organization exists!' }) + return res + .status(HttpStatus.NOT_FOUND) + .json({ error: "No such organization exists!" }); } // if user is admin or not - const adminIds = organization.adminInfo.adminId - const isAdmin = adminIds.indexOf(req.user.id) || req.user.isAdmin + const adminIds = organization.adminInfo.adminId; + const isAdmin = adminIds.indexOf(req.user.id) || req.user.isAdmin; // user is admin then perform operation if (isAdmin !== -1 || req.user.isAdmin) { // toggle maintenance mode - organization.isMaintenance = !organization.isMaintenance - await organization.save() - notification.tag = TAGS.MAINTENANCE + organization.isMaintenance = !organization.isMaintenance; + await organization.save(); + notification.tag = TAGS.MAINTENANCE; if (organization.isMaintenance) { - req.io.emit('org under maintenance', { data: organization.name }) - notification.heading = 'Maintenance mode on!' - notification.content = `${organization.name} is kept under maintenance!` - notificationHelper.addToNotificationForAll(req, res, notification, next) - return res.status(HttpStatus.OK).json({ msg: 'Organization is kept under the maintenance!!' }) + req.io.emit("org under maintenance", { data: organization.name }); + notification.heading = "Maintenance mode on!"; + notification.content = `${organization.name} is kept under maintenance!`; + notificationHelper.addToNotificationForAll( + req, + res, + notification, + next + ); + return res + .status(HttpStatus.OK) + .json({ msg: "Organization is kept under the maintenance!!" }); } - req.io.emit('org revoked maintenance', { data: organization.name }) - notification.heading = 'Maintenance mode off!' - notification.content = `${organization.name} is revoked from maintenance!` - return res.status(HttpStatus.OK).json({ msg: 'Organization is recovered from maintenance!!' }) + req.io.emit("org revoked maintenance", { data: organization.name }); + notification.heading = "Maintenance mode off!"; + notification.content = `${organization.name} is revoked from maintenance!`; + return res + .status(HttpStatus.OK) + .json({ msg: "Organization is recovered from maintenance!!" }); } else { - return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have access to triggerMaintenance!' }) + return res + .status(HttpStatus.BAD_REQUEST) + .json({ msg: "You don't have access to triggerMaintenance!" }); } } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, updateSettings: async (req, res, next) => { - const { id } = req.params + const { id } = req.params; try { // check if org exists - const organization = await Organization.findById(id) + const organization = await Organization.findById(id); if (!organization) { - return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No Organization found!' }) + return res + .status(HttpStatus.NOT_FOUND) + .json({ msg: "No Organization found!" }); } // check if user is admin or not - const adminIds = organization.adminInfo.adminId - const isAdmin = adminIds.indexOf(req.user.id) - const updates = Object.keys(req.body) - console.log('req.body ', req.body) - console.log('isAdmin ', isAdmin) - const allowedUpdates = [ - 'settings', - 'permissions', - 'authentication' - ] + const adminIds = organization.adminInfo.adminId; + const isAdmin = adminIds.indexOf(req.user.id); + const updates = Object.keys(req.body); + console.log("req.body ", req.body); + console.log("isAdmin ", isAdmin); + const allowedUpdates = ["settings", "permissions", "authentication"]; // if admin then check if valid update if (isAdmin !== -1) { const isValidOperation = updates.every((update) => { - return allowedUpdates.includes(update) - }) + return allowedUpdates.includes(update); + }); // if valid update if (isValidOperation) { - updates.forEach(update => { - organization.options[update] = req.body[update] - }) - await organization.save() - return res.status(HttpStatus.OK).json({ msg: 'Successfully updated!' }) + updates.forEach((update) => { + organization.options[update] = req.body[update]; + }); + await organization.save(); + return res + .status(HttpStatus.OK) + .json({ msg: "Successfully updated!" }); } // invalid update - return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Invalid update' }) + return res + .status(HttpStatus.BAD_REQUEST) + .json({ msg: "Invalid update" }); } // else not admin - return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have access to perform this operation!' }) + return res + .status(HttpStatus.BAD_REQUEST) + .json({ msg: "You don't have access to perform this operation!" }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, getOrgOverView: async (req, res, next) => { - const orgOverView = {} + const orgOverView = {}; try { - const org = await Organization.find({}) + const org = await Organization.find({}); if (!org) { - return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No org exists!' }) + return res.status(HttpStatus.NOT_FOUND).json({ msg: "No org exists!" }); } - orgOverView.admins = org[0].adminInfo.length - orgOverView.members = await User.find({}).lean().count() - orgOverView.projects = await Project.find({}).lean().count() - orgOverView.events = await Event.find({}).lean().count() - return res.status(HttpStatus.OK).json({ orgOverView }) + orgOverView.admins = org[0].adminInfo.length; + orgOverView.members = await User.find({}).lean().count(); + orgOverView.projects = await Project.find({}).lean().count(); + orgOverView.events = await Event.find({}).lean().count(); + return res.status(HttpStatus.OK).json({ orgOverView }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // SEARCH FUNCTIONALITY getMembers: async (req, res, next) => { try { - const { search } = req.query + const { search } = req.query; if (search) { - const queryTerm = search.split(' ') - const regex = new RegExp('^' + queryTerm + '$', 'i') + const queryTerm = search.split(" "); + const regex = new RegExp("^" + queryTerm + "$", "i"); const member = await User.find({ $or: [ - { 'name.firstName': { $regex: regex } }, - { 'name.lastName': { $regex: regex } } - ] + { "name.firstName": { $regex: regex } }, + { "name.lastName": { $regex: regex } }, + ], }) - .select('name email isAdmin info.about.designation isRemoved') + .select("name email isAdmin info.about.designation isRemoved") .lean() .sort({ createdAt: -1 }) - .exec() + .exec(); if (!member) { - return res.status(HttpStatus.OK).json({ msg: 'Member not found!' }) + return res.status(HttpStatus.OK).json({ msg: "Member not found!" }); } - return res.status(HttpStatus.OK).json({ member }) + return res.status(HttpStatus.OK).json({ member }); } else { const members = await User.find({}) - .select('name email isAdmin info.about.designation isRemoved') + .select("name email isAdmin info.about.designation isRemoved") .lean() .sort({ createdAt: -1 }) - .exec() + .exec(); if (members.length === 0) { - return res.status(HttpStatus.OK).json({ msg: 'No members joined yet!' }) + return res + .status(HttpStatus.OK) + .json({ msg: "No members joined yet!" }); } - return res.status(HttpStatus.OK).json({ members }) + return res.status(HttpStatus.OK).json({ members }); } } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // REMOVE ADMIN removeAdmin: async (req, res, next) => { try { - const { userId, orgId } = req.params - const org = await Organization.findById(orgId) + const { userId, orgId } = req.params; + const org = await Organization.findById(orgId); if (!org) { - return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No org exists!' }) + return res.status(HttpStatus.NOT_FOUND).json({ msg: "No org exists!" }); } // only permitted for admins if (!req.user.isAdmin) { - return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You are not permitted!' }) + return res + .status(HttpStatus.BAD_REQUEST) + .json({ msg: "You are not permitted!" }); } // console.log('Permitted to removeAdmin') // REMOVE ADMINS FROM ADMINS LIST - const admins = org.adminInfo.adminId - console.log('adminIds ', admins) - const removableIndex = admins.indexOf(userId) + const admins = org.adminInfo.adminId; + console.log("adminIds ", admins); + const removableIndex = admins.indexOf(userId); if (removableIndex === -1) { - return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'User is not an admin!' }) + return res + .status(HttpStatus.BAD_REQUEST) + .json({ msg: "User is not an admin!" }); } // user is admin so remove - org.adminInfo.adminId.splice(removableIndex, 1) - await org.save() + org.adminInfo.adminId.splice(removableIndex, 1); + await org.save(); // also make isAdmin false - const user = await User.findById(userId) - user.isAdmin = false - await user.save() - return res.status(HttpStatus.OK).json({ org }) + const user = await User.findById(userId); + user.isAdmin = false; + await user.save(); + return res.status(HttpStatus.OK).json({ org }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } - } -} + }, +}; diff --git a/app/controllers/post.js b/app/controllers/post.js index f1f067f..369f268 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -1,123 +1,112 @@ -const PostModel = require('../models/Post') -const UserModel = require('../models/User') -const HANDLER = require('../utils/response-helper') -const HttpStatus = require('http-status-codes') -const imgUploadHelper = require('../utils/uploader') -const permission = require('../utils/permission') -const helper = require('../utils/paginate') +const PostModel = require("../models/Post"); +const UserModel = require("../models/User"); +const HANDLER = require("../utils/response-helper"); +const HttpStatus = require("http-status-codes"); +const imgUploadHelper = require("../utils/uploader"); +const permission = require("../utils/permission"); +const helper = require("../utils/paginate"); module.exports = { // CREATE POST create: async (req, res, next) => { - const post = new PostModel(req.body) - const userId = req.user.id.toString() - post.userId = userId + const post = new PostModel(req.body); + const userId = req.user.id.toString(); + post.userId = userId; if (req.file) { - imgUploadHelper.mapToDb(req, post) + imgUploadHelper.mapToDb(req, post); } try { - await post.save() + await post.save(); // req.io.emit('new post created', { data: post.content }) - return res.status(HttpStatus.CREATED).json({ post }) + return res.status(HttpStatus.CREATED).json({ post }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // DELETE POST delete: async (req, res, next) => { - const { id } = req.params + const { id } = req.params; try { - const post = await PostModel.findById(id) + const post = await PostModel.findById(id); if (!post) { return res .status(HttpStatus.NOT_FOUND) - .json({ message: 'No post exists' }) + .json({ message: "No post exists" }); } -<<<<<<< HEAD - // TODO ADD ADMIN RIGHTS AS WELL - if (JSON.stringify(userId) !== JSON.stringify(post.userId)) { - return res - .status(HttpStatus.FORBIDDEN) - .json({ message: 'Bad delete request' }) -======= if (!permission.check(req, res, post.userId)) { - return res.status(HttpStatus.BAD_REQUEST).json({ message: 'Bad delete request' }) ->>>>>>> cf9a8ab8cc6eb2b29aeb7f4caf6de92494275952 + return res + .status(HttpStatus.BAD_REQUEST) + .json({ message: "Bad delete request" }); } - await PostModel.findByIdAndRemove(id) - res.status(HttpStatus.OK).json({ post: post, msg: 'Deleted!' }) + await PostModel.findByIdAndRemove(id); + res.status(HttpStatus.OK).json({ post: post, msg: "Deleted!" }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // UPDATE POST updatePost: async (req, res, next) => { - const { id } = req.params - const updates = Object.keys(req.body) - const allowedUpdates = ['content', 'imgUrl'] + const { id } = req.params; + const updates = Object.keys(req.body); + const allowedUpdates = ["content", "imgUrl"]; const isValidOperation = updates.every((update) => { - return allowedUpdates.includes(update) - }) + return allowedUpdates.includes(update); + }); if (!isValidOperation) { return res .status(HttpStatus.BAD_REQUEST) - .json({ message: 'Invalid Update' }) + .json({ message: "Invalid Update" }); } try { - const post = await PostModel.findById(id) + const post = await PostModel.findById(id); if (!post) { return res .status(HttpStatus.BAD_REQUEST) - .json({ message: 'No post exists' }) + .json({ message: "No post exists" }); } -<<<<<<< HEAD - if (JSON.stringify(userId) !== JSON.stringify(post.userId)) { + if (!permission.check(req, res, post.userId)) { return res .status(HttpStatus.FORBIDDEN) - .json({ message: 'Bad update request' }) -======= - if (!permission.check(req, res, post.userId)) { - return res.status(HttpStatus.FORBIDDEN).json({ message: 'Bad update request' }) ->>>>>>> cf9a8ab8cc6eb2b29aeb7f4caf6de92494275952 + .json({ message: "Bad update request" }); } updates.forEach((update) => { - post[update] = req.body[update] - }) + post[update] = req.body[update]; + }); if (req.file) { - imgUploadHelper.mapToDb(req, post) + imgUploadHelper.mapToDb(req, post); } - await post.save() - res.status(HttpStatus.OK).json({ post: post }) + await post.save(); + res.status(HttpStatus.OK).json({ post: post }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // GET POST BY ID getPostById: async (req, res, next) => { - const { id } = req.params + const { id } = req.params; try { const post = await PostModel.findById(id) - .populate('comments', ['content', 'votes']) - .populate('userId', [ - 'name.firstName', - 'name.lastName', - 'email', - 'isAdmin' + .populate("comments", ["content", "votes"]) + .populate("userId", [ + "name.firstName", + "name.lastName", + "email", + "isAdmin", ]) .lean() - .exec() + .exec(); if (!post) { return res .status(HttpStatus.NOT_FOUND) - .json({ error: 'Post not found' }) + .json({ error: "Post not found" }); } - res.status(HttpStatus.OK).json({ post: post }) + res.status(HttpStatus.OK).json({ post: post }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, @@ -125,49 +114,49 @@ module.exports = { getAllPost: async (req, res, next) => { try { const posts = await PostModel.find({}, {}, helper.paginate(req)) - .populate('userId', [ - 'name.firstName', - 'name.lastName', - 'email', - 'isAdmin' + .populate("userId", [ + "name.firstName", + "name.lastName", + "email", + "isAdmin", ]) .sort({ updatedAt: -1 }) - .exec() + .exec(); if (!posts.length) { return res .status(HttpStatus.NOT_FOUND) - .json({ message: 'No posts found' }) + .json({ message: "No posts found" }); } - return res.status(HttpStatus.OK).json({ posts: posts }) + return res.status(HttpStatus.OK).json({ posts: posts }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // UPVOTE POST upvote: async (req, res, next) => { - const { id } = req.params - const userId = req.user.id.toString() + const { id } = req.params; + const userId = req.user.id.toString(); try { - const post = await PostModel.findById(id) + const post = await PostModel.findById(id); if (!post) { return res .status(HttpStatus.NOT_FOUND) - .json({ error: 'No post found' }) + .json({ error: "No post found" }); } // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT post.votes.upVotes.user.filter((user) => { if (JSON.stringify(user) === JSON.stringify(userId)) { return res .status(HttpStatus.BAD_REQUEST) - .json({ error: 'Bad request' }) + .json({ error: "Bad request" }); } - }) - post.votes.upVotes.user.unshift(userId) - await post.save() - res.status(HttpStatus.OK).json({ post: post }) + }); + post.votes.upVotes.user.unshift(userId); + await post.save(); + res.status(HttpStatus.OK).json({ post: post }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, @@ -178,65 +167,70 @@ module.exports = { {}, helper.paginate(req) ) - .populate('comments', ['content', 'votes']) - .populate('userId', ['name.firstName', 'name.lastName', '_id', 'isAdmin']) + .populate("comments", ["content", "votes"]) + .populate("userId", [ + "name.firstName", + "name.lastName", + "_id", + "isAdmin", + ]) .sort({ updatedAt: -1 }) - .exec() - return res.status(HttpStatus.OK).json({ posts }) + .exec(); + return res.status(HttpStatus.OK).json({ posts }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // PIN THE POST pinPost: async (req, res, next) => { - const { id } = req.params + const { id } = req.params; try { - const post = await PostModel.findById(id) - const user = await UserModel.findById(req.user._id) + const post = await PostModel.findById(id); + const user = await UserModel.findById(req.user._id); if (!post) { return res .status(HttpStatus.NOT_FOUND) - .json({ msg: 'No such post exists!' }) + .json({ msg: "No such post exists!" }); } // toggle pinned post - post.isPinned = !post.isPinned + post.isPinned = !post.isPinned; // if already pinned then remove from their pinned items - const PinnedItems = user.pinned.postId + const PinnedItems = user.pinned.postId; if (PinnedItems.length > 0) { - const pinnedPostIndex = PinnedItems.indexOf(id) - user.pinned.postId.splice(pinnedPostIndex, 1) - await user.save() + const pinnedPostIndex = PinnedItems.indexOf(id); + user.pinned.postId.splice(pinnedPostIndex, 1); + await user.save(); } else { // save to the user pinned items - user.pinned.postId.unshift(id) - await user.save() + user.pinned.postId.unshift(id); + await user.save(); } - await post.save() - return res.status(HttpStatus.OK).json({ post }) + await post.save(); + return res.status(HttpStatus.OK).json({ post }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } }, // GET ALL PINNED POST getPinned: async (req, res, next) => { try { - const posts = await PostModel.find({}) + const posts = await PostModel.find({}); if (posts.length === 0) { - return res.status(HttpStatus.OK).json({ msg: 'No post to show!' }) + return res.status(HttpStatus.OK).json({ msg: "No post to show!" }); } // check for pinned post const pinnedPost = posts.filter((post) => { - return post.isPinned === true - }) + return post.isPinned === true; + }); if (pinnedPost.length === 0) { - return res.status(HttpStatus.OK).json({ msg: 'No post pinned yet!' }) + return res.status(HttpStatus.OK).json({ msg: "No post pinned yet!" }); } // else return pinned posts - return res.status(HttpStatus.OK).json({ pinnedPost }) + return res.status(HttpStatus.OK).json({ pinnedPost }); } catch (error) { - HANDLER.handleError(res, error) + HANDLER.handleError(res, error); } - } -} + }, +}; diff --git a/app/controllers/proposal.js b/app/controllers/proposal.js index 5f4b21d..5d9d4b4 100644 --- a/app/controllers/proposal.js +++ b/app/controllers/proposal.js @@ -3,14 +3,43 @@ const UserModal = require("../models/User"); const HANDLER = require("../utils/response-helper"); const HttpStatus = require("http-status-codes"); const AWS = require("aws-sdk"); +const TAGS = require("../utils/notificationTags"); +const proposalNotificationHelper = require("../utils/proposal-notif-helper"); + +const notification = { + heading: "", + content: "", + tag: "", +}; module.exports = { // Creating a proposal createProposal: async (req, res, next) => { const proposal = new ProposalModel(req.body); + const creator = req.body.creator; try { await proposal.save(); + + const user = await UserModal.findById(creator); + const name = `${user.name.firstName} ${user.name.lastName}`; + + req.io.emit("new proposal created", { + heading: "New Proposal Created", + content: `New Proposal ${proposal.title} created by ${name}`, + tag: TAGS.NEW, + }); + proposalNotificationHelper.addNotificationForAll( + req, + res, + { + heading: "New Proposal Created", + content: `New Proposal ${proposal.title} created by ${name}`, + tag: TAGS.NEW, + }, + next + ); + res.status(HttpStatus.CREATED).json({ proposal }); } catch (error) { HANDLER.handleError(res, error); @@ -69,11 +98,18 @@ module.exports = { s3_key: params.Key, }; - console.log(proposalId); - ProposalModel.updateOne( + ProposalModel.findOneAndUpdate( { _id: proposalId }, - { $push: { attachments: newFileUploaded } } + { $push: { attachments: newFileUploaded } }, + function (error, success) { + if (error) { + console.log(error); + } else { + console.log(success); + } + } ); + res.send({ data }); } }); @@ -103,6 +139,27 @@ module.exports = { const proposalId = req.body.proposalId; const result = await ProposalModel.findByIdAndDelete(proposalId); + const creator = result.creator; + + const user = await UserModal.findById(creator); + const name = `${user.name.firstName} ${user.name.lastName}`; + + proposalNotificationHelper.addNotificationForAll( + req, + res, + { + heading: "Proposal Deleted", + content: `Proposal: "${result.title}" deleted by ${name}`, + tag: TAGS.NEW, + }, + next + ); + req.io.emit("proposal deleted", { + heading: "Proposal Deleted", + content: `Proposal: "${result.title}" deleted by ${name}`, + tag: TAGS.NEW, + }); + return res.status(HttpStatus.OK).json({ result: result }); } catch (error) { HANDLER.handleError(res, error); @@ -169,7 +226,7 @@ module.exports = { }, commentOnProposal: async (req, res, next) => { - const { proposalId, comment, userId } = req.body; + const { proposalId, comment, userId, isAuthor, author } = req.body; try { const user = await UserModal.findById(userId); @@ -185,15 +242,48 @@ module.exports = { { $push: { comments: { userName: name, comment: comment } } } ); + const updatedProposal = await ProposalModel.findById(proposalId); + if (!proposal) { return res .status(HttpStatus.BAD_REQUEST) .json({ message: "Proposal could not be found!" }); } + if (!isAuthor) { + proposalNotificationHelper.addToNotificationForUser( + author, + res, + { + heading: "New comment", + content: `New comments in your proposal "${updatedProposal.title}" by ${name}`, + tag: TAGS.COMMENT, + }, + next + ); + } return res.status(HttpStatus.OK).json({ proposal: proposal }); } catch (error) { HANDLER.handleError(res, error); } }, + + getProposalNotificationsByUser: async (req, res, next) => { + const userId = req.body.userId; + + try { + const user = await UserModal.findById(userId); + if (!user) { + return res + .status(HttpStatus.BAD_REQUEST) + .json({ message: "No user exists" }); + } + + return res + .status(HttpStatus.OK) + .json({ notifications: user.proposalNotifications }); + } catch (error) { + HANDLER.handleError(res, error); + } + }, }; diff --git a/app/models/ProposalNotification.js b/app/models/ProposalNotification.js new file mode 100644 index 0000000..bdd47a4 --- /dev/null +++ b/app/models/ProposalNotification.js @@ -0,0 +1,22 @@ +const mongoose = require("mongoose"); +const Schema = mongoose.Schema; + +const ProposalNotification = new Schema({ + heading: { + type: String, + }, + proposal: { + type: String, + }, + content: { + type: String, + }, + tag: { + type: String, + }, + createdAt: { + type: Date, + default: new Date().toISOString().substring(0, 25), + }, +}); +module.exports = mongoose.model("ProposalNotification", ProposalNotification); diff --git a/app/models/User.js b/app/models/User.js index 5010d1b..8f3e27b 100644 --- a/app/models/User.js +++ b/app/models/User.js @@ -1,9 +1,9 @@ -const mongoose = require('mongoose') -const bcrypt = require('bcrypt') -const validator = require('validator') -const jwt = require('jsonwebtoken') +const mongoose = require("mongoose"); +const bcrypt = require("bcrypt"); +const validator = require("validator"); +const jwt = require("jsonwebtoken"); -const saltRounds = 8 +const saltRounds = 8; // user schema const UserSchema = new mongoose.Schema({ @@ -12,21 +12,21 @@ const UserSchema = new mongoose.Schema({ type: String, trim: true, required: true, - validate (firstName) { + validate(firstName) { if (validator.isEmpty(firstName)) { - throw new Error('First name field can not be empty!') + throw new Error("First name field can not be empty!"); } - } + }, }, lastName: { type: String, trim: true, - validate (lastName) { + validate(lastName) { if (validator.isEmpty(lastName)) { - throw new Error('Last name field can not be empty!') + throw new Error("Last name field can not be empty!"); } - } - } + }, + }, }, email: { type: String, @@ -34,82 +34,82 @@ const UserSchema = new mongoose.Schema({ required: true, unique: true, lowercase: true, - validate (email) { + validate(email) { if (!validator.isEmail(email)) { - throw new Error('Invalid emailId') + throw new Error("Invalid emailId"); } if (validator.isEmpty(email)) { - throw new Error('Email is required!') + throw new Error("Email is required!"); } - } + }, }, phone: { type: String, trim: true, minlength: 10, - validate (phone) { + validate(phone) { if (!validator.isLength(phone, { min: 10, max: 10 })) { - throw new Error('Phone number is invalid!') + throw new Error("Phone number is invalid!"); } - } + }, }, password: { type: String, trim: true, required: true, minlength: 6, - validate (password) { + validate(password) { if (!validator.isLength(password, { min: 6 })) { - throw new Error('Password should be min 6 characters long!') + throw new Error("Password should be min 6 characters long!"); } if (validator.isEmpty(password)) { - throw new Error('Password is required!') + throw new Error("Password is required!"); } - } + }, }, socialMedia: { youtube: { - type: String + type: String, }, facebook: { - type: String + type: String, }, twitter: { - type: String + type: String, }, instagram: { - type: String + type: String, }, linkedin: { - type: String - } + type: String, + }, }, info: { about: { shortDescription: { type: String, required: true, - validate (shortDescription) { + validate(shortDescription) { if (validator.isEmpty(shortDescription)) { - throw new Error('Short description is required') + throw new Error("Short description is required"); } - } + }, }, longDescription: { - type: String + type: String, }, website: { type: String, trim: true, - validate (website) { + validate(website) { if (!validator.isURL(website)) { - throw new Error('Invalid website link!') + throw new Error("Invalid website link!"); } - } + }, }, designation: { type: String, - trim: true + trim: true, }, education: [ { @@ -117,126 +117,158 @@ const UserSchema = new mongoose.Schema({ school: { schoolName: { type: String, - trim: true + trim: true, }, year: { - type: String - } - } - } + type: String, + }, + }, + }, ], skills: [ { - type: String - } + type: String, + }, ], location: { type: String, - trim: true - } - } + trim: true, + }, + }, }, - notifications: [{ - heading: { - type: String + notifications: [ + { + heading: { + type: String, + }, + content: { + type: String, + }, + tag: { + type: String, + }, }, - content: { - type: String + ], + proposalNotifications: [ + { + heading: { + type: String, + }, + content: { + type: String, + }, + tag: { + type: String, + }, + createdAt: { + type: Date, + required: true, + default: Date.now(), + }, }, - tag: { - type: String - } - }], - followers: [{ - type: mongoose.Schema.Types.ObjectId, - ref: 'User' - }], - followings: [{ - type: mongoose.Schema.Types.ObjectId, - ref: 'User' - }], - blocked: [{ - type: mongoose.Schema.Types.ObjectId, - ref: 'User' - }], + ], + followers: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + }, + ], + followings: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + }, + ], + blocked: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + }, + ], pinned: { _id: false, - postId: [{ - type: mongoose.Schema.Types.ObjectId, - ref: 'Post' - }] + postId: [ + { + type: mongoose.Schema.Types.ObjectId, + ref: "Post", + }, + ], }, isAdmin: { type: Boolean, - default: false + default: false, }, isActivated: { type: Boolean, - default: false + default: false, }, isRemoved: { type: Boolean, - default: false + default: false, }, createdAt: { type: Date, required: true, - default: Date.now() + default: Date.now(), }, updatedAt: { type: Date, required: true, - default: Date.now() + default: Date.now(), }, - tokens: [{ - token: { - type: String, - required: true - } - }] -} -) + tokens: [ + { + token: { + type: String, + required: true, + }, + }, + ], +}); // generate auth token // Schema Methods, needs to be invoked by an instance of a Mongoose document UserSchema.methods.generateAuthToken = async function () { - const user = this - const token = jwt.sign({ _id: user._id.toString() }, 'process.env.JWT_SECRET') + const user = this; + const token = jwt.sign( + { _id: user._id.toString() }, + "process.env.JWT_SECRET" + ); - user.tokens = user.tokens.concat({ token: token }) - await user.save() + user.tokens = user.tokens.concat({ token: token }); + await user.save(); - return token -} + return token; +}; // Schema Statics are methods that can be invoked directly by a Model UserSchema.statics.findByCredentials = async (email, password) => { const user = await User.findOne({ - email: email - }) + email: email, + }); if (!user) { - throw new Error('No such user') + throw new Error("No such user"); } else { - const isMatch = await bcrypt.compare(password, user.password) + const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) { - throw new Error('Incorrect password provided') + throw new Error("Incorrect password provided"); } else { - return user + return user; } } -} +}; // hash user password before saving into database -UserSchema.pre('save', async function (next) { - const user = this +UserSchema.pre("save", async function (next) { + const user = this; - if (user.isModified('password')) { - user.password = await bcrypt.hash(user.password, saltRounds) + if (user.isModified("password")) { + user.password = await bcrypt.hash(user.password, saltRounds); } - next() -}) + next(); +}); -const User = mongoose.model('User', UserSchema) -module.exports = User +const User = mongoose.model("User", UserSchema); +module.exports = User; diff --git a/app/routes/notification.js b/app/routes/notification.js index 4a4e035..ff9a2eb 100644 --- a/app/routes/notification.js +++ b/app/routes/notification.js @@ -1,23 +1,30 @@ -const express = require('express') -const router = express.Router() -const auth = require('../middleware/auth') -const isUnderMaintenance = require('../middleware/maintenance') -const notificationController = require('../controllers/notification') +const express = require("express"); +const router = express.Router(); +const auth = require("../middleware/auth"); +const isUnderMaintenance = require("../middleware/maintenance"); +const notificationController = require("../controllers/notification"); // GET NOTIFICATIONS FOR ALL router.get( - '/org/all', + "/org/all", isUnderMaintenance, auth, notificationController.getOrgNotifications -) +); // GET NOTIFICATIONS FOR LOGGED IN USER router.get( - '/user/all', + "/user/all", isUnderMaintenance, auth, notificationController.getUserNotification -) +); -module.exports = router +// GET NOTICATIONS FOR PROPOSALS +router.get( + "/proposal/all", + auth, + notificationController.getProposalNotifications +); + +module.exports = router; diff --git a/app/routes/proposal.js b/app/routes/proposal.js index e9011e3..4269856 100644 --- a/app/routes/proposal.js +++ b/app/routes/proposal.js @@ -30,4 +30,10 @@ router.post("/all", auth, proposalController.getAllProposals); // Comment on the given proposal router.post("/comment", auth, proposalController.commentOnProposal); +router.post( + "/notifications", + auth, + proposalController.getProposalNotificationsByUser +); + module.exports = router; diff --git a/app/utils/notificationTags.js b/app/utils/notificationTags.js index 0256732..1b233e7 100644 --- a/app/utils/notificationTags.js +++ b/app/utils/notificationTags.js @@ -1,10 +1,11 @@ const tags = { - RSVP: 'RSVP', - UPDATE: 'Update', - DELETE: 'Delete', - NEW: 'New!', - MAINTENANCE: 'Maintenance', - FOLLOWER: 'Follower', - ACTIVATE: 'Activate' -} -module.exports = tags + RSVP: "RSVP", + UPDATE: "Update", + DELETE: "Delete", + NEW: "New!", + MAINTENANCE: "Maintenance", + FOLLOWER: "Follower", + ACTIVATE: "Activate", + COMMENT: "Comment", +}; +module.exports = tags; diff --git a/app/utils/proposal-notif-helper.js b/app/utils/proposal-notif-helper.js new file mode 100644 index 0000000..920f338 --- /dev/null +++ b/app/utils/proposal-notif-helper.js @@ -0,0 +1,26 @@ +const User = require("../models/User"); +const ProposalNotification = require("../models/ProposalNotification"); + +module.exports = { + // Notifications for a user + addToNotificationForUser: async (userId, res, obj, next) => { + try { + console.log("adding to user's notifications"); + const user = await User.findById(userId); + user.proposalNotifications.unshift(obj); + await user.save(); + } catch (error) { + console.log(error); + } + }, + // Notifications for all + addNotificationForAll: async (req, res, obj, next) => { + const newNotification = new ProposalNotification(obj); + + try { + await newNotification.save(); + } catch (error) { + console.log(error); + } + }, +}; diff --git a/package-lock.json b/package-lock.json index cdcdbe0..fd1b330 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1001,22 +1001,20 @@ } } }, -<<<<<<< HEAD - "base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" -======= "base64-arraybuffer": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz", "integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg=" }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, "base64id": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" ->>>>>>> cf9a8ab8cc6eb2b29aeb7f4caf6de92494275952 }, "basic-auth": { "version": "2.0.1", @@ -8310,7 +8308,6 @@ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", "dev": true }, -<<<<<<< HEAD "xml2js": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", @@ -8324,12 +8321,11 @@ "version": "9.0.7", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" -======= + }, "xmlhttprequest-ssl": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=" ->>>>>>> cf9a8ab8cc6eb2b29aeb7f4caf6de92494275952 }, "xtend": { "version": "4.0.2", From d65f73318664657874ff21cf90d0b09c9d2a9be0 Mon Sep 17 00:00:00 2001 From: Rupeshiya Date: Mon, 29 Jun 2020 01:33:29 +0530 Subject: [PATCH 32/42] modified backend --- app/controllers/event.js | 4 ++-- app/controllers/post.js | 14 ++++---------- app/controllers/user.js | 10 ++++++++-- app/models/Event.js | 4 +++- app/models/Project.js | 12 +----------- app/models/User.js | 5 ++++- app/routes/user.js | 6 +++--- 7 files changed, 25 insertions(+), 30 deletions(-) diff --git a/app/controllers/event.js b/app/controllers/event.js index cf87ada..b8898cb 100644 --- a/app/controllers/event.js +++ b/app/controllers/event.js @@ -123,9 +123,9 @@ module.exports = { if (!EventData) { return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such Event is available!' }) } - res.status(HttpStatus.OK).json({ Event: EventData }) + return res.status(HttpStatus.OK).json({ event: EventData }) } catch (error) { - next(error) + HANDLER.handleError(res, error) } }, diff --git a/app/controllers/post.js b/app/controllers/post.js index cacece8..2f8947d 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -100,9 +100,6 @@ module.exports = { .populate('userId', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) .sort({ updatedAt: -1 }) .exec() - if (!posts.length) { - return res.status(HttpStatus.NOT_FOUND).json({ message: 'No posts found' }) - } return res.status(HttpStatus.OK).json({ posts: posts }) } catch (error) { HANDLER.handleError(res, error) @@ -177,17 +174,14 @@ module.exports = { // GET ALL PINNED POST getPinned: async (req, res, next) => { try { - const posts = await PostModel.find({}) - if (posts.length === 0) { - return res.status(HttpStatus.OK).json({ msg: 'No post to show!' }) - } + const posts = await PostModel.find({}, {}, helper.paginate(req)) + .populate('userId', ['name.firstName', 'name.lastName', 'email', 'isAdmin']) + .sort({ updatedAt: -1 }) + .exec() // check for pinned post const pinnedPost = posts.filter((post) => { return post.isPinned === true }) - if (pinnedPost.length === 0) { - return res.status(HttpStatus.OK).json({ msg: 'No post pinned yet!' }) - } // else return pinned posts return res.status(HttpStatus.OK).json({ pinnedPost }) } catch (error) { diff --git a/app/controllers/user.js b/app/controllers/user.js index b374fb2..622d812 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -19,6 +19,12 @@ module.exports = { createUser: async (req, res, next) => { const user = new User(req.body) try { + const isRegisteredUserExists = await User.findOne({ firstRegister: true }) + // for the first user who will be setting up the platform for their community + if (!isRegisteredUserExists) { + user.isAdmin = true + user.firstRegister = true + } await user.save() const token = await user.generateAuthToken() // Added fn to send email to activate account with warm message @@ -77,7 +83,7 @@ module.exports = { try { const user = await User.findOne({ email: email }) if (!user) { - res.status(HttpStatus.NOT_FOUND).json({ msg: 'User not found!' }) + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'User not found!' }) } const token = jwt.sign({ _id: user._id, expiry: Date.now() + 10800000 }, process.env.JWT_SECRET) await user.save() @@ -86,7 +92,7 @@ module.exports = { if (process.env.NODE_ENV !== 'production' && error) { console.log('Error in forgotPasswordRequest ', error) } - res.status(HttpStatus.BAD_REQUEST).json({ error }) + return res.status(HttpStatus.BAD_REQUEST).json({ error }) } }, diff --git a/app/models/Event.js b/app/models/Event.js index 6b45638..782bf5c 100644 --- a/app/models/Event.js +++ b/app/models/Event.js @@ -47,7 +47,9 @@ const eventSchema = new Schema({ } } }, - + eventTime: { + type: String + }, rsvpYes: [{ type: Schema.Types.ObjectId, ref: 'User' diff --git a/app/models/Project.js b/app/models/Project.js index 35223c7..7d5ada8 100644 --- a/app/models/Project.js +++ b/app/models/Project.js @@ -48,24 +48,14 @@ const projectSchema = new Schema({ }, version: { type: String, - trim: true, - required: true, - validate (version) { - if (validator.isEmpty(version)) { - throw new Error('Short description for the project is required!') - } - } + trim: true }, links: [ { githubLink: { type: String, - required: true, trim: true, validate (githubLink) { - if (validator.isEmpty(githubLink)) { - throw new Error('Project github link is required!') - } if (!validator.isURL(githubLink)) { throw new Error('Invalid project url!') } diff --git a/app/models/User.js b/app/models/User.js index 5010d1b..579abd9 100644 --- a/app/models/User.js +++ b/app/models/User.js @@ -88,7 +88,6 @@ const UserSchema = new mongoose.Schema({ about: { shortDescription: { type: String, - required: true, validate (shortDescription) { if (validator.isEmpty(shortDescription)) { throw new Error('Short description is required') @@ -166,6 +165,10 @@ const UserSchema = new mongoose.Schema({ ref: 'Post' }] }, + firstRegister: { + type: Boolean, + default: false + }, isAdmin: { type: Boolean, default: false diff --git a/app/routes/user.js b/app/routes/user.js index 03de47f..0c9be6e 100644 --- a/app/routes/user.js +++ b/app/routes/user.js @@ -30,14 +30,14 @@ router.patch( ) // user forgot password request -router.post( - '/password_reset', +router.patch( + '/password_reset/request', isUnderMaintenance, userController.forgotPasswordRequest ) // update password -router.post( +router.patch( '/password_reset/:token', isUnderMaintenance, userController.updatePassword From 8d1c1e3691407db062bc63bd1dcaad7361fbcf7e Mon Sep 17 00:00:00 2001 From: Asel Date: Fri, 3 Jul 2020 17:13:42 +0530 Subject: [PATCH 33/42] Requested changes --- .env.dev | 8 +- app.js | 130 ++++++------ app/controllers/notification.js | 44 ++-- app/controllers/organization.js | 312 ++++++++++++++--------------- app/controllers/post.js | 196 +++++++++--------- app/controllers/proposal.js | 235 +++++++++++----------- app/middleware/auth.js | 25 ++- app/models/Proposal.js | 25 +-- app/models/ProposalNotification.js | 20 +- app/models/User.js | 198 +++++++++--------- app/routes/notification.js | 26 +-- app/routes/proposal.js | 34 ++-- app/utils/notificationTags.js | 20 +- app/utils/proposal-notif-helper.js | 24 +-- config/fileHandlingConstants.js | 6 +- config/mongoose.js | 10 +- package.json | 1 + test/proposal.test.js | 3 + 18 files changed, 667 insertions(+), 650 deletions(-) diff --git a/.env.dev b/.env.dev index 1f302c1..86ff9cb 100644 --- a/.env.dev +++ b/.env.dev @@ -1,10 +1,6 @@ PORT=5000 NODE_ENV="development" JWT_SECRET="thisismysupersecrettokenjustkidding" -DATABASE_URL="mongodb+srv://asel123:asel123@socialmediaappcluster-leoir.mongodb.net/example" +DATABASE_URL="mongodb://localhost:27017/donut-development" SENDGRID_API_KEY = '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' -AWS_BUCKET_NAME=donutproposals -AWS_ACCESS_KEY_ID=AKIASECEF4XZS5OLND4S -AWS_SECRET_ACCESS_KEY=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 -AWS_REGION=ap-south-1 -AWS_UPLOADED_FILE_URL_LINK=https://donutproposals.s3.ap-south-1.amazonaws.com/ +SOCKET_PORT = 8810 diff --git a/app.js b/app.js index f2b7662..a743490 100644 --- a/app.js +++ b/app.js @@ -1,88 +1,88 @@ -require("./config/mongoose"); -const express = require("express"); -const logger = require("morgan"); -const cookieParser = require("cookie-parser"); -const createError = require("http-errors"); -const path = require("path"); -const socket = require("socket.io"); -const multer = require("multer"); -const bodyParser = require("body-parser"); -const cors = require("cors"); -const fileConstants = require("./config/fileHandlingConstants"); +require('./config/mongoose') +const express = require('express') +const logger = require('morgan') +const cookieParser = require('cookie-parser') +const createError = require('http-errors') +const path = require('path') +const socket = require('socket.io') +const multer = require('multer') +const bodyParser = require('body-parser') +const cors = require('cors') +const fileConstants = require('./config/fileHandlingConstants') -const indexRouter = require("./app/routes/index"); -const authRouter = require("./app/routes/auth"); -const usersRouter = require("./app/routes/user"); -const postRouter = require("./app/routes/post"); -const eventRouter = require("./app/routes/event"); -const shortUrlRouter = require("./app/routes/urlShortner"); -const organizationRouter = require("./app/routes/organisation"); -const commentRouter = require("./app/routes/comment"); -const projectRouter = require("./app/routes/project"); -const notificationRouter = require("./app/routes/notification"); -const proposalRouter = require("./app/routes/proposal"); +const indexRouter = require('./app/routes/index') +const authRouter = require('./app/routes/auth') +const usersRouter = require('./app/routes/user') +const postRouter = require('./app/routes/post') +const eventRouter = require('./app/routes/event') +const shortUrlRouter = require('./app/routes/urlShortner') +const organizationRouter = require('./app/routes/organisation') +const commentRouter = require('./app/routes/comment') +const projectRouter = require('./app/routes/project') +const notificationRouter = require('./app/routes/notification') +const proposalRouter = require('./app/routes/proposal') -const app = express(); -const server = require("http").Server(app); +const app = express() +const server = require('http').Server(app) -app.use(cors()); +app.use(cors()) -app.use(bodyParser.json({ limit: "200mb" })); -app.use(bodyParser.urlencoded(fileConstants.fileParameters)); +app.use(bodyParser.json({ limit: '200mb' })) +app.use(bodyParser.urlencoded(fileConstants.fileParameters)) -const memoryStorage = multer.memoryStorage(); -app.use(multer({ storage: memoryStorage }).single("file")); +const memoryStorage = multer.memoryStorage() +app.use(multer({ storage: memoryStorage }).single('file')) -server.listen(process.env.SOCKET_PORT || 8810); +server.listen(process.env.SOCKET_PORT || 8810) // WARNING: app.listen(80) will NOT work here! -const io = socket.listen(server); -let count = 0; -io.on("connection", (socket) => { - console.log("socket connected count ", count++); - io.emit("user connected"); -}); +const io = socket.listen(server) +let count = 0 +io.on('connection', (socket) => { + console.log('socket connected count ', count++) + io.emit('user connected') +}) // view engine setup -app.set("views", path.join(__dirname, "views")); -app.set("view engine", "ejs"); +app.set('views', path.join(__dirname, 'views')) +app.set('view engine', 'ejs') -app.use(logger("tiny")); -app.use(express.json()); -app.use(express.urlencoded({ extended: false })); -app.use(cookieParser()); -app.use(express.static(path.join(__dirname, "public"))); +app.use(logger('tiny')) +app.use(express.json()) +app.use(express.urlencoded({ extended: false })) +app.use(cookieParser()) +app.use(express.static(path.join(__dirname, 'public'))) app.use((req, res, next) => { - req.io = io; - next(); -}); + req.io = io + next() +}) -app.use("/notification", notificationRouter); -app.use("/", indexRouter); -app.use("/auth", authRouter); -app.use("/user", usersRouter); -app.use("/post", postRouter); -app.use("/org", organizationRouter); -app.use("/event", eventRouter); -app.use("/shortUrl", shortUrlRouter); -app.use("/comment", commentRouter); -app.use("/project", projectRouter); -app.use("/proposal", proposalRouter); +app.use('/notification', notificationRouter) +app.use('/', indexRouter) +app.use('/auth', authRouter) +app.use('/user', usersRouter) +app.use('/post', postRouter) +app.use('/org', organizationRouter) +app.use('/event', eventRouter) +app.use('/shortUrl', shortUrlRouter) +app.use('/comment', commentRouter) +app.use('/project', projectRouter) +app.use('/proposal', proposalRouter) // catch 404 and forward to error handler app.use(function (req, res, next) { - next(createError(404, "route doesn't exist")); -}); + next(createError(404, "route doesn't exist")) +}) // error handler app.use(function (err, req, res, next) { // set locals, only providing error in development - res.locals.message = err.message; - res.locals.error = req.app.get("env") === "development" ? err : {}; + res.locals.message = err.message + res.locals.error = req.app.get('env') === 'development' ? err : {} // render the error page - res.status(err.status || 500); - res.render("error"); -}); + res.status(err.status || 500) + res.render('error') +}) -module.exports = { app, io }; +module.exports = { app, io } diff --git a/app/controllers/notification.js b/app/controllers/notification.js index 440b940..96147b4 100644 --- a/app/controllers/notification.js +++ b/app/controllers/notification.js @@ -1,9 +1,9 @@ -const HANDLER = require("../utils/response-helper"); -const HttpStatus = require("http-status-codes"); -const Notifications = require("../models/Notifications"); -const helper = require("../utils/paginate"); -const User = require("../models/User"); -const ProposalNotifications = require("../models/ProposalNotification"); +const HANDLER = require('../utils/response-helper') +const HttpStatus = require('http-status-codes') +const Notifications = require('../models/Notifications') +const helper = require('../utils/paginate') +const User = require('../models/User') +const ProposalNotifications = require('../models/ProposalNotification') module.exports = { // GET ALL THE NOTIFICATIONS FOR ALL @@ -16,40 +16,40 @@ module.exports = { ) .lean() .sort({ createdAt: -1 }) - .exec(); - return res.status(HttpStatus.OK).json({ notifications }); + .exec() + return res.status(HttpStatus.OK).json({ notifications }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // GET LOGGED IN USER NOTIFICATIONS getUserNotification: async (req, res, next) => { - const userId = req.user._id; + const userId = req.user._id try { - const user = await User.findById(userId); + const user = await User.findById(userId) if (!user) { return res .status(HttpStatus.BAD_REQUEST) - .json({ msg: "No such user exists!" }); + .json({ msg: 'No such user exists!' }) } // get all notifications of existing user - const notifications = user.notifications; + const notifications = user.notifications if (notifications.length === 0) { - return res.status(HttpStatus.OK).json({ msg: "No new notifications!" }); + return res.status(HttpStatus.OK).json({ msg: 'No new notifications!' }) } - return res.status(HttpStatus.OK).json({ notifications }); + return res.status(HttpStatus.OK).json({ notifications }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, getProposalNotifications: async (req, res, next) => { try { - const notifications = await ProposalNotifications.find({}); - console.log(notifications); - return res.status(HttpStatus.OK).json({ notifications }); + const notifications = await ProposalNotifications.find({}) + console.log(notifications) + return res.status(HttpStatus.OK).json({ notifications }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } - }, -}; + } +} diff --git a/app/controllers/organization.js b/app/controllers/organization.js index 63fde5c..92627d8 100644 --- a/app/controllers/organization.js +++ b/app/controllers/organization.js @@ -1,337 +1,337 @@ -const Organization = require("../models/Organisation"); -const HANDLER = require("../utils/response-helper"); -const HttpStatus = require("http-status-codes"); -const helper = require("../utils/uploader"); -const notificationHelper = require("../utils/notif-helper"); -const User = require("../models/User"); -const Project = require("../models/Project"); -const Event = require("../models/Event"); -const permission = require("../utils/permission"); -const TAGS = require("../utils/notificationTags"); +const Organization = require('../models/Organisation') +const HANDLER = require('../utils/response-helper') +const HttpStatus = require('http-status-codes') +const helper = require('../utils/uploader') +const notificationHelper = require('../utils/notif-helper') +const User = require('../models/User') +const Project = require('../models/Project') +const Event = require('../models/Event') +const permission = require('../utils/permission') +const TAGS = require('../utils/notificationTags') const notification = { - heading: "", - content: "", - tag: "", -}; + heading: '', + content: '', + tag: '' +} module.exports = { createOrganization: async (req, res, next) => { - const org = new Organization(req.body); + const org = new Organization(req.body) if (req.file) { - helper.mapToDb(req, org); + helper.mapToDb(req, org) } try { - await org.save(); - req.io.emit("new org created", { data: org.name }); - notification.heading = "New org!"; - notification.content = `${org.name} is created!`; - notification.tag = TAGS.NEW; - notificationHelper.addToNotificationForAll(req, res, notification, next); - return res.status(HttpStatus.CREATED).json({ org }); + await org.save() + req.io.emit('new org created', { data: org.name }) + notification.heading = 'New org!' + notification.content = `${org.name} is created!` + notification.tag = TAGS.NEW + notificationHelper.addToNotificationForAll(req, res, notification, next) + return res.status(HttpStatus.CREATED).json({ org }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, updateOrgDetails: async (req, res, next) => { - const { id } = req.params; - const updates = Object.keys(req.body); + const { id } = req.params + const updates = Object.keys(req.body) const allowedUpdates = [ - "name", - "description", - "contactInfo", - "image", - "imgUrl", - "adminInfo", - "moderatorInfo", - ]; + 'name', + 'description', + 'contactInfo', + 'image', + 'imgUrl', + 'adminInfo', + 'moderatorInfo' + ] const isValidOperation = updates.every((update) => { - return allowedUpdates.includes(update); - }); + return allowedUpdates.includes(update) + }) if (!isValidOperation) { return res .status(HttpStatus.BAD_REQUEST) - .json({ error: "invalid update" }); + .json({ error: 'invalid update' }) } try { - const org = await Organization.findById(id); + const org = await Organization.findById(id) // check for permission (ONLY ADMINS CAN UPDATE) if (!permission.check(req, res)) { return res .status(HttpStatus.BAD_REQUEST) - .json({ msg: "You don't have the permission" }); + .json({ msg: "You don't have the permission" }) } updates.forEach((update) => { - org[update] = req.body[update]; - }); + org[update] = req.body[update] + }) if (req.file) { - helper.mapToDb(req, org); + helper.mapToDb(req, org) } - await org.save(); - res.status(HttpStatus.OK).json({ organization: org }); + await org.save() + res.status(HttpStatus.OK).json({ organization: org }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, getOrgDetailsById: async (req, res, next) => { - const { id } = req.params; + const { id } = req.params try { const orgData = await Organization.findById(id) - .populate("adminInfo", [ - "name.firstName", - "name.lastName", - "email", - "isAdmin", + .populate('adminInfo', [ + 'name.firstName', + 'name.lastName', + 'email', + 'isAdmin' ]) - .populate("moderatorInfo", [ - "name.firstName", - "name.lastName", - "email", - "isAdmin", + .populate('moderatorInfo', [ + 'name.firstName', + 'name.lastName', + 'email', + 'isAdmin' ]) .sort({ createdAt: -1 }) .lean() - .exec(); + .exec() if (!orgData) { return res .status(HttpStatus.NOT_FOUND) - .json({ error: "No such organization exists!" }); + .json({ error: 'No such organization exists!' }) } - res.status(HttpStatus.OK).json({ organization: orgData }); + res.status(HttpStatus.OK).json({ organization: orgData }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, deleteOrg: async (req, res, next) => { - const { id } = req.params; + const { id } = req.params try { - const org = await Organization.findByIdAndRemove(id); + const org = await Organization.findByIdAndRemove(id) if (!org) { return res .status(HttpStatus.NOT_FOUND) - .json({ error: "No such organization exists!" }); + .json({ error: 'No such organization exists!' }) } // check for permission if (!permission.check(req, res)) { return res .status(HttpStatus.BAD_REQUEST) - .json({ msg: "You don't have the permission!" }); + .json({ msg: "You don't have the permission!" }) } - req.io.emit("org deleted", { data: org.name }); - notification.heading = "Org deleted!"; - notification.content = `${org.name} is deleted!`; - notification.tag = TAGS.DELETE; - notificationHelper.addToNotificationForAll(req, res, notification, next); - return res.status(HttpStatus.OK).json({ organization: org }); + req.io.emit('org deleted', { data: org.name }) + notification.heading = 'Org deleted!' + notification.content = `${org.name} is deleted!` + notification.tag = TAGS.DELETE + notificationHelper.addToNotificationForAll(req, res, notification, next) + return res.status(HttpStatus.OK).json({ organization: org }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, archiveOrg: async (req, res, next) => { - const { id } = req.params; + const { id } = req.params try { - const org = await Organization.findById(id); + const org = await Organization.findById(id) if (!org) { return res .status(HttpStatus.NOT_FOUND) - .json({ error: "No such organization exists!" }); + .json({ error: 'No such organization exists!' }) } - org.isArchived = true; - await org.save(); - return res.status(HttpStatus.OK).json({ organization: org }); + org.isArchived = true + await org.save() + return res.status(HttpStatus.OK).json({ organization: org }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, triggerMaintenance: async (req, res, next) => { - const { id } = req.params; + const { id } = req.params try { - const organization = await Organization.findById(id); + const organization = await Organization.findById(id) // if org exists or not if (!organization) { return res .status(HttpStatus.NOT_FOUND) - .json({ error: "No such organization exists!" }); + .json({ error: 'No such organization exists!' }) } // if user is admin or not - const adminIds = organization.adminInfo.adminId; - const isAdmin = adminIds.indexOf(req.user.id) || req.user.isAdmin; + const adminIds = organization.adminInfo.adminId + const isAdmin = adminIds.indexOf(req.user.id) || req.user.isAdmin // user is admin then perform operation if (isAdmin !== -1 || req.user.isAdmin) { // toggle maintenance mode - organization.isMaintenance = !organization.isMaintenance; - await organization.save(); - notification.tag = TAGS.MAINTENANCE; + organization.isMaintenance = !organization.isMaintenance + await organization.save() + notification.tag = TAGS.MAINTENANCE if (organization.isMaintenance) { - req.io.emit("org under maintenance", { data: organization.name }); - notification.heading = "Maintenance mode on!"; - notification.content = `${organization.name} is kept under maintenance!`; + req.io.emit('org under maintenance', { data: organization.name }) + notification.heading = 'Maintenance mode on!' + notification.content = `${organization.name} is kept under maintenance!` notificationHelper.addToNotificationForAll( req, res, notification, next - ); + ) return res .status(HttpStatus.OK) - .json({ msg: "Organization is kept under the maintenance!!" }); + .json({ msg: 'Organization is kept under the maintenance!!' }) } - req.io.emit("org revoked maintenance", { data: organization.name }); - notification.heading = "Maintenance mode off!"; - notification.content = `${organization.name} is revoked from maintenance!`; + req.io.emit('org revoked maintenance', { data: organization.name }) + notification.heading = 'Maintenance mode off!' + notification.content = `${organization.name} is revoked from maintenance!` return res .status(HttpStatus.OK) - .json({ msg: "Organization is recovered from maintenance!!" }); + .json({ msg: 'Organization is recovered from maintenance!!' }) } else { return res .status(HttpStatus.BAD_REQUEST) - .json({ msg: "You don't have access to triggerMaintenance!" }); + .json({ msg: "You don't have access to triggerMaintenance!" }) } } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, updateSettings: async (req, res, next) => { - const { id } = req.params; + const { id } = req.params try { // check if org exists - const organization = await Organization.findById(id); + const organization = await Organization.findById(id) if (!organization) { return res .status(HttpStatus.NOT_FOUND) - .json({ msg: "No Organization found!" }); + .json({ msg: 'No Organization found!' }) } // check if user is admin or not - const adminIds = organization.adminInfo.adminId; - const isAdmin = adminIds.indexOf(req.user.id); - const updates = Object.keys(req.body); - console.log("req.body ", req.body); - console.log("isAdmin ", isAdmin); - const allowedUpdates = ["settings", "permissions", "authentication"]; + const adminIds = organization.adminInfo.adminId + const isAdmin = adminIds.indexOf(req.user.id) + const updates = Object.keys(req.body) + console.log('req.body ', req.body) + console.log('isAdmin ', isAdmin) + const allowedUpdates = ['settings', 'permissions', 'authentication'] // if admin then check if valid update if (isAdmin !== -1) { const isValidOperation = updates.every((update) => { - return allowedUpdates.includes(update); - }); + return allowedUpdates.includes(update) + }) // if valid update if (isValidOperation) { updates.forEach((update) => { - organization.options[update] = req.body[update]; - }); - await organization.save(); + organization.options[update] = req.body[update] + }) + await organization.save() return res .status(HttpStatus.OK) - .json({ msg: "Successfully updated!" }); + .json({ msg: 'Successfully updated!' }) } // invalid update return res .status(HttpStatus.BAD_REQUEST) - .json({ msg: "Invalid update" }); + .json({ msg: 'Invalid update' }) } // else not admin return res .status(HttpStatus.BAD_REQUEST) - .json({ msg: "You don't have access to perform this operation!" }); + .json({ msg: "You don't have access to perform this operation!" }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, getOrgOverView: async (req, res, next) => { - const orgOverView = {}; + const orgOverView = {} try { - const org = await Organization.find({}); + const org = await Organization.find({}) if (!org) { - return res.status(HttpStatus.NOT_FOUND).json({ msg: "No org exists!" }); + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No org exists!' }) } - orgOverView.admins = org[0].adminInfo.length; - orgOverView.members = await User.find({}).lean().count(); - orgOverView.projects = await Project.find({}).lean().count(); - orgOverView.events = await Event.find({}).lean().count(); - return res.status(HttpStatus.OK).json({ orgOverView }); + orgOverView.admins = org[0].adminInfo.length + orgOverView.members = await User.find({}).lean().count() + orgOverView.projects = await Project.find({}).lean().count() + orgOverView.events = await Event.find({}).lean().count() + return res.status(HttpStatus.OK).json({ orgOverView }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // SEARCH FUNCTIONALITY getMembers: async (req, res, next) => { try { - const { search } = req.query; + const { search } = req.query if (search) { - const queryTerm = search.split(" "); - const regex = new RegExp("^" + queryTerm + "$", "i"); + const queryTerm = search.split(' ') + const regex = new RegExp('^' + queryTerm + '$', 'i') const member = await User.find({ $or: [ - { "name.firstName": { $regex: regex } }, - { "name.lastName": { $regex: regex } }, - ], + { 'name.firstName': { $regex: regex } }, + { 'name.lastName': { $regex: regex } } + ] }) - .select("name email isAdmin info.about.designation isRemoved") + .select('name email isAdmin info.about.designation isRemoved') .lean() .sort({ createdAt: -1 }) - .exec(); + .exec() if (!member) { - return res.status(HttpStatus.OK).json({ msg: "Member not found!" }); + return res.status(HttpStatus.OK).json({ msg: 'Member not found!' }) } - return res.status(HttpStatus.OK).json({ member }); + return res.status(HttpStatus.OK).json({ member }) } else { const members = await User.find({}) - .select("name email isAdmin info.about.designation isRemoved") + .select('name email isAdmin info.about.designation isRemoved') .lean() .sort({ createdAt: -1 }) - .exec(); + .exec() if (members.length === 0) { return res .status(HttpStatus.OK) - .json({ msg: "No members joined yet!" }); + .json({ msg: 'No members joined yet!' }) } - return res.status(HttpStatus.OK).json({ members }); + return res.status(HttpStatus.OK).json({ members }) } } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // REMOVE ADMIN removeAdmin: async (req, res, next) => { try { - const { userId, orgId } = req.params; - const org = await Organization.findById(orgId); + const { userId, orgId } = req.params + const org = await Organization.findById(orgId) if (!org) { - return res.status(HttpStatus.NOT_FOUND).json({ msg: "No org exists!" }); + return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No org exists!' }) } // only permitted for admins if (!req.user.isAdmin) { return res .status(HttpStatus.BAD_REQUEST) - .json({ msg: "You are not permitted!" }); + .json({ msg: 'You are not permitted!' }) } // console.log('Permitted to removeAdmin') // REMOVE ADMINS FROM ADMINS LIST - const admins = org.adminInfo.adminId; - console.log("adminIds ", admins); - const removableIndex = admins.indexOf(userId); + const admins = org.adminInfo.adminId + console.log('adminIds ', admins) + const removableIndex = admins.indexOf(userId) if (removableIndex === -1) { return res .status(HttpStatus.BAD_REQUEST) - .json({ msg: "User is not an admin!" }); + .json({ msg: 'User is not an admin!' }) } // user is admin so remove - org.adminInfo.adminId.splice(removableIndex, 1); - await org.save(); + org.adminInfo.adminId.splice(removableIndex, 1) + await org.save() // also make isAdmin false - const user = await User.findById(userId); - user.isAdmin = false; - await user.save(); - return res.status(HttpStatus.OK).json({ org }); + const user = await User.findById(userId) + user.isAdmin = false + await user.save() + return res.status(HttpStatus.OK).json({ org }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } - }, -}; + } +} diff --git a/app/controllers/post.js b/app/controllers/post.js index 369f268..af60db9 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -1,112 +1,112 @@ -const PostModel = require("../models/Post"); -const UserModel = require("../models/User"); -const HANDLER = require("../utils/response-helper"); -const HttpStatus = require("http-status-codes"); -const imgUploadHelper = require("../utils/uploader"); -const permission = require("../utils/permission"); -const helper = require("../utils/paginate"); +const PostModel = require('../models/Post') +const UserModel = require('../models/User') +const HANDLER = require('../utils/response-helper') +const HttpStatus = require('http-status-codes') +const imgUploadHelper = require('../utils/uploader') +const permission = require('../utils/permission') +const helper = require('../utils/paginate') module.exports = { // CREATE POST create: async (req, res, next) => { - const post = new PostModel(req.body); - const userId = req.user.id.toString(); - post.userId = userId; + const post = new PostModel(req.body) + const userId = req.user.id.toString() + post.userId = userId if (req.file) { - imgUploadHelper.mapToDb(req, post); + imgUploadHelper.mapToDb(req, post) } try { - await post.save(); + await post.save() // req.io.emit('new post created', { data: post.content }) - return res.status(HttpStatus.CREATED).json({ post }); + return res.status(HttpStatus.CREATED).json({ post }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // DELETE POST delete: async (req, res, next) => { - const { id } = req.params; + const { id } = req.params try { - const post = await PostModel.findById(id); + const post = await PostModel.findById(id) if (!post) { return res .status(HttpStatus.NOT_FOUND) - .json({ message: "No post exists" }); + .json({ message: 'No post exists' }) } if (!permission.check(req, res, post.userId)) { return res .status(HttpStatus.BAD_REQUEST) - .json({ message: "Bad delete request" }); + .json({ message: 'Bad delete request' }) } - await PostModel.findByIdAndRemove(id); - res.status(HttpStatus.OK).json({ post: post, msg: "Deleted!" }); + await PostModel.findByIdAndRemove(id) + res.status(HttpStatus.OK).json({ post: post, msg: 'Deleted!' }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // UPDATE POST updatePost: async (req, res, next) => { - const { id } = req.params; - const updates = Object.keys(req.body); - const allowedUpdates = ["content", "imgUrl"]; + const { id } = req.params + const updates = Object.keys(req.body) + const allowedUpdates = ['content', 'imgUrl'] const isValidOperation = updates.every((update) => { - return allowedUpdates.includes(update); - }); + return allowedUpdates.includes(update) + }) if (!isValidOperation) { return res .status(HttpStatus.BAD_REQUEST) - .json({ message: "Invalid Update" }); + .json({ message: 'Invalid Update' }) } try { - const post = await PostModel.findById(id); + const post = await PostModel.findById(id) if (!post) { return res .status(HttpStatus.BAD_REQUEST) - .json({ message: "No post exists" }); + .json({ message: 'No post exists' }) } if (!permission.check(req, res, post.userId)) { return res .status(HttpStatus.FORBIDDEN) - .json({ message: "Bad update request" }); + .json({ message: 'Bad update request' }) } updates.forEach((update) => { - post[update] = req.body[update]; - }); + post[update] = req.body[update] + }) if (req.file) { - imgUploadHelper.mapToDb(req, post); + imgUploadHelper.mapToDb(req, post) } - await post.save(); - res.status(HttpStatus.OK).json({ post: post }); + await post.save() + res.status(HttpStatus.OK).json({ post: post }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // GET POST BY ID getPostById: async (req, res, next) => { - const { id } = req.params; + const { id } = req.params try { const post = await PostModel.findById(id) - .populate("comments", ["content", "votes"]) - .populate("userId", [ - "name.firstName", - "name.lastName", - "email", - "isAdmin", + .populate('comments', ['content', 'votes']) + .populate('userId', [ + 'name.firstName', + 'name.lastName', + 'email', + 'isAdmin' ]) .lean() - .exec(); + .exec() if (!post) { return res .status(HttpStatus.NOT_FOUND) - .json({ error: "Post not found" }); + .json({ error: 'Post not found' }) } - res.status(HttpStatus.OK).json({ post: post }); + res.status(HttpStatus.OK).json({ post: post }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, @@ -114,49 +114,49 @@ module.exports = { getAllPost: async (req, res, next) => { try { const posts = await PostModel.find({}, {}, helper.paginate(req)) - .populate("userId", [ - "name.firstName", - "name.lastName", - "email", - "isAdmin", + .populate('userId', [ + 'name.firstName', + 'name.lastName', + 'email', + 'isAdmin' ]) .sort({ updatedAt: -1 }) - .exec(); + .exec() if (!posts.length) { return res .status(HttpStatus.NOT_FOUND) - .json({ message: "No posts found" }); + .json({ message: 'No posts found' }) } - return res.status(HttpStatus.OK).json({ posts: posts }); + return res.status(HttpStatus.OK).json({ posts: posts }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // UPVOTE POST upvote: async (req, res, next) => { - const { id } = req.params; - const userId = req.user.id.toString(); + const { id } = req.params + const userId = req.user.id.toString() try { - const post = await PostModel.findById(id); + const post = await PostModel.findById(id) if (!post) { return res .status(HttpStatus.NOT_FOUND) - .json({ error: "No post found" }); + .json({ error: 'No post found' }) } // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT post.votes.upVotes.user.filter((user) => { if (JSON.stringify(user) === JSON.stringify(userId)) { return res .status(HttpStatus.BAD_REQUEST) - .json({ error: "Bad request" }); + .json({ error: 'Bad request' }) } - }); - post.votes.upVotes.user.unshift(userId); - await post.save(); - res.status(HttpStatus.OK).json({ post: post }); + }) + post.votes.upVotes.user.unshift(userId) + await post.save() + res.status(HttpStatus.OK).json({ post: post }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, @@ -167,70 +167,70 @@ module.exports = { {}, helper.paginate(req) ) - .populate("comments", ["content", "votes"]) - .populate("userId", [ - "name.firstName", - "name.lastName", - "_id", - "isAdmin", + .populate('comments', ['content', 'votes']) + .populate('userId', [ + 'name.firstName', + 'name.lastName', + '_id', + 'isAdmin' ]) .sort({ updatedAt: -1 }) - .exec(); - return res.status(HttpStatus.OK).json({ posts }); + .exec() + return res.status(HttpStatus.OK).json({ posts }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // PIN THE POST pinPost: async (req, res, next) => { - const { id } = req.params; + const { id } = req.params try { - const post = await PostModel.findById(id); - const user = await UserModel.findById(req.user._id); + const post = await PostModel.findById(id) + const user = await UserModel.findById(req.user._id) if (!post) { return res .status(HttpStatus.NOT_FOUND) - .json({ msg: "No such post exists!" }); + .json({ msg: 'No such post exists!' }) } // toggle pinned post - post.isPinned = !post.isPinned; + post.isPinned = !post.isPinned // if already pinned then remove from their pinned items - const PinnedItems = user.pinned.postId; + const PinnedItems = user.pinned.postId if (PinnedItems.length > 0) { - const pinnedPostIndex = PinnedItems.indexOf(id); - user.pinned.postId.splice(pinnedPostIndex, 1); - await user.save(); + const pinnedPostIndex = PinnedItems.indexOf(id) + user.pinned.postId.splice(pinnedPostIndex, 1) + await user.save() } else { // save to the user pinned items - user.pinned.postId.unshift(id); - await user.save(); + user.pinned.postId.unshift(id) + await user.save() } - await post.save(); - return res.status(HttpStatus.OK).json({ post }); + await post.save() + return res.status(HttpStatus.OK).json({ post }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // GET ALL PINNED POST getPinned: async (req, res, next) => { try { - const posts = await PostModel.find({}); + const posts = await PostModel.find({}) if (posts.length === 0) { - return res.status(HttpStatus.OK).json({ msg: "No post to show!" }); + return res.status(HttpStatus.OK).json({ msg: 'No post to show!' }) } // check for pinned post const pinnedPost = posts.filter((post) => { - return post.isPinned === true; - }); + return post.isPinned === true + }) if (pinnedPost.length === 0) { - return res.status(HttpStatus.OK).json({ msg: "No post pinned yet!" }); + return res.status(HttpStatus.OK).json({ msg: 'No post pinned yet!' }) } // else return pinned posts - return res.status(HttpStatus.OK).json({ pinnedPost }); + return res.status(HttpStatus.OK).json({ pinnedPost }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } - }, -}; + } +} diff --git a/app/controllers/proposal.js b/app/controllers/proposal.js index 5d9d4b4..d550cf1 100644 --- a/app/controllers/proposal.js +++ b/app/controllers/proposal.js @@ -1,289 +1,292 @@ -const ProposalModel = require("../models/Proposal"); -const UserModal = require("../models/User"); -const HANDLER = require("../utils/response-helper"); -const HttpStatus = require("http-status-codes"); -const AWS = require("aws-sdk"); -const TAGS = require("../utils/notificationTags"); -const proposalNotificationHelper = require("../utils/proposal-notif-helper"); - -const notification = { - heading: "", - content: "", - tag: "", -}; +const ProposalModel = require('../models/Proposal') +const UserModal = require('../models/User') +const HANDLER = require('../utils/response-helper') +const HttpStatus = require('http-status-codes') +const AWS = require('aws-sdk') +const TAGS = require('../utils/notificationTags') +const proposalNotificationHelper = require('../utils/proposal-notif-helper') module.exports = { // Creating a proposal createProposal: async (req, res, next) => { - const proposal = new ProposalModel(req.body); - const creator = req.body.creator; + const proposal = new ProposalModel(req.body) + const creator = req.body.creator try { - await proposal.save(); + await proposal.save() - const user = await UserModal.findById(creator); - const name = `${user.name.firstName} ${user.name.lastName}`; + const user = await UserModal.findById(creator) + const name = `${user.name.firstName} ${user.name.lastName}` - req.io.emit("new proposal created", { - heading: "New Proposal Created", + req.io.emit('new proposal created', { + heading: 'New Proposal Created', content: `New Proposal ${proposal.title} created by ${name}`, - tag: TAGS.NEW, - }); + tag: TAGS.NEW + }) proposalNotificationHelper.addNotificationForAll( req, res, { - heading: "New Proposal Created", + heading: 'New Proposal Created', content: `New Proposal ${proposal.title} created by ${name}`, - tag: TAGS.NEW, + tag: TAGS.NEW }, next - ); + ) - res.status(HttpStatus.CREATED).json({ proposal }); + res.status(HttpStatus.CREATED).json({ proposal }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // Updates the content of the proposal saveProposal: async (req, res, next) => { - const { proposalId } = req.params; - const content = req.body.content; - const title = req.body.title; - const description = req.body.description; + const { proposalId } = req.params + const { content, title, description } = req.body + try { const proposal = await ProposalModel.findByIdAndUpdate(proposalId, { content: content, title: title, - proposalDescription: description, - }); + proposalDescription: description + }) if (!proposal) { return res .status(HttpStatus.NOT_FOUND) - .json({ message: "No proposal exists under the provided ID" }); + .json({ message: 'No proposal exists under the provided ID' }) } - res.status(HttpStatus.OK).json({ proposal: proposal }); + res.status(HttpStatus.OK).json({ proposal: proposal }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // attaches a file to the given proposal attachFile: (req, res, next) => { - const { proposalId } = req.params; - const file = req.file; - const s3FileURL = process.env.AWS_UPLOADED_FILE_URL_LINK; + const { proposalId } = req.params + const file = req.file + const s3FileURL = process.env.AWS_UPLOADED_FILE_URL_LINK const s3bucket = new AWS.S3({ accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, - region: process.env.AWS_REGION, - }); + region: process.env.AWS_REGION + }) var params = { Bucket: process.env.AWS_BUCKET_NAME, Key: file.originalname, Body: file.buffer, ContentType: file.mimetype, - ACL: "public-read", - }; + ACL: 'public-read' + } s3bucket.upload(params, function (err, data) { if (err) { - res.status(500).json({ error: true, Message: err }); + res.status(500).json({ error: true, Message: err }) } else { var newFileUploaded = { fileLink: s3FileURL + file.originalname, - s3_key: params.Key, - }; + s3_key: params.Key + } ProposalModel.findOneAndUpdate( { _id: proposalId }, { $push: { attachments: newFileUploaded } }, function (error, success) { if (error) { - console.log(error); + console.log(error) } else { - console.log(success); + console.log(success) } } - ); + ) - res.send({ data }); + res.send({ data }) } - }); + }) }, // Get proposals by userId getByUserId: async (req, res, next) => { - const { userId } = req.params; + const { userId } = req.params try { - const proposals = await ProposalModel.find({ creator: userId }); + const proposals = await ProposalModel.find({ creator: userId }) if (!proposals) { return res .status(HttpStatus.NOT_FOUND) - .json({ message: "No proposals found for the given user ID" }); + .json({ message: 'No proposals found for the given user ID' }) } - return res.status(HttpStatus.OK).json({ proposal: proposals }); + return res.status(HttpStatus.OK).json({ proposal: proposals }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // Delete proposal by proposalId deleteById: async (req, res, next) => { try { - const proposalId = req.body.proposalId; + const proposalId = req.body.proposalId - const result = await ProposalModel.findByIdAndDelete(proposalId); - const creator = result.creator; + console.log(proposalId) + const result = await ProposalModel.findByIdAndDelete(proposalId) + const creator = result.creator - const user = await UserModal.findById(creator); - const name = `${user.name.firstName} ${user.name.lastName}`; + const user = await UserModal.findById(creator) + const name = `${user.name.firstName} ${user.name.lastName}` proposalNotificationHelper.addNotificationForAll( req, res, { - heading: "Proposal Deleted", + heading: 'Proposal Deleted', content: `Proposal: "${result.title}" deleted by ${name}`, - tag: TAGS.NEW, + tag: TAGS.NEW }, next - ); - req.io.emit("proposal deleted", { - heading: "Proposal Deleted", + ) + req.io.emit('proposal deleted', { + heading: 'Proposal Deleted', content: `Proposal: "${result.title}" deleted by ${name}`, - tag: TAGS.NEW, - }); + tag: TAGS.NEW + }) - return res.status(HttpStatus.OK).json({ result: result }); + return res.status(HttpStatus.OK).json({ result: result }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // Changes the state of a given proposal changeState: async (req, res, next) => { - const { proposalId } = req.params; - const proposalStatus = req.body.proposalStatus; + const { proposalId } = req.params + const proposalStatus = req.body.proposalStatus try { const proposal = await ProposalModel.findByIdAndUpdate(proposalId, { - proposalStatus: proposalStatus, - }); + proposalStatus: proposalStatus + }) if (!proposal) { return res .status(HttpStatus.NOT_FOUND) - .json({ message: "No proposal exists under the provided ID" }); + .json({ message: 'No proposal exists under the provided ID' }) } - res.status(HttpStatus.OK).json({ proposal: proposal }); + + req.io.emit('proposal submitted', { + heading: 'Proposal Submitted', + content: `Proposal ${proposal.title} was submitted for review`, + tag: TAGS.NEW + }) + proposalNotificationHelper.addNotificationForAll( + req, + res, + { + heading: 'Proposal Submitted', + content: `Proposal "${proposal.title}" was submitted for review`, + tag: TAGS.NEW + }, + next + ) + res.status(HttpStatus.OK).json({ proposal: proposal }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // Obtains the proposal by given proposal ID getProposalById: async (req, res, next) => { - const { proposalId } = req.params; + const { proposalId } = req.params try { - const proposal = await ProposalModel.findById(proposalId); + const proposal = await ProposalModel.findById(proposalId) if (!proposal) { return res .status(HttpStatus.NOT_FOUND) - .json({ error: "Proposal not found" }); + .json({ error: 'Proposal not found' }) } - return res.status(HttpStatus.OK).json({ proposal: proposal }); + return res.status(HttpStatus.OK).json({ proposal: proposal }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, getAllProposals: async (req, res, next) => { + console.log('All proposals called') try { - const user = await UserModal.findById(req.body.userId); - - if (user.isAdmin == true) { - const proposals = await ProposalModel.find({}); - - if (!proposals.length) { - return res - .status(HttpStatus.NOT_FOUND) - .json({ message: "No posts found" }); - } - return res.status(HttpStatus.OK).json({ proposals: proposals }); + const proposals = await ProposalModel.find({}) + if (!proposals.length) { + return res + .status(HttpStatus.NOT_FOUND) + .json({ message: 'No proposals found' }) } - return res - .status(HttpStatus.BAD_REQUEST) - .json({ msg: "You don't have permission for this request" }); + return res.status(HttpStatus.OK).json({ proposals: proposals }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, commentOnProposal: async (req, res, next) => { - const { proposalId, comment, userId, isAuthor, author } = req.body; + const { proposalId, comment, userId, isAuthor, author } = req.body try { - const user = await UserModal.findById(userId); + const user = await UserModal.findById(userId) if (!user) { return res .status(HttpStatus.BAD_REQUEST) - .json({ message: "No user exists" }); + .json({ message: 'No user exists' }) } - const name = `${user.name.firstName} ${user.name.lastName}`; + const name = `${user.name.firstName} ${user.name.lastName}` const proposal = await ProposalModel.updateOne( { _id: proposalId }, { $push: { comments: { userName: name, comment: comment } } } - ); + ) - const updatedProposal = await ProposalModel.findById(proposalId); + const updatedProposal = await ProposalModel.findById(proposalId) if (!proposal) { return res .status(HttpStatus.BAD_REQUEST) - .json({ message: "Proposal could not be found!" }); + .json({ message: 'Proposal could not be found!' }) } if (!isAuthor) { proposalNotificationHelper.addToNotificationForUser( author, res, { - heading: "New comment", + heading: 'New comment', content: `New comments in your proposal "${updatedProposal.title}" by ${name}`, - tag: TAGS.COMMENT, + tag: TAGS.COMMENT }, next - ); + ) } - return res.status(HttpStatus.OK).json({ proposal: proposal }); + return res.status(HttpStatus.OK).json({ proposal: proposal }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, getProposalNotificationsByUser: async (req, res, next) => { - const userId = req.body.userId; + const userId = req.body.userId try { - const user = await UserModal.findById(userId); + const user = await UserModal.findById(userId) if (!user) { return res .status(HttpStatus.BAD_REQUEST) - .json({ message: "No user exists" }); + .json({ message: 'No user exists' }) } return res .status(HttpStatus.OK) - .json({ notifications: user.proposalNotifications }); + .json({ notifications: user.proposalNotifications }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } - }, -}; + } +} diff --git a/app/middleware/auth.js b/app/middleware/auth.js index 9762089..67c266c 100644 --- a/app/middleware/auth.js +++ b/app/middleware/auth.js @@ -10,10 +10,29 @@ const auth = async (req, res, next) => { _id: decoded._id, 'tokens.token': token }) - .populate('followings', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) - .populate('followers', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) - .populate('blocked', ['name.firstName', 'name.lastName', 'info.about.designation', '_id', 'isAdmin']) + .populate('followings', [ + 'name.firstName', + 'name.lastName', + 'info.about.designation', + '_id', + 'isAdmin' + ]) + .populate('followers', [ + 'name.firstName', + 'name.lastName', + 'info.about.designation', + '_id', + 'isAdmin' + ]) + .populate('blocked', [ + 'name.firstName', + 'name.lastName', + 'info.about.designation', + '_id', + 'isAdmin' + ]) .exec() + console.log(user) if (!user) { throw new Error() diff --git a/app/models/Proposal.js b/app/models/Proposal.js index f1e412c..4a1f97b 100644 --- a/app/models/Proposal.js +++ b/app/models/Proposal.js @@ -1,42 +1,43 @@ -const mongoose = require("mongoose"); -const Schema = mongoose.Schema; -const validator = require("validator"); +const mongoose = require('mongoose') +const Schema = mongoose.Schema const proposalSchema = new Schema( { title: { type: String, + required: true }, content: { type: String, + required: true }, proposalStatus: { type: String, - default: "draft", + default: 'draft' }, creator: { type: Schema.Types.ObjectId, - ref: "User", - required: true, + ref: 'User', + required: true }, proposalDescription: { - type: String, + type: String }, attachments: [{ fileLink: String, s3_key: String }], createdAt: { type: Date, required: true, - default: Date.now(), + default: Date.now() }, updatedAt: { type: Date, required: true, - default: Date.now(), + default: Date.now() }, - comments: [{ userName: String, comment: String }], + comments: [{ userName: String, comment: String }] }, { timestamps: true } -); +) -module.exports = mongoose.model("Proposal", proposalSchema); +module.exports = mongoose.model('Proposal', proposalSchema) diff --git a/app/models/ProposalNotification.js b/app/models/ProposalNotification.js index bdd47a4..920b39f 100644 --- a/app/models/ProposalNotification.js +++ b/app/models/ProposalNotification.js @@ -1,22 +1,22 @@ -const mongoose = require("mongoose"); -const Schema = mongoose.Schema; +const mongoose = require('mongoose') +const Schema = mongoose.Schema const ProposalNotification = new Schema({ heading: { - type: String, + type: String }, proposal: { - type: String, + type: String }, content: { - type: String, + type: String }, tag: { - type: String, + type: String }, createdAt: { type: Date, - default: new Date().toISOString().substring(0, 25), - }, -}); -module.exports = mongoose.model("ProposalNotification", ProposalNotification); + default: new Date().toISOString().substring(0, 25) + } +}) +module.exports = mongoose.model('ProposalNotification', ProposalNotification) diff --git a/app/models/User.js b/app/models/User.js index 8f3e27b..545ae74 100644 --- a/app/models/User.js +++ b/app/models/User.js @@ -1,9 +1,9 @@ -const mongoose = require("mongoose"); -const bcrypt = require("bcrypt"); -const validator = require("validator"); -const jwt = require("jsonwebtoken"); +const mongoose = require('mongoose') +const bcrypt = require('bcrypt') +const validator = require('validator') +const jwt = require('jsonwebtoken') -const saltRounds = 8; +const saltRounds = 8 // user schema const UserSchema = new mongoose.Schema({ @@ -12,21 +12,21 @@ const UserSchema = new mongoose.Schema({ type: String, trim: true, required: true, - validate(firstName) { + validate (firstName) { if (validator.isEmpty(firstName)) { - throw new Error("First name field can not be empty!"); + throw new Error('First name field can not be empty!') } - }, + } }, lastName: { type: String, trim: true, - validate(lastName) { + validate (lastName) { if (validator.isEmpty(lastName)) { - throw new Error("Last name field can not be empty!"); + throw new Error('Last name field can not be empty!') } - }, - }, + } + } }, email: { type: String, @@ -34,82 +34,82 @@ const UserSchema = new mongoose.Schema({ required: true, unique: true, lowercase: true, - validate(email) { + validate (email) { if (!validator.isEmail(email)) { - throw new Error("Invalid emailId"); + throw new Error('Invalid emailId') } if (validator.isEmpty(email)) { - throw new Error("Email is required!"); + throw new Error('Email is required!') } - }, + } }, phone: { type: String, trim: true, minlength: 10, - validate(phone) { + validate (phone) { if (!validator.isLength(phone, { min: 10, max: 10 })) { - throw new Error("Phone number is invalid!"); + throw new Error('Phone number is invalid!') } - }, + } }, password: { type: String, trim: true, required: true, minlength: 6, - validate(password) { + validate (password) { if (!validator.isLength(password, { min: 6 })) { - throw new Error("Password should be min 6 characters long!"); + throw new Error('Password should be min 6 characters long!') } if (validator.isEmpty(password)) { - throw new Error("Password is required!"); + throw new Error('Password is required!') } - }, + } }, socialMedia: { youtube: { - type: String, + type: String }, facebook: { - type: String, + type: String }, twitter: { - type: String, + type: String }, instagram: { - type: String, + type: String }, linkedin: { - type: String, - }, + type: String + } }, info: { about: { shortDescription: { type: String, required: true, - validate(shortDescription) { + validate (shortDescription) { if (validator.isEmpty(shortDescription)) { - throw new Error("Short description is required"); + throw new Error('Short description is required') } - }, + } }, longDescription: { - type: String, + type: String }, website: { type: String, trim: true, - validate(website) { + validate (website) { if (!validator.isURL(website)) { - throw new Error("Invalid website link!"); + throw new Error('Invalid website link!') } - }, + } }, designation: { type: String, - trim: true, + trim: true }, education: [ { @@ -117,158 +117,158 @@ const UserSchema = new mongoose.Schema({ school: { schoolName: { type: String, - trim: true, + trim: true }, year: { - type: String, - }, - }, - }, + type: String + } + } + } ], skills: [ { - type: String, - }, + type: String + } ], location: { type: String, - trim: true, - }, - }, + trim: true + } + } }, notifications: [ { heading: { - type: String, + type: String }, content: { - type: String, + type: String }, tag: { - type: String, - }, - }, + type: String + } + } ], proposalNotifications: [ { heading: { - type: String, + type: String }, content: { - type: String, + type: String }, tag: { - type: String, + type: String }, createdAt: { type: Date, required: true, - default: Date.now(), - }, - }, + default: Date.now() + } + } ], followers: [ { type: mongoose.Schema.Types.ObjectId, - ref: "User", - }, + ref: 'User' + } ], followings: [ { type: mongoose.Schema.Types.ObjectId, - ref: "User", - }, + ref: 'User' + } ], blocked: [ { type: mongoose.Schema.Types.ObjectId, - ref: "User", - }, + ref: 'User' + } ], pinned: { _id: false, postId: [ { type: mongoose.Schema.Types.ObjectId, - ref: "Post", - }, - ], + ref: 'Post' + } + ] }, isAdmin: { type: Boolean, - default: false, + default: false }, isActivated: { type: Boolean, - default: false, + default: false }, isRemoved: { type: Boolean, - default: false, + default: false }, createdAt: { type: Date, required: true, - default: Date.now(), + default: Date.now() }, updatedAt: { type: Date, required: true, - default: Date.now(), + default: Date.now() }, tokens: [ { token: { type: String, - required: true, - }, - }, - ], -}); + required: true + } + } + ] +}) // generate auth token // Schema Methods, needs to be invoked by an instance of a Mongoose document UserSchema.methods.generateAuthToken = async function () { - const user = this; + const user = this const token = jwt.sign( { _id: user._id.toString() }, - "process.env.JWT_SECRET" - ); + 'process.env.JWT_SECRET' + ) - user.tokens = user.tokens.concat({ token: token }); - await user.save(); + user.tokens = user.tokens.concat({ token: token }) + await user.save() - return token; -}; + return token +} // Schema Statics are methods that can be invoked directly by a Model UserSchema.statics.findByCredentials = async (email, password) => { const user = await User.findOne({ - email: email, - }); + email: email + }) if (!user) { - throw new Error("No such user"); + throw new Error('No such user') } else { - const isMatch = await bcrypt.compare(password, user.password); + const isMatch = await bcrypt.compare(password, user.password) if (!isMatch) { - throw new Error("Incorrect password provided"); + throw new Error('Incorrect password provided') } else { - return user; + return user } } -}; +} // hash user password before saving into database -UserSchema.pre("save", async function (next) { - const user = this; +UserSchema.pre('save', async function (next) { + const user = this - if (user.isModified("password")) { - user.password = await bcrypt.hash(user.password, saltRounds); + if (user.isModified('password')) { + user.password = await bcrypt.hash(user.password, saltRounds) } - next(); -}); + next() +}) -const User = mongoose.model("User", UserSchema); -module.exports = User; +const User = mongoose.model('User', UserSchema) +module.exports = User diff --git a/app/routes/notification.js b/app/routes/notification.js index ff9a2eb..58152bd 100644 --- a/app/routes/notification.js +++ b/app/routes/notification.js @@ -1,30 +1,26 @@ -const express = require("express"); -const router = express.Router(); -const auth = require("../middleware/auth"); -const isUnderMaintenance = require("../middleware/maintenance"); -const notificationController = require("../controllers/notification"); +const express = require('express') +const router = express.Router() +const auth = require('../middleware/auth') +const isUnderMaintenance = require('../middleware/maintenance') +const notificationController = require('../controllers/notification') // GET NOTIFICATIONS FOR ALL router.get( - "/org/all", + '/org/all', isUnderMaintenance, auth, notificationController.getOrgNotifications -); +) // GET NOTIFICATIONS FOR LOGGED IN USER router.get( - "/user/all", + '/user/all', isUnderMaintenance, auth, notificationController.getUserNotification -); +) // GET NOTICATIONS FOR PROPOSALS -router.get( - "/proposal/all", - auth, - notificationController.getProposalNotifications -); +router.get('/proposal/all', notificationController.getProposalNotifications) -module.exports = router; +module.exports = router diff --git a/app/routes/proposal.js b/app/routes/proposal.js index 4269856..4a3650c 100644 --- a/app/routes/proposal.js +++ b/app/routes/proposal.js @@ -1,39 +1,37 @@ -const express = require("express"); -const router = express.Router(); -const auth = require("../middleware/auth"); -const proposalController = require("../controllers/proposal"); +const express = require('express') +const router = express.Router() +const proposalController = require('../controllers/proposal') // Create a new proposal -router.post("/", auth, proposalController.createProposal); +router.post('/', proposalController.createProposal) // Save the content of a proposal -router.patch("/:proposalId", auth, proposalController.saveProposal); +router.patch('/:proposalId', proposalController.saveProposal) // Attach file to the given proposal -router.post("/attach/:proposalId", auth, proposalController.attachFile); +router.post('/attach/:proposalId', proposalController.attachFile) // Get proposals by userId -router.get("/user/:userId", auth, proposalController.getByUserId); +router.get('/user/:userId', proposalController.getByUserId) // get proposal by proposalId -router.get("/:proposalId", auth, proposalController.getProposalById); +router.get('/:proposalId', proposalController.getProposalById) // Deletes a proposal by given proposalId -router.delete("/", auth, proposalController.deleteById); +router.delete('/', proposalController.deleteById) // Update proposal state -router.patch("/change/:proposalId", auth, proposalController.changeState); +router.patch('/change/:proposalId', proposalController.changeState) // Get all the proposals -router.post("/all", auth, proposalController.getAllProposals); +router.post('/all', proposalController.getAllProposals) -// Comment on the given proposal -router.post("/comment", auth, proposalController.commentOnProposal); +// Comment on the given proposala +router.post('/comment', proposalController.commentOnProposal) router.post( - "/notifications", - auth, + '/notifications', proposalController.getProposalNotificationsByUser -); +) -module.exports = router; +module.exports = router diff --git a/app/utils/notificationTags.js b/app/utils/notificationTags.js index 1b233e7..b7dbebe 100644 --- a/app/utils/notificationTags.js +++ b/app/utils/notificationTags.js @@ -1,11 +1,11 @@ const tags = { - RSVP: "RSVP", - UPDATE: "Update", - DELETE: "Delete", - NEW: "New!", - MAINTENANCE: "Maintenance", - FOLLOWER: "Follower", - ACTIVATE: "Activate", - COMMENT: "Comment", -}; -module.exports = tags; + RSVP: 'RSVP', + UPDATE: 'Update', + DELETE: 'Delete', + NEW: 'New!', + MAINTENANCE: 'Maintenance', + FOLLOWER: 'Follower', + ACTIVATE: 'Activate', + COMMENT: 'Comment' +} +module.exports = tags diff --git a/app/utils/proposal-notif-helper.js b/app/utils/proposal-notif-helper.js index 920f338..bcbcb33 100644 --- a/app/utils/proposal-notif-helper.js +++ b/app/utils/proposal-notif-helper.js @@ -1,26 +1,26 @@ -const User = require("../models/User"); -const ProposalNotification = require("../models/ProposalNotification"); +const User = require('../models/User') +const ProposalNotification = require('../models/ProposalNotification') module.exports = { // Notifications for a user addToNotificationForUser: async (userId, res, obj, next) => { try { - console.log("adding to user's notifications"); - const user = await User.findById(userId); - user.proposalNotifications.unshift(obj); - await user.save(); + console.log("adding to user's notifications") + const user = await User.findById(userId) + user.proposalNotifications.unshift(obj) + await user.save() } catch (error) { - console.log(error); + console.log(error) } }, // Notifications for all addNotificationForAll: async (req, res, obj, next) => { - const newNotification = new ProposalNotification(obj); + const newNotification = new ProposalNotification(obj) try { - await newNotification.save(); + await newNotification.save() } catch (error) { - console.log(error); + console.log(error) } - }, -}; + } +} diff --git a/config/fileHandlingConstants.js b/config/fileHandlingConstants.js index f10cb63..d760cb5 100644 --- a/config/fileHandlingConstants.js +++ b/config/fileHandlingConstants.js @@ -1,5 +1,5 @@ module.exports.fileParameters = { - limit: "200mb", + limit: '200mb', extended: true, - parameterLimit: 1000000, -}; + parameterLimit: 1000000 +} diff --git a/config/mongoose.js b/config/mongoose.js index 8e59c2a..25aa95b 100644 --- a/config/mongoose.js +++ b/config/mongoose.js @@ -1,15 +1,15 @@ -const mongoose = require("mongoose"); +const mongoose = require('mongoose') mongoose .connect(process.env.DATABASE_URL, { useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true, - useFindAndModify: false, + useFindAndModify: false }) .then(() => { - console.log("mongodb connection successful"); + console.log('mongodb connection successful') }) .catch((err) => { - console.log("mongodb connection error", err); - }); + console.log('mongodb connection error', err) + }) diff --git a/package.json b/package.json index dd8f1d4..f0c6ffd 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev": "env-cmd -f .env.dev nodemon ./bin/www", "pretest": "eslint --ignore-path .gitignore .", "lint": "eslint .", + "lint:fix": "eslint --fix --ext .js,.jsx .", "test": "env-cmd -f .env.test jest --detectOpenHandles && codecov -t 2b12ad97-07e0-45b2-a569-8aa2fd3e8c54" }, "dependencies": { diff --git a/test/proposal.test.js b/test/proposal.test.js index 92660bd..1098d9f 100644 --- a/test/proposal.test.js +++ b/test/proposal.test.js @@ -175,6 +175,9 @@ test('Should delete the proposal', async (done) => { .send(deleteProposalContent) .expect(HttpStatus.OK) + // confirm that the proposal was deleted + const proposal = await Proposal.findById(testProposalId) + expect(proposal).toBeNull() done() }) From a4dc630fcfbb3a539ab7e363995c53864039e68d Mon Sep 17 00:00:00 2001 From: Asel Date: Fri, 3 Jul 2020 17:20:59 +0530 Subject: [PATCH 34/42] minor changes --- app/controllers/post.js | 202 ++++++++++++++++++++-------------------- package.json | 1 - 2 files changed, 101 insertions(+), 102 deletions(-) diff --git a/app/controllers/post.js b/app/controllers/post.js index 1ff3601..affa4dc 100644 --- a/app/controllers/post.js +++ b/app/controllers/post.js @@ -1,112 +1,112 @@ -const PostModel = require("../models/Post"); -const UserModel = require("../models/User"); -const HANDLER = require("../utils/response-helper"); -const HttpStatus = require("http-status-codes"); -const imgUploadHelper = require("../utils/uploader"); -const permission = require("../utils/permission"); -const helper = require("../utils/paginate"); +const PostModel = require('../models/Post') +const UserModel = require('../models/User') +const HANDLER = require('../utils/response-helper') +const HttpStatus = require('http-status-codes') +const imgUploadHelper = require('../utils/uploader') +const permission = require('../utils/permission') +const helper = require('../utils/paginate') module.exports = { // CREATE POST create: async (req, res, next) => { - const post = new PostModel(req.body); - const userId = req.user.id.toString(); - post.userId = userId; + const post = new PostModel(req.body) + const userId = req.user.id.toString() + post.userId = userId if (req.file) { - imgUploadHelper.mapToDb(req, post); + imgUploadHelper.mapToDb(req, post) } try { - await post.save(); + await post.save() // req.io.emit('new post created', { data: post.content }) - return res.status(HttpStatus.CREATED).json({ post }); + return res.status(HttpStatus.CREATED).json({ post }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // DELETE POST delete: async (req, res, next) => { - const { id } = req.params; + const { id } = req.params try { - const post = await PostModel.findById(id); + const post = await PostModel.findById(id) if (!post) { return res .status(HttpStatus.NOT_FOUND) - .json({ message: "No post exists" }); + .json({ message: 'No post exists' }) } if (!permission.check(req, res, post.userId)) { return res .status(HttpStatus.BAD_REQUEST) - .json({ message: "Bad delete request" }); + .json({ message: 'Bad delete request' }) } - await PostModel.findByIdAndRemove(id); - res.status(HttpStatus.OK).json({ post: post, msg: "Deleted!" }); + await PostModel.findByIdAndRemove(id) + res.status(HttpStatus.OK).json({ post: post, msg: 'Deleted!' }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // UPDATE POST updatePost: async (req, res, next) => { - const { id } = req.params; - const updates = Object.keys(req.body); - const allowedUpdates = ["content", "imgUrl"]; + const { id } = req.params + const updates = Object.keys(req.body) + const allowedUpdates = ['content', 'imgUrl'] const isValidOperation = updates.every((update) => { - return allowedUpdates.includes(update); - }); + return allowedUpdates.includes(update) + }) if (!isValidOperation) { return res .status(HttpStatus.BAD_REQUEST) - .json({ message: "Invalid Update" }); + .json({ message: 'Invalid Update' }) } try { - const post = await PostModel.findById(id); + const post = await PostModel.findById(id) if (!post) { return res .status(HttpStatus.BAD_REQUEST) - .json({ message: "No post exists" }); + .json({ message: 'No post exists' }) } if (!permission.check(req, res, post.userId)) { return res .status(HttpStatus.FORBIDDEN) - .json({ message: "Bad update request" }); + .json({ message: 'Bad update request' }) } updates.forEach((update) => { - post[update] = req.body[update]; - }); + post[update] = req.body[update] + }) if (req.file) { - imgUploadHelper.mapToDb(req, post); + imgUploadHelper.mapToDb(req, post) } - await post.save(); - res.status(HttpStatus.OK).json({ post: post }); + await post.save() + res.status(HttpStatus.OK).json({ post: post }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // GET POST BY ID getPostById: async (req, res, next) => { - const { id } = req.params; + const { id } = req.params try { const post = await PostModel.findById(id) - .populate("comments", ["content", "votes"]) - .populate("userId", [ - "name.firstName", - "name.lastName", - "email", - "isAdmin", + .populate('comments', ['content', 'votes']) + .populate('userId', [ + 'name.firstName', + 'name.lastName', + 'email', + 'isAdmin' ]) .lean() - .exec(); + .exec() if (!post) { return res .status(HttpStatus.NOT_FOUND) - .json({ error: "Post not found" }); + .json({ error: 'Post not found' }) } - res.status(HttpStatus.OK).json({ post: post }); + res.status(HttpStatus.OK).json({ post: post }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, @@ -114,49 +114,49 @@ module.exports = { getAllPost: async (req, res, next) => { try { const posts = await PostModel.find({}, {}, helper.paginate(req)) - .populate("userId", [ - "name.firstName", - "name.lastName", - "email", - "isAdmin", + .populate('userId', [ + 'name.firstName', + 'name.lastName', + 'email', + 'isAdmin' ]) .sort({ updatedAt: -1 }) - .exec(); + .exec() if (!posts.length) { return res .status(HttpStatus.NOT_FOUND) - .json({ message: "No posts found" }); + .json({ message: 'No posts found' }) } - return res.status(HttpStatus.OK).json({ posts: posts }); + return res.status(HttpStatus.OK).json({ posts: posts }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // UPVOTE POST upvote: async (req, res, next) => { - const { id } = req.params; - const userId = req.user.id.toString(); + const { id } = req.params + const userId = req.user.id.toString() try { - const post = await PostModel.findById(id); + const post = await PostModel.findById(id) if (!post) { return res .status(HttpStatus.NOT_FOUND) - .json({ error: "No post found" }); + .json({ error: 'No post found' }) } // CHECKS IF THE USER HAS ALREADY UPVOTED THE COMMENT post.votes.upVotes.user.filter((user) => { if (JSON.stringify(user) === JSON.stringify(userId)) { return res .status(HttpStatus.BAD_REQUEST) - .json({ error: "Bad request" }); + .json({ error: 'Bad request' }) } - }); - post.votes.upVotes.user.unshift(userId); - await post.save(); - res.status(HttpStatus.OK).json({ post: post }); + }) + post.votes.upVotes.user.unshift(userId) + await post.save() + res.status(HttpStatus.OK).json({ post: post }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, @@ -167,49 +167,49 @@ module.exports = { {}, helper.paginate(req) ) - .populate("comments", ["content", "votes"]) - .populate("userId", [ - "name.firstName", - "name.lastName", - "_id", - "isAdmin", + .populate('comments', ['content', 'votes']) + .populate('userId', [ + 'name.firstName', + 'name.lastName', + '_id', + 'isAdmin' ]) .sort({ updatedAt: -1 }) - .exec(); - return res.status(HttpStatus.OK).json({ posts }); + .exec() + return res.status(HttpStatus.OK).json({ posts }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, // PIN THE POST pinPost: async (req, res, next) => { - const { id } = req.params; + const { id } = req.params try { - const post = await PostModel.findById(id); - const user = await UserModel.findById(req.user._id); + const post = await PostModel.findById(id) + const user = await UserModel.findById(req.user._id) if (!post) { return res .status(HttpStatus.NOT_FOUND) - .json({ msg: "No such post exists!" }); + .json({ msg: 'No such post exists!' }) } // toggle pinned post - post.isPinned = !post.isPinned; + post.isPinned = !post.isPinned // if already pinned then remove from their pinned items - const PinnedItems = user.pinned.postId; + const PinnedItems = user.pinned.postId if (PinnedItems.length > 0) { - const pinnedPostIndex = PinnedItems.indexOf(id); - user.pinned.postId.splice(pinnedPostIndex, 1); - await user.save(); + const pinnedPostIndex = PinnedItems.indexOf(id) + user.pinned.postId.splice(pinnedPostIndex, 1) + await user.save() } else { // save to the user pinned items - user.pinned.postId.unshift(id); - await user.save(); + user.pinned.postId.unshift(id) + await user.save() } - await post.save(); - return res.status(HttpStatus.OK).json({ post }); + await post.save() + return res.status(HttpStatus.OK).json({ post }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } }, @@ -217,22 +217,22 @@ module.exports = { getPinned: async (req, res, next) => { try { const posts = await PostModel.find({}, {}, helper.paginate(req)) - .populate("userId", [ - "name.firstName", - "name.lastName", - "email", - "isAdmin", + .populate('userId', [ + 'name.firstName', + 'name.lastName', + 'email', + 'isAdmin' ]) .sort({ updatedAt: -1 }) - .exec(); + .exec() // check for pinned post const pinnedPost = posts.filter((post) => { - return post.isPinned === true; - }); + return post.isPinned === true + }) // else return pinned posts - return res.status(HttpStatus.OK).json({ pinnedPost }); + return res.status(HttpStatus.OK).json({ pinnedPost }) } catch (error) { - HANDLER.handleError(res, error); + HANDLER.handleError(res, error) } - }, -}; + } +} diff --git a/package.json b/package.json index f0c6ffd..dd8f1d4 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ "dev": "env-cmd -f .env.dev nodemon ./bin/www", "pretest": "eslint --ignore-path .gitignore .", "lint": "eslint .", - "lint:fix": "eslint --fix --ext .js,.jsx .", "test": "env-cmd -f .env.test jest --detectOpenHandles && codecov -t 2b12ad97-07e0-45b2-a569-8aa2fd3e8c54" }, "dependencies": { From 346db8a6ec2719380d0c7fab1cfc4c181cbb02a3 Mon Sep 17 00:00:00 2001 From: K mehant <411843@student.nitandhra.ac.in> Date: Sun, 5 Jul 2020 15:16:01 +0530 Subject: [PATCH 35/42] Add container configurations Signed-off-by: K mehant <411843@student.nitandhra.ac.in> --- .dockerignore | 3 +++ Dockerfile.dev | 21 +++++++++++++++++++++ Dockerfile.prod | 14 ++++++++++++++ docker-compose.dev.yml | 27 +++++++++++++++++++++++++++ docker-compose.prod.yml | 30 ++++++++++++++++++++++++++++++ 5 files changed, 95 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile.dev create mode 100644 Dockerfile.prod create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.prod.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..842de51 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +node_modules +.git +.github diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 0000000..0ff91bb --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,21 @@ +FROM node:latest + +ENV NODE_ENV="development" + +# Copy package.json file into container +COPY package.json package.json +COPY package-lock.json package-lock.json + +# Install node modules +RUN npm install && \ + npm install --only=dev && \ + npm cache clean --force --loglevel=error + +# Volume to mount source code into container +VOLUME [ "/server" ] + +# move to the source code directory +WORKDIR /server + +# Start the server +CMD mv ../node_modules . && npm run dev \ No newline at end of file diff --git a/Dockerfile.prod b/Dockerfile.prod new file mode 100644 index 0000000..dd562e6 --- /dev/null +++ b/Dockerfile.prod @@ -0,0 +1,14 @@ +FROM node:latest + +ENV NODE_ENV="production" + +WORKDIR /server + +RUN git clone https://github.com/codeuino/social-platform-donut-backend.git + +WORKDIR /server/social-platform-donut-backend + +RUN npm install && npm cache clean --force --loglevel=error + +# Start the server +CMD [ "npm", "start" ] \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..469d76b --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,27 @@ +version: "3" +services: + server: + container_name: server + restart: always + expose: + - "5000" + build: + context: . + dockerfile: Dockerfile.dev + volumes: + - ./:/server + ports: + - "5000:5000" + links: + - mongo + env_file: + - .env.dev + mongo: + container_name: mongo + image: mongo + volumes: + - db-data:/data/db + ports: + - "27017:27017" +volumes: + db-data: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..96f2b1c --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,30 @@ +version: "3" +services: + server: + container_name: server-prod + restart: always + expose: + - "5000" + build: + context: . + dockerfile: Dockerfile.prod + ports: + - "5000:5000" + links: + - mongo + environment: + - PORT=5000 + - NODE_ENV="production" + - JWT_SECRET="thisismysupersecrettokenjustkidding" + - DATABASE_URL="mongodb://mongo:27017/donut-development" + - SENDGRID_API_KEY='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' + - SOCKET_PORT=8810 + mongo: + container_name: mongo + image: mongo + volumes: + - db-data:/data/db + ports: + - "27017:27017" +volumes: + db-data: From c047a4155fa6255fca86221cf964c40b9f2646de Mon Sep 17 00:00:00 2001 From: K mehant <411843@student.nitandhra.ac.in> Date: Sun, 5 Jul 2020 21:49:40 +0530 Subject: [PATCH 36/42] Add new line at EOF --- Dockerfile.dev | 2 +- Dockerfile.prod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 0ff91bb..1f0fd8e 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -18,4 +18,4 @@ VOLUME [ "/server" ] WORKDIR /server # Start the server -CMD mv ../node_modules . && npm run dev \ No newline at end of file +CMD mv ../node_modules . && npm run dev diff --git a/Dockerfile.prod b/Dockerfile.prod index dd562e6..ab12f99 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -11,4 +11,4 @@ WORKDIR /server/social-platform-donut-backend RUN npm install && npm cache clean --force --loglevel=error # Start the server -CMD [ "npm", "start" ] \ No newline at end of file +CMD [ "npm", "start" ] From 504834486dd99123366244e68b28dcb6cf32fbbb Mon Sep 17 00:00:00 2001 From: K mehant <411843@student.nitandhra.ac.in> Date: Sun, 5 Jul 2020 22:20:20 +0530 Subject: [PATCH 37/42] Add Github workflow for build and push donut-server image Signed-off-by: K mehant <411843@student.nitandhra.ac.in> --- .github/image-workflow.yaml | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 .github/image-workflow.yaml diff --git a/.github/image-workflow.yaml b/.github/image-workflow.yaml new file mode 100644 index 0000000..42d3307 --- /dev/null +++ b/.github/image-workflow.yaml @@ -0,0 +1,40 @@ +name: donut-server-image-ci + +on: + push: + branches: + - development + + tags: + - v* + +env: + IMAGE_NAME: donut-server:latest + REPO_NAME: codeuino + REGISTRY_NAME: registry.hub.docker.com + +jobs: + push: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Build image + run: docker build . --file Dockerfile.prod --tag $IMAGE_NAME + + - name: Log into registry + run: docker login -u {{ secrets.DOCKER_USERNAME }} -p {{ secrets.DOCKER_PASSWORD }} + + - name: Push image + run: | + IMAGE_ID=$REGISTRY_NAME/$REPO_NAME/$IMAGE_NAME + + [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') + + echo IMAGE_ID=$IMAGE_ID + echo VERSION=$VERSION + + docker tag $IMAGE_NAME $IMAGE_ID:$VERSION + docker push $IMAGE_ID:$VERSION + docker tag $IMAGE_NAME $IMAGE_ID:latest + docker push $IMAGE_ID:$VERSION From 703830d062fc8c094903f1da3abcc5fbc0011796 Mon Sep 17 00:00:00 2001 From: K mehant <411843@student.nitandhra.ac.in> Date: Mon, 6 Jul 2020 21:06:34 +0530 Subject: [PATCH 38/42] Fix env file Signed-off-by: K mehant <411843@student.nitandhra.ac.in> --- .env.dev | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.dev b/.env.dev index afc775f..9a4dbca 100644 --- a/.env.dev +++ b/.env.dev @@ -1,6 +1,6 @@ PORT=5000 NODE_ENV="development" JWT_SECRET="thisismysupersecrettokenjustkidding" -DATABASE_URL="mongodb://localhost:27017/donut-development" -SENDGRID_API_KEY = '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' -SOCKET_PORT = 8810 \ No newline at end of file +DATABASE_URL="mongodb://mongo:27017/donut-development" +SENDGRID_API_KEY='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' +SOCKET_PORT=8810 \ No newline at end of file From 4356a953a7dcb48ace7231e6e95b4b82dca30a7d Mon Sep 17 00:00:00 2001 From: K mehant <411843@student.nitandhra.ac.in> Date: Mon, 6 Jul 2020 21:07:28 +0530 Subject: [PATCH 39/42] Add pm2 to make server production ready --- Dockerfile.prod | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Dockerfile.prod b/Dockerfile.prod index ab12f99..464847e 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -8,7 +8,9 @@ RUN git clone https://github.com/codeuino/social-platform-donut-backend.git WORKDIR /server/social-platform-donut-backend -RUN npm install && npm cache clean --force --loglevel=error +RUN npm install && \ + npm install pm2@latest -g && \ + npm cache clean --force --loglevel=error # Start the server -CMD [ "npm", "start" ] +CMD [ "pm2", "start", "./bin/www", "--time", "--no-daemon" ] From bf387657aa331e95fc06f0ae5f038726529845d5 Mon Sep 17 00:00:00 2001 From: K mehant <411843@student.nitandhra.ac.in> Date: Tue, 7 Jul 2020 16:48:19 +0530 Subject: [PATCH 40/42] Change repo name to codeuino1 Signed-off-by: K mehant <411843@student.nitandhra.ac.in> --- .github/{ => workflows}/image-workflow.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/{ => workflows}/image-workflow.yaml (97%) diff --git a/.github/image-workflow.yaml b/.github/workflows/image-workflow.yaml similarity index 97% rename from .github/image-workflow.yaml rename to .github/workflows/image-workflow.yaml index 42d3307..317022e 100644 --- a/.github/image-workflow.yaml +++ b/.github/workflows/image-workflow.yaml @@ -10,7 +10,7 @@ on: env: IMAGE_NAME: donut-server:latest - REPO_NAME: codeuino + REPO_NAME: codeuino1 REGISTRY_NAME: registry.hub.docker.com jobs: From c4174216d72dc5cf0f1e79db27e826b5990422b5 Mon Sep 17 00:00:00 2001 From: K mehant <411843@student.nitandhra.ac.in> Date: Wed, 8 Jul 2020 19:23:18 +0530 Subject: [PATCH 41/42] Add specific node version Node->v14 ships npm->v6 Signed-off-by: K mehant <411843@student.nitandhra.ac.in> --- Dockerfile.dev | 2 +- Dockerfile.prod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile.dev b/Dockerfile.dev index 1f0fd8e..3c81e0c 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,4 @@ -FROM node:latest +FROM node:14 ENV NODE_ENV="development" diff --git a/Dockerfile.prod b/Dockerfile.prod index 464847e..f5d7097 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -1,4 +1,4 @@ -FROM node:latest +FROM node:14 ENV NODE_ENV="production" From 73161d45c7d9b0a6be21575630984b1251710e0b Mon Sep 17 00:00:00 2001 From: K mehant <411843@student.nitandhra.ac.in> Date: Wed, 8 Jul 2020 20:42:16 +0530 Subject: [PATCH 42/42] Fix workflow Signed-off-by: K mehant <411843@student.nitandhra.ac.in> --- .github/workflows/image-workflow.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/image-workflow.yaml b/.github/workflows/image-workflow.yaml index 317022e..acea905 100644 --- a/.github/workflows/image-workflow.yaml +++ b/.github/workflows/image-workflow.yaml @@ -23,8 +23,7 @@ jobs: run: docker build . --file Dockerfile.prod --tag $IMAGE_NAME - name: Log into registry - run: docker login -u {{ secrets.DOCKER_USERNAME }} -p {{ secrets.DOCKER_PASSWORD }} - + run: docker login --username {{ secrets.DOCKER_USERNAME }} --password {{ secrets.DOCKER_PASSWORD }} - name: Push image run: | IMAGE_ID=$REGISTRY_NAME/$REPO_NAME/$IMAGE_NAME