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/.env.dev b/.env.dev
index a4df8b4..b5e54cd 100644
--- a/.env.dev
+++ b/.env.dev
@@ -1,4 +1,6 @@
PORT=5000
NODE_ENV="development"
JWT_SECRET="thisismysupersecrettokenjustkidding"
-DATABASE_URL="mongodb://localhost:27017/donut-development"
+DATABASE_URL="mongodb://mongo:27017/donut-development"
+SENDGRID_API_KEY='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'
+SOCKET_PORT=8810
diff --git a/.env.test b/.env.test
index 42459d6..dd4d183 100644
--- a/.env.test
+++ b/.env.test
@@ -1,4 +1,6 @@
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 = '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'
+SOCKET_PORT = 8810
\ No newline at end of file
diff --git a/.github/workflows/image-workflow.yaml b/.github/workflows/image-workflow.yaml
new file mode 100644
index 0000000..acea905
--- /dev/null
+++ b/.github/workflows/image-workflow.yaml
@@ -0,0 +1,39 @@
+name: donut-server-image-ci
+
+on:
+ push:
+ branches:
+ - development
+
+ tags:
+ - v*
+
+env:
+ IMAGE_NAME: donut-server:latest
+ REPO_NAME: codeuino1
+ 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 --username {{ secrets.DOCKER_USERNAME }} --password {{ 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
diff --git a/Dockerfile.dev b/Dockerfile.dev
new file mode 100644
index 0000000..3c81e0c
--- /dev/null
+++ b/Dockerfile.dev
@@ -0,0 +1,21 @@
+FROM node:14
+
+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
diff --git a/Dockerfile.prod b/Dockerfile.prod
new file mode 100644
index 0000000..f5d7097
--- /dev/null
+++ b/Dockerfile.prod
@@ -0,0 +1,16 @@
+FROM node:14
+
+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 install pm2@latest -g && \
+ npm cache clean --force --loglevel=error
+
+# Start the server
+CMD [ "pm2", "start", "./bin/www", "--time", "--no-daemon" ]
diff --git a/app.js b/app.js
index 0171e14..a743490 100644
--- a/app.js
+++ b/app.js
@@ -4,13 +4,44 @@ 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 app = express()
+const server = require('http').Server(app)
+
+app.use(cors())
+
+app.use(bodyParser.json({ limit: '200mb' }))
+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'))
@@ -21,15 +52,26 @@ 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)
+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
@@ -43,4 +85,4 @@ app.use(function (err, req, res, next) {
res.render('error')
})
-module.exports = app
+module.exports = { app, io }
diff --git a/app/controllers/auth.js b/app/controllers/auth.js
index dd5ea0e..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
@@ -10,15 +10,15 @@ 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(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/comment.js b/app/controllers/comment.js
new file mode 100644
index 0000000..f78bb6b
--- /dev/null
+++ b/app/controllers/comment.js
@@ -0,0 +1,151 @@
+const HANDLER = require('../utils/response-helper')
+const HttpStatus = require('http-status-codes')
+const CommentModel = require('../models/Comment')
+const permission = require('../utils/permission')
+const helper = require('../utils/paginate')
+
+module.exports = {
+ // CREATE COMMENT (ISSUE IN CREATE COMMENT )
+ comment: async (req, res, next) => {
+ const { id } = req.params
+ const userId = req.user.id.toString()
+ 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
+ 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 (!permission.check(req, res, comment.userId)) {
+ return res.status(HttpStatus.FORBIDDEN).json({ message: 'Bad delete request' })
+ }
+ await CommentModel.findByIdAndRemove(id)
+ res.status(HttpStatus.OK).json({ comment: comment })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ },
+
+ // UPDATE COMMENT
+ update: async (req, res, next) => {
+ const { id } = req.params
+ const updates = Object.keys(req.body)
+ const valid = ['content']
+ 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 exist' })
+ }
+ // also add admin or moderator control (TODO)
+ if (!permission.check(req, res, comment.userId)) {
+ return res.status(HttpStatus.BAD_REQUEST).json({ error: 'Wrong update' })
+ }
+ updates.forEach(update => {
+ comment[update] = req.body[update]
+ })
+ await comment.save()
+ 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 }, {}, helper.paginate(req))
+ .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 })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ },
+
+ // UPVOTE COMMENT
+ 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(HttpStatus.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(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.user.remove(user)
+ }
+ })
+ comment.votes.upVotes.user.unshift(userId)
+ await comment.save()
+ 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()
+ try {
+ const comment = await CommentModel.findById(id)
+ if (!comment) {
+ return res.status(HttpStatus.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(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.user.remove(user)
+ }
+ })
+ comment.votes.downVotes.user.unshift(userId)
+ await comment.save()
+ res.status(HttpStatus.OK).json({ comment: comment })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ }
+}
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/event.js b/app/controllers/event.js
new file mode 100644
index 0000000..b8898cb
--- /dev/null
+++ b/app/controllers/event.js
@@ -0,0 +1,189 @@
+const Event = require('../models/Event')
+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) => {
+ const event = new Event(req.body)
+ 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, next) => {
+ 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' })
+ }
+ // check for permission (TODO AFTER PREVIOUS PR MERGED)
+ updates.forEach(update => {
+ 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, next) => {
+ const { yes, no, maybe } = req.body
+ const { id } = req.params
+ notification.tag = 'RSVP'
+ 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)) {
+ 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()
+ 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 })
+ }
+ }
+ if (no) {
+ try {
+ event.rsvpNo.push(req.user.id)
+ await event.save()
+ 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 })
+ }
+ }
+ if (maybe) {
+ try {
+ event.rsvpMaybe.push(req.user.id)
+ await event.save()
+ 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 })
+ }
+ }
+ } 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!' })
+ }
+ return res.status(HttpStatus.OK).json({ event: EventData })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ },
+
+ 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()
+ return res.status(HttpStatus.OK).json({ events: EventData })
+ } catch (error) {
+ HANDLER.handleError(res, 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' })
+ }
+ 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!' })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ },
+
+ UpComingEvents: async (req, res, next) => {
+ try {
+ const events = await Event.find({ eventDate: { $gt: Date.now() } }, {}, helper.paginate(req))
+ .sort({ eventDate: -1 })
+ .exec()
+ console.log('Upcoming events ', events)
+ return res.status(HttpStatus.OK).json({ events })
+ } catch (error) {
+ HANDLER.handleError(res, next)
+ }
+ },
+
+ getAllEventByUser: async (req, res, next) => {
+ try {
+ const events = await Event.find({ createdBy: req.user._id }, {}, helper.paginate(req))
+ .sort({ eventDate: -1 })
+ .populate('createdBy', '_id name.firstName name.lastName')
+ .exec()
+ 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..96147b4
--- /dev/null
+++ b/app/controllers/notification.js
@@ -0,0 +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 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)
+ )
+ .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)
+ }
+ },
+
+ 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
new file mode 100644
index 0000000..92627d8
--- /dev/null
+++ b/app/controllers/organization.js
@@ -0,0 +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 notification = {
+ heading: '',
+ content: '',
+ tag: ''
+}
+
+module.exports = {
+ createOrganization: async (req, res, next) => {
+ const org = new Organization(req.body)
+ if (req.file) {
+ 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 })
+ } 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',
+ 'image',
+ 'imgUrl',
+ 'adminInfo',
+ 'moderatorInfo'
+ ]
+ const isValidOperation = updates.every((update) => {
+ return allowedUpdates.includes(update)
+ })
+
+ if (!isValidOperation) {
+ return res
+ .status(HttpStatus.BAD_REQUEST)
+ .json({ error: 'invalid update' })
+ }
+ 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]
+ })
+ if (req.file) {
+ helper.mapToDb(req, org)
+ }
+ await org.save()
+ res.status(HttpStatus.OK).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 })
+ .lean()
+ .exec()
+ if (!orgData) {
+ return res
+ .status(HttpStatus.NOT_FOUND)
+ .json({ error: 'No such organization exists!' })
+ }
+ res.status(HttpStatus.OK).json({ organization: orgData })
+ } 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(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!" })
+ }
+ 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)
+ }
+ },
+
+ archiveOrg: async (req, res, next) => {
+ const { id } = req.params
+ try {
+ const org = await Organization.findById(id)
+ if (!org) {
+ 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 })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ },
+
+ triggerMaintenance: async (req, res, next) => {
+ 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) || 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
+
+ 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!" })
+ }
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ },
+
+ 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)
+ }
+ },
+ 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)
+ }
+ },
+ // SEARCH FUNCTIONALITY
+ getMembers: async (req, res, next) => {
+ try {
+ const { search } = req.query
+ if (search) {
+ 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()
+ 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 isRemoved')
+ .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)
+ }
+ },
+ // 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 36c1ff2..affa4dc 100644
--- a/app/controllers/post.js
+++ b/app/controllers/post.js
@@ -1,32 +1,238 @@
-const userModel = require('../models/User')
-const bcrypt = require('bcrypt')
-const jwt = require('jsonwebtoken')
+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: 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 POST
+ 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()
+ // req.io.emit('new post created', { data: post.content })
+ return res.status(HttpStatus.CREATED).json({ post })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ },
+
+ // DELETE POST
+ delete: async (req, res, next) => {
+ const { id } = req.params
+ try {
+ const post = await PostModel.findById(id)
+ if (!post) {
+ return res
+ .status(HttpStatus.NOT_FOUND)
+ .json({ message: 'No post exists' })
+ }
+ if (!permission.check(req, res, post.userId)) {
+ return res
+ .status(HttpStatus.BAD_REQUEST)
+ .json({ message: 'Bad delete request' })
}
+ await PostModel.findByIdAndRemove(id)
+ 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
+ const updates = Object.keys(req.body)
+ const allowedUpdates = ['content', 'imgUrl']
+ const isValidOperation = updates.every((update) => {
+ return allowedUpdates.includes(update)
})
+
+ if (!isValidOperation) {
+ 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' })
+ }
+ if (!permission.check(req, res, post.userId)) {
+ return res
+ .status(HttpStatus.FORBIDDEN)
+ .json({ message: 'Bad update request' })
+ }
+ updates.forEach((update) => {
+ post[update] = req.body[update]
+ })
+ if (req.file) {
+ imgUploadHelper.mapToDb(req, post)
+ }
+ await post.save()
+ res.status(HttpStatus.OK).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 })
+
+ // 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' })
+ }
+ res.status(HttpStatus.OK).json({ post: post })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ },
+
+ // GET ALL THE POSTS
+ getAllPost: async (req, res, next) => {
+ try {
+ const posts = await PostModel.find({}, {}, helper.paginate(req))
+ .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)
+ }
+ },
+
+ // UPVOTE POST
+ 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(HttpStatus.NOT_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' })
}
+ })
+ post.votes.upVotes.user.unshift(userId)
+ await post.save()
+ res.status(HttpStatus.OK).json({ post: post })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ },
+
+ getPostByUser: async (req, res, next) => {
+ 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()
+ return res.status(HttpStatus.OK).json({ posts })
+ } 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)
+ }
},
- test: function (req, res, next) {
- res.json({ success: 'ulllu' })
+
+ // GET ALL PINNED POST
+ getPinned: async (req, res, next) => {
+ try {
+ 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
+ })
+ // else return pinned posts
+ return res.status(HttpStatus.OK).json({ pinnedPost })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
}
}
diff --git a/app/controllers/project.js b/app/controllers/project.js
new file mode 100644
index 0000000..784f9e4
--- /dev/null
+++ b/app/controllers/project.js
@@ -0,0 +1,108 @@
+const Project = require('../models/Project')
+const HANDLER = require('../utils/response-helper')
+const HttpStatus = require('http-status-codes')
+const helper = require('../utils/paginate')
+const permission = require('../utils/permission')
+
+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) => {
+ try {
+ const projects = await Project.find({}, {}, helper.paginate(req))
+ .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 (permission.check(req, res, project.createdBy)) {
+ await Project.findByIdAndRemove(id)
+ return res.status(HttpStatus.OK).json({ msg: 'Project deleted!' })
+ }
+ return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Not permitted!' })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ },
+ projectCreatedByUser: async (req, res, next) => {
+ try {
+ const { id } = req.user
+ const projects = await Project.find({ createdBy: id }, {}, helper.paginate(req))
+ .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)
+ }
+ }
+}
diff --git a/app/controllers/proposal.js b/app/controllers/proposal.js
new file mode 100644
index 0000000..d550cf1
--- /dev/null
+++ b/app/controllers/proposal.js
@@ -0,0 +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')
+
+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)
+ }
+ },
+
+ // Updates the content of the proposal
+ saveProposal: async (req, res, next) => {
+ const { proposalId } = req.params
+ const { content, title, description } = req.body
+
+ try {
+ const proposal = await ProposalModel.findByIdAndUpdate(proposalId, {
+ content: content,
+ title: title,
+ proposalDescription: description
+ })
+ 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 {
+ var newFileUploaded = {
+ fileLink: s3FileURL + file.originalname,
+ s3_key: params.Key
+ }
+
+ ProposalModel.findOneAndUpdate(
+ { _id: proposalId },
+ { $push: { attachments: newFileUploaded } },
+ function (error, success) {
+ if (error) {
+ console.log(error)
+ } else {
+ console.log(success)
+ }
+ }
+ )
+
+ res.send({ data })
+ }
+ })
+ },
+
+ // Get proposals by userId
+ getByUserId: async (req, res, next) => {
+ const { userId } = req.params
+
+ try {
+ 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' })
+ }
+ 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
+
+ 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}`
+
+ 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)
+ }
+ },
+
+ // Changes the state of a given proposal
+ changeState: async (req, res, next) => {
+ const { proposalId } = req.params
+ const proposalStatus = req.body.proposalStatus
+ try {
+ 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' })
+ }
+
+ 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)
+ }
+ },
+
+ // 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)
+ }
+ },
+
+ getAllProposals: async (req, res, next) => {
+ console.log('All proposals called')
+ try {
+ const proposals = await ProposalModel.find({})
+ if (!proposals.length) {
+ return res
+ .status(HttpStatus.NOT_FOUND)
+ .json({ message: 'No proposals found' })
+ }
+ return res.status(HttpStatus.OK).json({ proposals: proposals })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ },
+
+ commentOnProposal: async (req, res, next) => {
+ const { proposalId, comment, userId, isAuthor, author } = req.body
+
+ try {
+ 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 } } }
+ )
+
+ 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/controllers/urlShortner.js b/app/controllers/urlShortner.js
new file mode 100644
index 0000000..0235673
--- /dev/null
+++ b/app/controllers/urlShortner.js
@@ -0,0 +1,46 @@
+const UrlModel = require('../models/UrlShortner')
+const HttpStatus = require('http-status-codes')
+const validator = require('validator')
+
+module.exports = {
+ redirect: async (req, res) => {
+ try {
+ const { urlcode } = req.params
+ const url = await UrlModel.findOne({ urlCode: urlcode })
+ if (url) {
+ return res.status(HttpStatus.OK).redirect(url.longUrl)
+ } else {
+ return res.status(HttpStatus.NOT_FOUND).json('No url found!')
+ }
+ } catch (error) {
+ res.status(HttpStatus.INTERNAL_SERVER_ERROR).json('Server error!')
+ }
+ },
+
+ shorten: async (req, res) => {
+ var { longUrl } = req.body
+ var baseurl = req.get('host')
+ var urlCode = Date.now()
+ if (validator.isURL(longUrl)) {
+ try {
+ var url = await UrlModel.findOne({ longUrl })
+ if (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)
+ return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json('Server error')
+ }
+ } else {
+ res.status(HttpStatus.NOT_FOUND).json('invalid long url')
+ }
+ }
+}
diff --git a/app/controllers/user.js b/app/controllers/user.js
index a3e9f1e..622d812 100644
--- a/app/controllers/user.js
+++ b/app/controllers/user.js
@@ -1,29 +1,69 @@
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')
+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
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()
- res.status(201).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(400).json({ error: error })
+ return res.status(HttpStatus.NOT_ACCEPTABLE).json({ error: error })
}
},
+ // GET USER PROFILE
userProfile: async (req, res, next) => {
- res.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
userProfileUpdate: async (req, res, next) => {
const updates = Object.keys(req.body)
- const allowedUpdates = ['name', 'email', 'password', 'company', 'website', 'location', 'about']
+ const allowedUpdates = [
+ 'name',
+ 'email',
+ 'phone',
+ 'info',
+ 'about'
+ ]
const isValidOperation = updates.every((update) => {
return allowedUpdates.includes(update)
})
if (!isValidOperation) {
- return res.status(400).json({ error: 'invalid update' })
+ return res.status(HttpStatus.BAD_REQUEST).json({ error: 'invalid update' })
}
try {
@@ -31,18 +71,323 @@ module.exports = {
req.user[update] = req.body[update]
})
await req.user.save()
- res.status(200).json({ data: req.user })
+ return res.status(HttpStatus.OK).json({ data: req.user })
+ } catch (error) {
+ return res.status(HttpStatus.BAD_REQUEST).json({ error })
+ }
+ },
+
+ // FORGOT PASSWORD REQUEST
+ forgotPasswordRequest: async (req, res) => {
+ const { email } = req.body
+ try {
+ const user = await User.findOne({ email: email })
+ if (!user) {
+ 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()
+ return res.status(HttpStatus.OK).json({ success: true, token })
+ } catch (error) {
+ if (process.env.NODE_ENV !== 'production' && error) {
+ console.log('Error in forgotPasswordRequest ', error)
+ }
+ return res.status(HttpStatus.BAD_REQUEST).json({ error })
+ }
+ },
+
+ updatePassword: async (req, res, next) => {
+ const { password, id } = req.body
+ const { token } = req.params
+ try {
+ 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(HttpStatus.BAD_REQUEST).json({ msg: 'No such user' })
+ }
+ 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') {
+ console.log('token expired')
+ }
+ res.status(HttpStatus.BAD_REQUEST).json({ error: 'Token expired' })
+ }
} catch (error) {
- res.status(400).json({ error })
+ if (process.env.NODE_ENV !== 'production' && error) {
+ console.log('Something went wrong ', error)
+ }
+ res.status(HttpStatus.BAD_REQUEST).json({ error })
}
},
+
+ // LOGOUT USER
+ 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)
+ }
+ },
+
+ // REMOVE USER
userDelete: async (req, res, next) => {
try {
- await req.user.remove()
- res.send({ data: 'user deletetion 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(500).json({ error })
+ return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ error })
+ }
+ },
+
+ // USER ACCOUNT ACTIVATION
+ activateAccount: async (req, res, next) => {
+ try {
+ const { token } = req.params
+ const decodedToken = jwt.verify(token, 'process.env.JWT_SECRET')
+ const expiryTime = decodedToken.iat + 24 * 3600 * 1000 // 24 hrs
+ if (expiryTime <= Date.now()) {
+ const user = await User.findById(decodedToken._id)
+ if (!user) {
+ return res.status(HttpStatus.NOT_FOUND).json({ msg: 'User not found!' })
+ }
+ // 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) {
+ return res.status(HttpStatus.BAD_REQUEST).json({ Error })
+ }
+ },
+
+ // GET INVITE LINK
+ 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 })
+ },
+
+ // PROCESS THE INVITE LINK
+ 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!' })
+ },
+
+ // 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)
+ 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()
+ 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)
+ }
+ },
+
+ // 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)
+ 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()
+ 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)
+ }
+ },
+
+ // BLOCK THE USER
+ 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)
+ }
+ },
+
+ // UNBLOCK USER
+ 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({ user })
+ }
+ return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have permission!' })
+ } catch (error) {
+ HANDLER.handleError(res, error)
+ }
+ },
+
+ // GET OVERALL PERSONAL OVERVIEW
+ getPersonalOverview: async (req, res, next) => {
+ const userId = req.user._id
+ const personalOverview = {}
+ try {
+ personalOverview.projects = await Projects.find({ createdBy: userId }).estimatedDocumentCount()
+ personalOverview.events = await Events.find({ createdBy: userId }).estimatedDocumentCount()
+ return res.status(HttpStatus.OK).json({ personalOverview })
+ } 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/activate.js b/app/middleware/activate.js
new file mode 100644
index 0000000..5104d83
--- /dev/null
+++ b/app/middleware/activate.js
@@ -0,0 +1,19 @@
+const User = require('../models/User')
+const HttpStatus = require('http-status-codes')
+
+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(HttpStatus.BAD_REQUEST).json({ Error })
+ }
+}
+
+module.exports = isActivated
diff --git a/app/middleware/auth.js b/app/middleware/auth.js
index 7acd520..67c266c 100644
--- a/app/middleware/auth.js
+++ b/app/middleware/auth.js
@@ -1,14 +1,38 @@
const jwt = require('jsonwebtoken')
const User = require('../models/User')
+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
})
+ .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()
@@ -18,7 +42,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/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/Comment.js b/app/models/Comment.js
index 0398a53..7daa41d 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,
@@ -35,28 +23,16 @@ const commentSchema = new Schema({
},
votes: {
upVotes: {
- count: {
- 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/models/Event.js b/app/models/Event.js
index 59473e7..782bf5c 100644
--- a/app/models/Event.js
+++ b/app/models/Event.js
@@ -47,27 +47,27 @@ 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'
- }
+ eventTime: {
+ type: String
},
+ 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 +84,15 @@ 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()
+ },
+ createdBy: {
+ type: Schema.Types.ObjectId,
+ ref: 'User'
+ },
+ isOnline: {
+ type: Boolean,
+ default: false
},
createdAt: {
type: Date,
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/Organisation.js b/app/models/Organisation.js
index fa63da8..8891abe 100644
--- a/app/models/Organisation.js
+++ b/app/models/Organisation.js
@@ -46,32 +46,36 @@ const orgSchema = new Schema({
}
}
},
- logo: {
- type: Buffer,
+ image: {
+ data: Buffer,
contentType: String
},
- logoUrl: {
+ imgUrl: {
type: String,
trim: true,
- validator (logoUrl) {
- if (!validator.isURL(logoUrl)) {
- throw new Error('Invalid logo URL!')
+ validator (imgUrl) {
+ if (!validator.isURL(imgUrl)) {
+ throw new Error('Invalid image URL!')
}
}
},
contactInfo: {
- emailId: {
+ email: {
type: String,
required: true,
- validate (emailId) {
- if (validator.isEmpty(emailId)) {
+ validate (email) {
+ if (validator.isEmpty(email)) {
throw new Error('EmailId or org is required!')
}
- if (!validator.isEmail(emailId)) {
+ if (!validator.isEmail(email)) {
throw new Error('Invalid emailId')
}
}
},
+ adminEmail: {
+ type: String,
+ trim: true
+ },
website: {
type: String,
trim: true,
@@ -87,29 +91,91 @@ const orgSchema = new Schema({
},
chattingPlatform: [
{
+ _id: false,
link: {
type: String
}
}
]
},
- adminInfo: {
- type: Object,
- required: true,
- validate (adminInfo) {
- if (validator.isEmpty(adminInfo)) {
- throw new Error('Admin info is required!')
+ options: {
+ _id: false,
+ 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: {
- type: Object,
- required: true,
- validate (adminInfo) {
- if (validator.isEmpty(adminInfo)) {
- throw new Error('Admin info is required!')
- }
- }
+ _id: false,
+ adminId: [{
+ type: Schema.Types.ObjectId,
+ ref: 'User'
+ }]
+ },
+ isArchived: {
+ type: Boolean,
+ default: false
+ },
+ isMaintenance: {
+ type: Boolean,
+ default: false
},
createdAt: {
type: Date,
diff --git a/app/models/Post.js b/app/models/Post.js
index ae528b0..36b2a7d 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
@@ -28,36 +32,20 @@ const PostSchema = new Schema({
},
votes: {
upVotes: {
- count: {
- type: Number,
- default: 0
- },
- users: {
- user: {
- type: Schema.Types.ObjectId,
- ref: 'User',
- required: true
- }
- }
- },
- downVotes: {
- count: {
- type: Number,
- default: 0
- },
- users: {
- user: {
- type: Schema.Types.ObjectId,
- ref: 'User',
- required: true
- }
- }
+ user: [{
+ type: Schema.Types.ObjectId,
+ ref: 'User'
+ }]
}
},
comments: {
type: Schema.Types.ObjectId,
ref: 'Comment'
},
+ isPinned: {
+ type: Boolean,
+ default: false
+ },
createdAt: {
type: Date,
required: true,
diff --git a/app/models/Project.js b/app/models/Project.js
index cc49ac8..7d5ada8 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: {
@@ -52,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!')
}
@@ -89,6 +75,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/Proposal.js b/app/models/Proposal.js
new file mode 100644
index 0000000..4a1f97b
--- /dev/null
+++ b/app/models/Proposal.js
@@ -0,0 +1,43 @@
+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'
+ },
+ creator: {
+ type: Schema.Types.ObjectId,
+ ref: 'User',
+ required: true
+ },
+ proposalDescription: {
+ type: String
+ },
+ attachments: [{ fileLink: String, s3_key: String }],
+
+ createdAt: {
+ type: Date,
+ required: true,
+ default: Date.now()
+ },
+ updatedAt: {
+ type: Date,
+ required: true,
+ default: Date.now()
+ },
+ comments: [{ userName: String, comment: String }]
+ },
+ { timestamps: true }
+)
+
+module.exports = mongoose.model('Proposal', proposalSchema)
diff --git a/app/models/ProposalNotification.js b/app/models/ProposalNotification.js
new file mode 100644
index 0000000..920b39f
--- /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/UrlShortner.js b/app/models/UrlShortner.js
new file mode 100644
index 0000000..e18b4e0
--- /dev/null
+++ b/app/models/UrlShortner.js
@@ -0,0 +1,17 @@
+const mongoose = require('mongoose')
+
+const urlShortnerSchema = new mongoose.Schema({
+ longUrl: {
+ type: String,
+ required: true
+ },
+ urlCode: {
+ type: String
+ },
+ shortUrl: {
+ type: String
+ }
+})
+
+const shortURL = mongoose.model('shortURL', urlShortnerSchema)
+module.exports = shortURL
diff --git a/app/models/User.js b/app/models/User.js
index a2af919..9dd5946 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 })) {
@@ -89,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')
@@ -137,6 +135,80 @@ const UserSchema = new mongoose.Schema({
}
}
},
+ notifications: [
+ {
+ heading: {
+ type: String
+ },
+ content: {
+ type: String
+ },
+ tag: {
+ type: String
+ }
+ }
+ ],
+ proposalNotifications: [
+ {
+ heading: {
+ type: String
+ },
+ content: {
+ type: String
+ },
+ tag: {
+ type: String
+ },
+ createdAt: {
+ type: Date,
+ required: true,
+ default: Date.now()
+ }
+ }
+ ],
+ 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'
+ }
+ ]
+ },
+ firstRegister: {
+ type: Boolean,
+ default: false
+ },
+ isAdmin: {
+ type: Boolean,
+ default: false
+ },
+ isActivated: {
+ type: Boolean,
+ default: false
+ },
+ isRemoved: {
+ type: Boolean,
+ default: false
+ },
createdAt: {
type: Date,
required: true,
@@ -147,20 +219,24 @@ const UserSchema = new mongoose.Schema({
required: true,
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 token = jwt.sign(
+ { _id: user._id.toString() },
+ 'process.env.JWT_SECRET'
+ )
user.tokens = user.tokens.concat({ token: token })
await user.save()
@@ -175,11 +251,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/auth.js b/app/routes/auth.js
index 2c78f57..f42fd96 100644
--- a/app/routes/auth.js
+++ b/app/routes/auth.js
@@ -1,10 +1,13 @@
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
new file mode 100644
index 0000000..5d16b55
--- /dev/null
+++ b/app/routes/comment.js
@@ -0,0 +1,55 @@
+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
+)
+
+// DELETE COMMENT BY ID
+router.delete(
+ '/:id',
+ isUnderMaintenance,
+ auth,
+ commentController.delete
+)
+
+// UPDATE COMMENT BY ID
+router.patch(
+ '/:id',
+ isUnderMaintenance,
+ auth,
+ commentController.update
+)
+
+// GET COMMENT BY POST ID
+router.get(
+ '/:id',
+ isUnderMaintenance,
+ auth,
+ commentController.getCommentByPost
+)
+
+// UPVOTE COMMENT BY COMMENT ID
+router.patch(
+ '/upvote/:id',
+ isUnderMaintenance,
+ auth,
+ commentController.upvote
+)
+
+// DOWNVOTE COMMENT BY COMMENT ID
+router.patch(
+ '/downvote/:id',
+ isUnderMaintenance,
+ auth,
+ commentController.downvote
+)
+
+module.exports = router
diff --git a/app/routes/event.js b/app/routes/event.js
new file mode 100644
index 0000000..0d0dc02
--- /dev/null
+++ b/app/routes/event.js
@@ -0,0 +1,67 @@
+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
+)
+
+// get all the events
+router.get(
+ '/upcoming',
+ isUnderMaintenance,
+ auth,
+ eventController.UpComingEvents
+)
+
+// 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.patch(
+ '/rsvp/:id',
+ isUnderMaintenance,
+ auth,
+ eventController.rsvp
+)
+// delete an event
+router.delete(
+ '/:id',
+ isUnderMaintenance,
+ auth,
+ eventController.deleteEvent
+)
+
+// GET ALL EVENT POSTED BY A USER
+router.get(
+ '/me/all',
+ isUnderMaintenance,
+ auth,
+ eventController.getAllEventByUser
+)
+
+module.exports = router
diff --git a/app/routes/index.js b/app/routes/index.js
index 741fddc..57acc51 100644
--- a/app/routes/index.js
+++ b/app/routes/index.js
@@ -1,9 +1,14 @@
var express = require('express')
var router = express.Router()
const documentationUrl = 'https://documenter.getpostman.com/view/1159934/SWDze1Rp'
+
/* GET home page. */
router.get('/', function (req, res, next) {
res.redirect(documentationUrl)
})
+// router.get('/:shorturl', (req, res, next) => {
+// res.redirect('/shortUrl/' + req.params.shorturl)
+// })
+
module.exports = router
diff --git a/app/routes/notification.js b/app/routes/notification.js
new file mode 100644
index 0000000..58152bd
--- /dev/null
+++ b/app/routes/notification.js
@@ -0,0 +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')
+
+// 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
+)
+
+// GET NOTICATIONS FOR PROPOSALS
+router.get('/proposal/all', notificationController.getProposalNotifications)
+
+module.exports = router
diff --git a/app/routes/organisation.js b/app/routes/organisation.js
new file mode 100644
index 0000000..a69abc9
--- /dev/null
+++ b/app/routes/organisation.js
@@ -0,0 +1,87 @@
+const express = require('express')
+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
+)
+
+// GET ORG DETAILS BY ID
+router.get(
+ '/:id',
+ isUnderMaintenance,
+ auth,
+ OrgController.getOrgDetailsById
+)
+
+// UPDATE ORG DETAILS
+router.patch(
+ '/:id',
+ isUnderMaintenance,
+ uploader.upload.single('image'),
+ auth,
+ OrgController.updateOrgDetails
+)
+
+// DELETE ORG
+router.delete(
+ '/:id',
+ isUnderMaintenance,
+ auth,
+ OrgController.deleteOrg
+)
+
+// ARCHIVE ORG
+router.patch(
+ '/archive/:id',
+ isUnderMaintenance,
+ auth,
+ OrgController.archiveOrg
+)
+
+// TRIGGER MAINTENANCE MODE
+router.patch(
+ '/:id/maintenance',
+ auth,
+ 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
+)
+
+// UPDATE THE ORG SETTINGS
+router.patch(
+ '/:id/settings/update',
+ isUnderMaintenance,
+ auth,
+ OrgController.updateSettings
+)
+
+// REMOVE ADMIN
+router.patch(
+ '/remove/:orgId/:userId',
+ isUnderMaintenance,
+ auth,
+ OrgController.removeAdmin
+)
+
+module.exports = router
diff --git a/app/routes/post.js b/app/routes/post.js
index 1d6d72a..0208282 100644
--- a/app/routes/post.js
+++ b/app/routes/post.js
@@ -1,35 +1,82 @@
+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(
'/',
- userController.create
+ isUnderMaintenance,
+ auth,
+ uploader.upload.single('image'),
+ postController.create
)
-// GET ALL POSTS OF A USER
+// GET ALL POSTS
router.get(
- '/',
- userController.authenticate
+ '/all_posts',
+ isUnderMaintenance,
+ auth,
+ postController.getAllPost
)
-// GET PARTICULAR POST OF A USER
-router.get(
+// UPDATE POST
+router.patch(
'/:id',
- userController.test
+ isUnderMaintenance,
+ auth,
+ uploader.upload.single('image'),
+ postController.updatePost
)
-// UPDATE A TASK
-router.patch(
+// DELETE A POST BY ID
+router.delete(
'/:id',
- userController.test
+ isUnderMaintenance,
+ auth,
+ postController.delete
)
-// DELETE A TASK
-router.delete(
+// GET POST BY ID
+router.get(
'/:id',
- userController.test
+ isUnderMaintenance,
+ auth,
+ postController.getPostById
+)
+
+// UPVOTE POST BY POST ID
+router.patch(
+ '/upvote/:id',
+ isUnderMaintenance,
+ auth,
+ postController.upvote
+)
+
+// GET POST PER USER
+router.get(
+ '/me/all',
+ auth,
+ postController.getPostByUser
+)
+
+// 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/project.js b/app/routes/project.js
new file mode 100644
index 0000000..178a3f6
--- /dev/null
+++ b/app/routes/project.js
@@ -0,0 +1,55 @@
+const express = require('express')
+const projectController = require('../controllers/project')
+const router = express.Router()
+const auth = require('../middleware/auth')
+const isUnderMaintenance = require('../middleware/maintenance')
+
+// ADD PROJECT
+router.post(
+ '/',
+ isUnderMaintenance,
+ auth,
+ projectController.createProject
+)
+
+// GET ALL PROJECTS
+router.get(
+ '/',
+ isUnderMaintenance,
+ auth,
+ projectController.getAllProjects
+)
+
+// GET PROJECT BY ID
+router.get(
+ '/:id',
+ isUnderMaintenance,
+ auth,
+ projectController.getProjectById
+)
+
+// UPDATE PROJECT INFO
+router.patch(
+ '/:id',
+ isUnderMaintenance,
+ auth,
+ projectController.updateProject
+)
+
+// DELETE PROJECT
+router.delete(
+ '/:id',
+ isUnderMaintenance,
+ auth,
+ projectController.deleteProject
+)
+
+// GET PROJECTS CREATED BY A USER
+router.get(
+ '/me/all',
+ isUnderMaintenance,
+ auth,
+ projectController.projectCreatedByUser
+)
+
+module.exports = router
diff --git a/app/routes/proposal.js b/app/routes/proposal.js
new file mode 100644
index 0000000..4a3650c
--- /dev/null
+++ b/app/routes/proposal.js
@@ -0,0 +1,37 @@
+const express = require('express')
+const router = express.Router()
+const proposalController = require('../controllers/proposal')
+
+// Create a new proposal
+router.post('/', proposalController.createProposal)
+
+// Save the content of a proposal
+router.patch('/:proposalId', proposalController.saveProposal)
+
+// Attach file to the given proposal
+router.post('/attach/:proposalId', proposalController.attachFile)
+
+// Get proposals by userId
+router.get('/user/:userId', proposalController.getByUserId)
+
+// get proposal by proposalId
+router.get('/:proposalId', proposalController.getProposalById)
+
+// Deletes a proposal by given proposalId
+router.delete('/', proposalController.deleteById)
+
+// Update proposal state
+router.patch('/change/:proposalId', proposalController.changeState)
+
+// Get all the proposals
+router.post('/all', proposalController.getAllProposals)
+
+// Comment on the given proposala
+router.post('/comment', proposalController.commentOnProposal)
+
+router.post(
+ '/notifications',
+ proposalController.getProposalNotificationsByUser
+)
+
+module.exports = router
diff --git a/app/routes/urlShortner.js b/app/routes/urlShortner.js
new file mode 100644
index 0000000..68f2203
--- /dev/null
+++ b/app/routes/urlShortner.js
@@ -0,0 +1,17 @@
+const express = require('express')
+const router = express.Router()
+const shortnerController = require('../controllers/urlShortner')
+
+// Redirects the ShortURL back to LongURL
+router.get(
+ '/:urlcode',
+ shortnerController.redirect
+)
+
+// Shorten the LongURL and saves in DB
+router.post(
+ '/shorten',
+ shortnerController.shorten
+)
+
+module.exports = router
diff --git a/app/routes/user.js b/app/routes/user.js
index 99b8c87..0c9be6e 100644
--- a/app/routes/user.js
+++ b/app/routes/user.js
@@ -2,16 +2,21 @@ 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
)
// get user profile
router.get(
'/me',
+ isUnderMaintenance,
auth,
userController.userProfile
)
@@ -19,15 +24,110 @@ router.get(
// update user info
router.patch(
'/me',
+ isUnderMaintenance,
auth,
userController.userProfileUpdate
)
+// user forgot password request
+router.patch(
+ '/password_reset/request',
+ isUnderMaintenance,
+ userController.forgotPasswordRequest
+)
+
+// update password
+router.patch(
+ '/password_reset/:token',
+ isUnderMaintenance,
+ userController.updatePassword
+)
+
+// get invite link (for sender)
+router.get(
+ '/invite',
+ isUnderMaintenance,
+ auth,
+ userController.getInviteLink
+)
+
+// 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
)
+// LOGOUT USER
+router.post(
+ '/logout',
+ auth,
+ userController.logout
+)
+
+// follow the user
+router.patch(
+ '/follow',
+ isUnderMaintenance,
+ auth,
+ userController.addFollowing,
+ userController.addFollower
+)
+
+// unFollow the user
+router.patch(
+ '/unfollow',
+ isUnderMaintenance,
+ auth,
+ userController.removeFollowing,
+ userController.removeFollower
+)
+
+// BLOCK THE USER
+router.patch(
+ '/block/:id',
+ isUnderMaintenance,
+ auth,
+ userController.blockUser
+)
+
+// UNBLOCK THE USER
+router.patch(
+ '/unblock/:id',
+ isUnderMaintenance,
+ auth,
+ userController.unBlockUser
+)
+
+// GET PERSONAL OVERVIEW
+router.get(
+ '/overview',
+ isUnderMaintenance,
+ auth,
+ userController.getPersonalOverview
+)
+
+// REMOVE USER
+router.patch(
+ '/remove/:id',
+ isUnderMaintenance,
+ auth,
+ userController.removeUser
+)
+
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/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..b7dbebe
--- /dev/null
+++ b/app/utils/notificationTags.js
@@ -0,0 +1,11 @@
+const 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/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/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/proposal-notif-helper.js b/app/utils/proposal-notif-helper.js
new file mode 100644
index 0000000..bcbcb33
--- /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/app/utils/status-codes.js b/app/utils/status-codes.js
deleted file mode 100644
index e69de29..0000000
diff --git a/app/utils/uploader.js b/app/utils/uploader.js
new file mode 100644
index 0000000..239abc1
--- /dev/null
+++ b/app/utils/uploader.js
@@ -0,0 +1,41 @@
+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)
+ db.image.data = img
+ db.image.contentType = 'image/png'
+}
diff --git a/bin/www b/bin/www
old mode 100755
new mode 100644
index 2b4e4ed..f03de80
--- 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/config/fileHandlingConstants.js b/config/fileHandlingConstants.js
new file mode 100644
index 0000000..d760cb5
--- /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 73bd299..25aa95b 100644
--- a/config/mongoose.js
+++ b/config/mongoose.js
@@ -1,8 +1,15 @@
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')
+ })
+ .catch((err) => {
+ console.log('mongodb connection error', err)
+ })
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:
diff --git a/package-lock.json b/package-lock.json
index 4547b5b..fd1b330 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -430,6 +430,33 @@
"@types/yargs": "^13.0.0"
}
},
+ "@sendgrid/client": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@sendgrid/client/-/client-7.0.0.tgz",
+ "integrity": "sha512-Wo3Cs8XJ6F2UFYgZJ3WmRVj7y/cTgyBfkwCOxJKml3DnsAbtc0RozkBOvvIAyBKNF1LE6hqQPgckspg59BO6Vg==",
+ "requires": {
+ "@sendgrid/helpers": "^7.0.0",
+ "axios": "^0.19.2"
+ }
+ },
+ "@sendgrid/helpers": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@sendgrid/helpers/-/helpers-7.0.0.tgz",
+ "integrity": "sha512-sAoZTH3WBURV5Gep6Y9AhsuLQ1KW4TEzn1MKKBhSGFnYTWBOc562f+ee1j9XI0/7pdI/I2hUWuiw0vhY3joBDA==",
+ "requires": {
+ "chalk": "^2.0.1",
+ "deepmerge": "^4.2.2"
+ }
+ },
+ "@sendgrid/mail": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/@sendgrid/mail/-/mail-7.0.0.tgz",
+ "integrity": "sha512-HlRath7i1imzvB87HvIPJDsv81MuoF4H4OgVMvQO4MTclHe6oJaORJlHFr0+82sCEdQqEOH33rBrEb4j2FfxMA==",
+ "requires": {
+ "@sendgrid/client": "^7.0.0",
+ "@sendgrid/helpers": "^7.0.0"
+ }
+ },
"@types/babel__core": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz",
@@ -573,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",
@@ -651,7 +683,6 @@
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
- "dev": true,
"requires": {
"color-convert": "^1.9.0"
}
@@ -677,6 +708,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",
@@ -751,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",
@@ -787,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",
@@ -802,6 +842,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",
@@ -814,6 +882,14 @@
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
"dev": true
},
+ "axios": {
+ "version": "0.19.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz",
+ "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==",
+ "requires": {
+ "follow-redirects": "1.5.10"
+ }
+ },
"babel-jest": {
"version": "24.9.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz",
@@ -860,6 +936,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",
@@ -920,6 +1001,21 @@
}
}
},
+ "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=="
+ },
"basic-auth": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
@@ -946,12 +1042,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",
@@ -1121,6 +1230,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",
@@ -1129,8 +1248,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",
@@ -1154,6 +1304,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",
@@ -1191,7 +1346,6 @@
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
- "dev": true,
"requires": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
@@ -1362,7 +1516,6 @@
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
- "dev": true,
"requires": {
"color-name": "1.1.3"
}
@@ -1370,8 +1523,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
- "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
- "dev": true
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"combined-stream": {
"version": "1.0.8",
@@ -1388,17 +1540,37 @@
"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",
"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",
@@ -1479,6 +1651,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",
@@ -1499,6 +1680,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",
@@ -1584,6 +1770,11 @@
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
},
+ "deepmerge": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
+ "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
+ },
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@@ -1666,6 +1857,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",
@@ -1758,6 +1981,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",
@@ -1844,8 +2153,7 @@
"escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
- "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
- "dev": true
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
"escodegen": {
"version": "1.12.0",
@@ -2351,6 +2659,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",
@@ -2740,6 +3053,24 @@
"integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
"dev": true
},
+ "follow-redirects": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+ "requires": {
+ "debug": "=3.1.0"
+ },
+ "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"
+ }
+ }
+ }
+ },
"for-in": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz",
@@ -2764,9 +3095,9 @@
}
},
"formidable": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
- "integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz",
+ "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==",
"dev": true
},
"forwarded": {
@@ -3528,11 +3859,30 @@
"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",
- "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
- "dev": true
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
},
"has-symbols": {
"version": "1.0.0",
@@ -3614,6 +3964,11 @@
"sshpk": "^1.7.0"
}
},
+ "http-status-codes": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-1.4.0.tgz",
+ "integrity": "sha512-JrT3ua+WgH8zBD3HEJYbeEgnuQaAnUeRRko/YojPAJjGmIfGD3KPU/asLdsLwKjfxOmQe5nXMQ0pt/7MyapVbQ=="
+ },
"https-proxy-agent": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz",
@@ -3649,6 +4004,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",
@@ -3709,6 +4069,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",
@@ -4749,6 +5114,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",
@@ -5320,6 +5690,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",
@@ -5564,6 +5949,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",
@@ -5802,6 +6192,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",
@@ -6005,6 +6411,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",
@@ -6707,6 +7118,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",
@@ -6861,6 +7411,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",
@@ -7000,7 +7555,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
"requires": {
"has-flag": "^3.0.0"
}
@@ -7142,6 +7696,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",
@@ -7279,6 +7838,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",
@@ -7426,6 +7990,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",
@@ -7728,6 +8308,30 @@
"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="
+ },
+ "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",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
+ },
"y18n": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
@@ -7808,6 +8412,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 6e6cf30..dd8f1d4 100644
--- a/package.json
+++ b/package.json
@@ -6,25 +6,34 @@
"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",
+ "cors": "^2.8.5",
+ "crypto": "^1.0.1",
"debug": "~2.6.9",
"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",
+ "multer": "^1.4.2",
+ "socket.io": "^2.3.0",
"validator": "^10.11.0"
},
"jest": {
"testEnvironment": "node",
"coverageDirectory": "./coverage/",
- "collectCoverage": true
+ "collectCoverage": true,
+ "testTimeout": 30000
},
"devDependencies": {
"codecov": "^3.6.1",
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
new file mode 100644
index 0000000..cb8e64b
--- /dev/null
+++ b/test/comment.test.js
@@ -0,0 +1,313 @@
+const app = require('../app').app
+const mongoose = require('mongoose')
+const jwt = require('jsonwebtoken')
+const HttpStatus = require('http-status-codes')
+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()
+const testCommentId = new mongoose.Types.ObjectId()
+let token = ''
+
+const demoComment = {
+ content: 'test comment content',
+ userId: testUserId,
+ postId: testPostId,
+ votes: {
+ upVotes: {
+ user: []
+ },
+ downVotes: {
+ user: []
+ }
+ }
+}
+
+const demoPost = {
+ _id: testPostId,
+ content: 'test post content',
+ userId: testUserId,
+ votes: {
+ upVotes: {
+ user: []
+ },
+ downVotes: {
+ user: []
+ }
+ }
+}
+
+const updateComment = {
+ content: 'updated comment content'
+}
+
+const upvoteComment = {
+ content: 'test comment content',
+ userId: testUserId,
+ votes: {
+ upVotes: {
+ user: [
+ testUserId
+ ]
+ },
+ downVotes: {
+ user: []
+ }
+ }
+}
+
+const downvoteComment = {
+ content: 'test comment content',
+ userId: testUserId,
+ votes: {
+ upVotes: {
+ user: []
+ },
+ downVotes: {
+ user: [
+ testUserId
+ ]
+ }
+ }
+}
+
+const testComment = {
+ _id: testCommentId,
+ ...demoComment
+}
+
+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,
+ 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)
+ })
+ 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/${testPostId}`)
+ .set('Authorization', `Bearer ${token}`)
+ .send(demoComment)
+ .expect(HttpStatus.CREATED)
+
+ // 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: {
+ user: demoComment.votes.upVotes.user
+ },
+ downVotes: {
+ user: demoComment.votes.downVotes.user
+ }
+ }
+ }
+ })
+ 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(HttpStatus.OK)
+ done()
+})
+
+/**
+ * Testing post deletion
+ */
+
+test('Should delete comment', async (done) => {
+ await request(app)
+ .delete(`/comment/${testCommentId}`)
+ .set('Authorization', `Bearer ${token}`)
+ .send()
+ .expect(HttpStatus.OK)
+
+ // 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(HttpStatus.OK)
+ done()
+})
+
+/**
+ * Testing upvote post
+ */
+
+test('Should upvote the comment', async (done) => {
+ const response = await request(app)
+ .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,
+ userId: `${userId}`,
+ postId: `${postId}`,
+ votes: {
+ upVotes: {
+ user: response.body.comment.votes.upVotes.user
+ },
+ downVotes: {
+ user: response.body.comment.votes.downVotes.user
+ }
+ }
+ }
+ })
+ done()
+})
+
+/**
+ * Testing downvote post
+ */
+
+test('Should downvote the post', async (done) => {
+ const response = await request(app)
+ .patch(`/comment/downVote/${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: downvoteComment.content,
+ userId: `${userId}`,
+ postId: `${postId}`,
+ votes: {
+ upVotes: {
+ user: response.body.comment.votes.upVotes.user
+ },
+ downVotes: {
+ user: response.body.comment.votes.downVotes.user
+ }
+ }
+ }
+ })
+ 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 () => {
+ // avoid jest open handle error
+ await new Promise((resolve) => setTimeout(() => resolve(), 500))
+ // 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()
+})
diff --git a/test/event.test.js b/test/event.test.js
new file mode 100644
index 0000000..672168a
--- /dev/null
+++ b/test/event.test.js
@@ -0,0 +1,317 @@
+const app = require('../app').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 randomDigit = Math.floor(Math.random() * 90 + 10)
+
+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: `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 + 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
+ */
+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 (done) => {
+ await Event.deleteMany()
+ await new Event(testEvent).save()
+ await User.deleteMany()
+ await new User(testUser).save()
+ done()
+})
+
+test('Should signup new user', async (done) => {
+ 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
+ }
+ }
+ }
+ })
+ expect(user.password).not.toBe('abc12345') // to check hashing
+ done()
+})
+
+/** Testing user login */
+test('Login existing user', async (done) => {
+ 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)
+ done()
+})
+/**
+ * Testing event creation
+ */
+test('Should create new event', async (done) => {
+ 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
+ }
+ })
+ done()
+})
+
+/**
+ * Testing event updation
+ */
+
+test('Should update event', async (done) => {
+ 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()
+ done()
+})
+
+/**
+ * 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}`)
+ .send(demoRsvp)
+ .expect(HttpStatus.OK)
+ const rsvpData = await Event.findById(response.body.rsvpData._id)
+ expect(rsvpData).not.toBeNull()
+ done()
+})
+
+/**
+ * Testing for event deletion
+ */
+
+test('Should delete event', async (done) => {
+ 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()
+ done()
+})
+
+/**
+ * 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()
+})
+
+/**
+ * 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:
+ * 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
+ await Event.deleteMany()
+ // Closing the DB connection allows Jest to exit successfully.
+ await mongoose.connection.close()
+})
diff --git a/test/organisation.test.js b/test/organisation.test.js
new file mode 100644
index 0000000..12f966b
--- /dev/null
+++ b/test/organisation.test.js
@@ -0,0 +1,185 @@
+const app = require('../app').app
+const mongoose = require('mongoose')
+const request = require('supertest')
+const HttpStatus = require('http-status-codes')
+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(HttpStatus.CREATED)
+ 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(HttpStatus.OK)
+ 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(HttpStatus.OK)
+ 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(HttpStatus.OK)
+
+ /** Check if deleted or not **/
+ const org = await Organization.findById(orgId)
+ expect(org).toBeNull()
+ done()
+ })
+})
+
+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
+ await Organization.deleteMany()
+ // Closing the DB connection allows Jest to exit successfully.
+ await mongoose.connection.close()
+})
diff --git a/test/post.test.js b/test/post.test.js
new file mode 100644
index 0000000..3095604
--- /dev/null
+++ b/test/post.test.js
@@ -0,0 +1,254 @@
+const app = require('../app').app
+const mongoose = require('mongoose')
+const jwt = require('jsonwebtoken')
+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 = ''
+const demoPost = {
+ content: 'test post content',
+ userId: testUserId,
+ votes: {
+ upVotes: {
+ user: []
+ },
+ downVotes: {
+ user: []
+ }
+ }
+}
+
+const updatePost = {
+ content: 'updated post content'
+}
+
+const upvotePost = {
+ content: 'test post content',
+ userId: testUserId,
+ votes: {
+ upVotes: {
+ user: [
+ testUserId
+ ]
+ },
+ downVotes: {
+ user: []
+ }
+ }
+}
+
+const testPostId = new mongoose.Types.ObjectId()
+const testPost = {
+ _id: testPostId,
+ ...demoPost
+}
+
+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 Post.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 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(HttpStatus.CREATED)
+
+ // 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: {
+ user: response.body.post.votes.upVotes.user
+ }
+ }
+ }
+ })
+ done()
+})
+
+/**
+ * Testing post deletion
+ */
+
+test('Should delete post', async (done) => {
+ await request(app)
+ .delete(`/post/${testPostId}`)
+ .set('Authorization', `Bearer ${token}`)
+ .send()
+ .expect(HttpStatus.OK)
+
+ // Assert that post was deleted
+ const post = await Post.findById(testPostId)
+ expect(post).toBeNull()
+ done()
+})
+
+/**
+ * Testing GET post by id
+ */
+
+test('Should get single post by id', async (done) => {
+ await request(app)
+ .get(`/post/${testPostId}`)
+ .set('Authorization', `Bearer ${token}`)
+ .send()
+ .expect(HttpStatus.OK)
+ done()
+})
+
+/**
+ * Testing upvote post
+ */
+
+test('Should upvote the post', async (done) => {
+ const response = await request(app)
+ .patch(`/post/upvote/${testPostId}`)
+ .set('Authorization', `Bearer ${token}`)
+ .send()
+ .expect(HttpStatus.OK)
+
+ const userId = response.body.post.userId
+
+ expect(response.body).toMatchObject({
+ post: {
+ content: upvotePost.content,
+ userId: `${userId}`,
+ votes: {
+ upVotes: {
+ user: response.body.post.votes.upVotes.user
+ }
+ }
+ }
+ })
+ 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(HttpStatus.OK)
+ 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:
+ * 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
+ await Post.deleteMany()
+ // Closing the DB connection allows Jest to exit successfully.
+ await mongoose.connection.close()
+})
diff --git a/test/project.test.js b/test/project.test.js
new file mode 100644
index 0000000..dd22655
--- /dev/null
+++ b/test/project.test.js
@@ -0,0 +1,220 @@
+const app = require('../app').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 () => {
+ // avoid jest open handle error
+ await new Promise((resolve) => setTimeout(() => resolve(), 500))
+ // 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/proposal.test.js b/test/proposal.test.js
new file mode 100644
index 0000000..1098d9f
--- /dev/null
+++ b/test/proposal.test.js
@@ -0,0 +1,197 @@
+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)
+
+ // confirm that the proposal was deleted
+ const proposal = await Proposal.findById(testProposalId)
+ expect(proposal).toBeNull()
+ 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()
+})
diff --git a/test/url.test.js b/test/url.test.js
new file mode 100644
index 0000000..1105634
--- /dev/null
+++ b/test/url.test.js
@@ -0,0 +1,67 @@
+const app = require('../app').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 () => {
+ // 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.
+ await mongoose.connection.close()
+})
diff --git a/test/user.test.js b/test/user.test.js
index 0f49355..04534ec 100644
--- a/test/user.test.js
+++ b/test/user.test.js
@@ -1,8 +1,12 @@
-const app = require('../app')
+const app = require('../app').app
const mongoose = require('mongoose')
const jwt = require('jsonwebtoken')
const request = require('supertest')
const User = require('../app/models/User')
+const HttpStatus = require('http-status-codes')
+let token = ''
+let passwordToken = ''
+let inviteLink = ''
const demoUser = {
name: {
@@ -41,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)
+ }, '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, () => {
@@ -71,6 +101,7 @@ beforeAll(async (done) => {
beforeEach(async () => {
await User.deleteMany()
await new User(testUser).save()
+ await new User(testFollowUser).save()
})
/**
@@ -80,7 +111,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,8 +153,9 @@ test('Login existing user', async () => {
email: testUser.email,
password: testUser.password
})
- .expect(200)
+ .expect(HttpStatus.OK)
+ token = response.body.token
const user = await User.findById(testUserId)
expect(response.body.token).toBe(user.tokens[1].token)
})
@@ -133,7 +165,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 +174,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 +182,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 +191,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 +203,125 @@ test('Should not delete profile of unauthenticated user', async () => {
await request(app)
.delete('/user/me')
.send()
- .expect(401)
+ .expect(HttpStatus.UNAUTHORIZED)
+})
+
+/** 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 (done) => {
+ await request(app)
+ .get(`/user/activate/${token}`)
+ .send({
+ token: `${token}`
+ })
+ .expect(HttpStatus.OK)
+ done()
+})
+
+/* 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)
+})
+
+/* 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()
+})
+
+/* 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()
})
/**
@@ -180,6 +330,8 @@ test('Should not delete profile of unauthenticated user', async () => {
* 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
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+ 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!
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+ |
+
+
+ |
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+