Skip to content

Commit cf9a8ab

Browse files
authored
Notification system using socket.io, remove user/admin, case-insensitive functionality (#135)
* initial working mechanisam for notification system * modified backend to fix inconsistencies
1 parent ad28cb7 commit cf9a8ab

28 files changed

+790
-57
lines changed

.env.dev

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ NODE_ENV="development"
33
JWT_SECRET="thisismysupersecrettokenjustkidding"
44
DATABASE_URL="mongodb://localhost:27017/donut-development"
55
SENDGRID_API_KEY = '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'
6+
SOCKET_PORT = 8810

.env.test

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ PORT=3000
22
NODE_ENV=testing
33
JWT_SECRET=thisismysupersecrettokenjustkidding
44
DATABASE_URL=mongodb+srv://donut-admin:[email protected]/donut-testing?retryWrites=true&w=majority
5-
SENDGRID_API_KEY = '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'
5+
SENDGRID_API_KEY = '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'
6+
SOCKET_PORT = 8810

app.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const logger = require('morgan')
44
const cookieParser = require('cookie-parser')
55
const createError = require('http-errors')
66
const path = require('path')
7+
const socket = require('socket.io')
78

89
const indexRouter = require('./app/routes/index')
910
const authRouter = require('./app/routes/auth')
@@ -14,8 +15,20 @@ const shortUrlRouter = require('./app/routes/urlShortner')
1415
const organizationRouter = require('./app/routes/organisation')
1516
const commentRouter = require('./app/routes/comment')
1617
const projectRouter = require('./app/routes/project')
18+
const notificationRouter = require('./app/routes/notification')
1719

1820
const app = express()
21+
const server = require('http').Server(app)
22+
23+
server.listen(process.env.SOCKET_PORT || 8810)
24+
// WARNING: app.listen(80) will NOT work here!
25+
26+
const io = socket.listen(server)
27+
let count = 0
28+
io.on('connection', (socket) => {
29+
console.log('socket connected count ', count++)
30+
io.emit('user connected')
31+
})
1932

2033
// view engine setup
2134
app.set('views', path.join(__dirname, 'views'))
@@ -26,7 +39,12 @@ app.use(express.json())
2639
app.use(express.urlencoded({ extended: false }))
2740
app.use(cookieParser())
2841
app.use(express.static(path.join(__dirname, 'public')))
42+
app.use((req, res, next) => {
43+
req.io = io
44+
next()
45+
})
2946

47+
app.use('/notification', notificationRouter)
3048
app.use('/', indexRouter)
3149
app.use('/auth', authRouter)
3250
app.use('/user', usersRouter)
@@ -53,4 +71,4 @@ app.use(function (err, req, res, next) {
5371
res.render('error')
5472
})
5573

56-
module.exports = app
74+
module.exports = { app, io }

app/controllers/event.js

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,31 @@ const HANDLER = require('../utils/response-helper')
33
const HttpStatus = require('http-status-codes')
44
const permission = require('../utils/permission')
55
const helper = require('../utils/paginate')
6+
const notificationHelper = require('../utils/notif-helper')
7+
const notification = {
8+
heading: '',
9+
content: '',
10+
tag: ''
11+
}
612

713
module.exports = {
814
createEvent: async (req, res, next) => {
915
const event = new Event(req.body)
1016
try {
1117
event.createdBy = req.user._id
1218
await event.save()
19+
req.io.emit('new event created', { data: event.eventName })
20+
notification.heading = 'New Event!'
21+
notification.content = `${event.eventName} is added!`
22+
notification.tag = 'New!'
23+
notificationHelper.addToNotificationForAll(req, res, notification, next)
1324
res.status(HttpStatus.CREATED).json({ event: event })
1425
} catch (error) {
1526
res.status(HttpStatus.BAD_REQUEST).json({ error: error })
1627
}
1728
},
1829

19-
updateEvent: async (req, res) => {
30+
updateEvent: async (req, res, next) => {
2031
const { id } = req.params
2132
const updates = Object.keys(req.body)
2233
try {
@@ -29,15 +40,21 @@ module.exports = {
2940
event[update] = req.body[update]
3041
})
3142
await event.save()
43+
req.io.emit('event update', { data: `Event: ${event.eventName} is updated!` })
44+
notification.heading = 'Event update!'
45+
notification.content = `${event.eventName} is updated!`
46+
notification.tag = 'Update'
47+
notificationHelper.addToNotificationForAll(req, res, notification, next)
3248
res.status(HttpStatus.OK).json({ event: event })
3349
} catch (error) {
3450
HANDLER.handleError(res, error)
3551
}
3652
},
3753

38-
rsvp: async (req, res) => {
54+
rsvp: async (req, res, next) => {
3955
const { yes, no, maybe } = req.body
4056
const { id } = req.params
57+
notification.tag = 'RSVP'
4158
try {
4259
const data = await Event.findById(id)
4360
if (!data) {
@@ -47,14 +64,23 @@ module.exports = {
4764
if (data.rsvpMaybe.includes(req.user.id) ||
4865
data.rsvpNo.includes(req.user.id) ||
4966
data.rsvpYes.includes(req.user.id)) {
50-
return res.status(HttpStatus.OK).json({ msg: 'You have already done the rsvp' })
67+
req.io.emit('already rsvp', { data: 'You have already done the rsvp' })
68+
notification.heading = 'Already rsvp!'
69+
notification.content = 'You have already done the rsvp'
70+
notificationHelper.addToNotificationForUser(req.user._id, res, notification, next)
71+
res.status(HttpStatus.OK).json({ msg: 'You have already done the rsvp' })
72+
return
5173
}
5274
const event = await Event.findByIdAndUpdate(id)
5375
if (yes) {
5476
try {
5577
event.rsvpYes.push(req.user.id)
5678
await event.save()
57-
return res.status(HttpStatus.OK).json({ rsvpData: data })
79+
req.io.emit('rsvp done', { data: 'RSVP successfully done!' })
80+
notification.heading = 'RSVP done!'
81+
notification.content = 'RSVP successfully done!'
82+
notificationHelper.addToNotificationForUser(req.user._id, res, notification, next)
83+
res.status(HttpStatus.OK).json({ rsvpData: data })
5884
} catch (error) {
5985
return res.status(HttpStatus.BAD_REQUEST).json({ error: error })
6086
}
@@ -63,7 +89,11 @@ module.exports = {
6389
try {
6490
event.rsvpNo.push(req.user.id)
6591
await event.save()
66-
return res.status(HttpStatus.OK).json({ rsvpData: data })
92+
req.io.emit('rsvp done', { data: 'RSVP successfully done!' })
93+
notification.heading = 'RSVP done!'
94+
notification.content = 'RSVP successfully done!'
95+
notificationHelper.addToNotificationForUser(req.user._id, res, notification, next)
96+
res.status(HttpStatus.OK).json({ rsvpData: data })
6797
} catch (error) {
6898
return res.status(HttpStatus.BAD_REQUEST).json({ error: error })
6999
}
@@ -72,7 +102,11 @@ module.exports = {
72102
try {
73103
event.rsvpMaybe.push(req.user.id)
74104
await event.save()
75-
return res.status(HttpStatus.OK).json({ rsvpData: data })
105+
req.io.emit('rsvp done', { data: 'RSVP successfully done!' })
106+
notification.heading = 'RSVP done!'
107+
notification.content = 'RSVP successfully done!'
108+
notificationHelper.addToNotificationForUser(req.user._id, res, notification, next)
109+
res.status(HttpStatus.OK).json({ rsvpData: data })
76110
} catch (error) {
77111
return res.status(HttpStatus.BAD_REQUEST).json({ error: error })
78112
}
@@ -98,12 +132,10 @@ module.exports = {
98132
GetAllEvent: async (req, res, next) => {
99133
try {
100134
const EventData = await Event.find({}, {}, helper.paginate(req))
135+
.populate('createdBy', ['name.firstName', 'name.lastName', '_id', 'isAdmin'])
101136
.sort({ eventDate: -1 })
102137
.lean()
103-
if (!EventData) {
104-
return res.status(HttpStatus.NOT_FOUND).json({ error: 'No such Event is available!' })
105-
}
106-
return res.status(HttpStatus.OK).json({ Event: EventData })
138+
return res.status(HttpStatus.OK).json({ events: EventData })
107139
} catch (error) {
108140
HANDLER.handleError(res, error)
109141
}
@@ -118,6 +150,11 @@ module.exports = {
118150
}
119151
if (permission.check(req, res, deleteEvent.createdBy)) {
120152
await Event.findByIdAndRemove(id)
153+
req.io.emit('event deleted', { data: deleteEvent.eventName })
154+
notification.heading = 'Event deleted!'
155+
notification.content = `Event ${deleteEvent.eventName} is deleted!`
156+
notification.tag = 'Deleted'
157+
notificationHelper.addToNotificationForAll(req, res, notification, next)
121158
return res.status(HttpStatus.OK).json({ deleteEvent: deleteEvent, message: 'Deleted the event' })
122159
}
123160
return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'Not permitted!' })
@@ -132,9 +169,6 @@ module.exports = {
132169
.sort({ eventDate: -1 })
133170
.exec()
134171
console.log('Upcoming events ', events)
135-
if (events.length === 0) {
136-
return res.status(HttpStatus.OK).json({ msg: 'No Upcoming events exists!' })
137-
}
138172
return res.status(HttpStatus.OK).json({ events })
139173
} catch (error) {
140174
HANDLER.handleError(res, next)
@@ -147,9 +181,6 @@ module.exports = {
147181
.sort({ eventDate: -1 })
148182
.populate('createdBy', '_id name.firstName name.lastName')
149183
.exec()
150-
if (events.length === 0) {
151-
return res.status(HttpStatus.OK).json({ msg: 'No events posted by user!' })
152-
}
153184
return res.status(HttpStatus.OK).json({ events })
154185
} catch (error) {
155186
HANDLER.handleError(res, error)

app/controllers/notification.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const HANDLER = require('../utils/response-helper')
2+
const HttpStatus = require('http-status-codes')
3+
const Notifications = require('../models/Notifications')
4+
const helper = require('../utils/paginate')
5+
const User = require('../models/User')
6+
7+
module.exports = {
8+
// GET ALL THE NOTIFICATIONS FOR ALL
9+
getOrgNotifications: async (req, res, next) => {
10+
try {
11+
const notifications = await Notifications.find({}, {}, helper.paginate(req))
12+
.lean()
13+
.sort({ createdAt: -1 })
14+
.exec()
15+
return res.status(HttpStatus.OK).json({ notifications })
16+
} catch (error) {
17+
HANDLER.handleError(res, error)
18+
}
19+
},
20+
// GET LOGGED IN USER NOTIFICATIONS
21+
getUserNotification: async (req, res, next) => {
22+
const userId = req.user._id
23+
try {
24+
const user = await User.findById(userId)
25+
if (!user) {
26+
return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'No such user exists!' })
27+
}
28+
// get all notifications of existing user
29+
const notifications = user.notifications
30+
if (notifications.length === 0) {
31+
return res.status(HttpStatus.OK).json({ msg: 'No new notifications!' })
32+
}
33+
return res.status(HttpStatus.OK).json({ notifications })
34+
} catch (error) {
35+
HANDLER.handleError(res, error)
36+
}
37+
}
38+
}

app/controllers/organization.js

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,17 @@ const Organization = require('../models/Organisation')
22
const HANDLER = require('../utils/response-helper')
33
const HttpStatus = require('http-status-codes')
44
const helper = require('../utils/uploader')
5+
const notificationHelper = require('../utils/notif-helper')
56
const User = require('../models/User')
67
const Project = require('../models/Project')
78
const Event = require('../models/Event')
89
const permission = require('../utils/permission')
10+
const TAGS = require('../utils/notificationTags')
11+
const notification = {
12+
heading: '',
13+
content: '',
14+
tag: ''
15+
}
916

1017
module.exports = {
1118
createOrganization: async (req, res, next) => {
@@ -15,7 +22,12 @@ module.exports = {
1522
}
1623
try {
1724
await org.save()
18-
res.status(HttpStatus.CREATED).json({ org })
25+
req.io.emit('new org created', { data: org.name })
26+
notification.heading = 'New org!'
27+
notification.content = `${org.name} is created!`
28+
notification.tag = TAGS.NEW
29+
notificationHelper.addToNotificationForAll(req, res, notification, next)
30+
return res.status(HttpStatus.CREATED).json({ org })
1931
} catch (error) {
2032
HANDLER.handleError(res, error)
2133
}
@@ -80,6 +92,11 @@ module.exports = {
8092
if (!permission.check(req, res)) {
8193
return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have the permission!' })
8294
}
95+
req.io.emit('org deleted', { data: org.name })
96+
notification.heading = 'Org deleted!'
97+
notification.content = `${org.name} is deleted!`
98+
notification.tag = TAGS.DELETE
99+
notificationHelper.addToNotificationForAll(req, res, notification, next)
83100
return res.status(HttpStatus.OK).json({ organization: org })
84101
} catch (error) {
85102
HANDLER.handleError(res, error)
@@ -111,15 +128,25 @@ module.exports = {
111128
}
112129
// if user is admin or not
113130
const adminIds = organization.adminInfo.adminId
114-
const isAdmin = adminIds.indexOf(req.user.id)
131+
const isAdmin = adminIds.indexOf(req.user.id) || req.user.isAdmin
115132
// user is admin then perform operation
116-
if (isAdmin !== -1) {
133+
if (isAdmin !== -1 || req.user.isAdmin) {
117134
// toggle maintenance mode
118135
organization.isMaintenance = !organization.isMaintenance
119136
await organization.save()
137+
notification.tag = TAGS.MAINTENANCE
138+
120139
if (organization.isMaintenance) {
140+
req.io.emit('org under maintenance', { data: organization.name })
141+
notification.heading = 'Maintenance mode on!'
142+
notification.content = `${organization.name} is kept under maintenance!`
143+
notificationHelper.addToNotificationForAll(req, res, notification, next)
121144
return res.status(HttpStatus.OK).json({ msg: 'Organization is kept under the maintenance!!' })
122145
}
146+
147+
req.io.emit('org revoked maintenance', { data: organization.name })
148+
notification.heading = 'Maintenance mode off!'
149+
notification.content = `${organization.name} is revoked from maintenance!`
123150
return res.status(HttpStatus.OK).json({ msg: 'Organization is recovered from maintenance!!' })
124151
} else {
125152
return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You don\'t have access to triggerMaintenance!' })
@@ -191,9 +218,15 @@ module.exports = {
191218
try {
192219
const { search } = req.query
193220
if (search) {
194-
const regex = search.split(' ')
195-
const member = await User.find({ $or: [{ 'name.firstName': regex }, { 'name.lastName': regex }] })
196-
.select('name email isAdmin info.about.designation')
221+
const queryTerm = search.split(' ')
222+
const regex = new RegExp('^' + queryTerm + '$', 'i')
223+
const member = await User.find({
224+
$or: [
225+
{ 'name.firstName': { $regex: regex } },
226+
{ 'name.lastName': { $regex: regex } }
227+
]
228+
})
229+
.select('name email isAdmin info.about.designation isRemoved')
197230
.lean()
198231
.sort({ createdAt: -1 })
199232
.exec()
@@ -203,7 +236,7 @@ module.exports = {
203236
return res.status(HttpStatus.OK).json({ member })
204237
} else {
205238
const members = await User.find({})
206-
.select('name email isAdmin info.about.designation')
239+
.select('name email isAdmin info.about.designation isRemoved')
207240
.lean()
208241
.sort({ createdAt: -1 })
209242
.exec()
@@ -215,5 +248,37 @@ module.exports = {
215248
} catch (error) {
216249
HANDLER.handleError(res, error)
217250
}
251+
},
252+
// REMOVE ADMIN
253+
removeAdmin: async (req, res, next) => {
254+
try {
255+
const { userId, orgId } = req.params
256+
const org = await Organization.findById(orgId)
257+
if (!org) {
258+
return res.status(HttpStatus.NOT_FOUND).json({ msg: 'No org exists!' })
259+
}
260+
// only permitted for admins
261+
if (!req.user.isAdmin) {
262+
return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'You are not permitted!' })
263+
}
264+
// console.log('Permitted to removeAdmin')
265+
// REMOVE ADMINS FROM ADMINS LIST
266+
const admins = org.adminInfo.adminId
267+
console.log('adminIds ', admins)
268+
const removableIndex = admins.indexOf(userId)
269+
if (removableIndex === -1) {
270+
return res.status(HttpStatus.BAD_REQUEST).json({ msg: 'User is not an admin!' })
271+
}
272+
// user is admin so remove
273+
org.adminInfo.adminId.splice(removableIndex, 1)
274+
await org.save()
275+
// also make isAdmin false
276+
const user = await User.findById(userId)
277+
user.isAdmin = false
278+
await user.save()
279+
return res.status(HttpStatus.OK).json({ org })
280+
} catch (error) {
281+
HANDLER.handleError(res, error)
282+
}
218283
}
219284
}

0 commit comments

Comments
 (0)