Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
e658c58
Added Notification schema
Sebastian-Webster Nov 24, 2023
55f04f8
Added temp/clearnotifications API
Sebastian-Webster Nov 24, 2023
8ed9ad4
Added tests for temp/clearnotifications
Sebastian-Webster Nov 24, 2023
ef0322e
Fixed bug in tests
Sebastian-Webster Nov 24, 2023
6eddbab
Changed order in test
Sebastian-Webster Nov 25, 2023
8048984
Change userId test selection process
Sebastian-Webster Nov 25, 2023
2117125
Added temp/deletenotification API
Sebastian-Webster Nov 25, 2023
91ed4b7
Added first temp/deletenotification test
Sebastian-Webster Nov 25, 2023
7689bce
Added temp/deletenotification type tests
Sebastian-Webster Nov 25, 2023
cce588c
Added temp/deletenotification not found tests
Sebastian-Webster Nov 25, 2023
e55cf34
Fixed bug in tests
Sebastian-Webster Nov 25, 2023
28f639a
Fixed bug in tests
Sebastian-Webster Nov 25, 2023
4ea682a
Fixed bug in tests
Sebastian-Webster Nov 25, 2023
28c69dd
Fixed bug in tests
Sebastian-Webster Nov 25, 2023
175092c
Added skeleton getnotifications API
Sebastian-Webster Dec 12, 2023
f52ac28
Added more to temp/getnotifications
Sebastian-Webster Dec 17, 2023
0dfd4fd
Finished temp/getnotifications API (still need tests)
Sebastian-Webster Dec 17, 2023
7b3439b
Fixed server crashing issue
Sebastian-Webster Dec 17, 2023
f8a318d
Merge branch 'main' into 321-add-notification-collection-and-apis-to-…
Sebastian-Webster Dec 19, 2023
5a8742f
Stringify notification id
Sebastian-Webster Dec 19, 2023
e8a2a24
Add lean option to temp/getnotifications
Sebastian-Webster Dec 21, 2023
e000918
Add getnotifications test skeleton file
Sebastian-Webster Dec 29, 2023
7f71777
Partially complete one delete notification test (not done yet)
Sebastian-Webster Dec 29, 2023
bb096db
Added comment deletion fails if not comment owner test
Sebastian-Webster Dec 29, 2023
e58f36f
Finalized temp/deletenotification tests
Sebastian-Webster Dec 29, 2023
22795c9
Added first temp/getnotifications test
Sebastian-Webster Dec 29, 2023
b3a03a7
Added all data type tests for temp/getnotifications
Sebastian-Webster Dec 29, 2023
69c970b
Fixed bug in temp/deletenotification test
Sebastian-Webster Dec 29, 2023
3fac6c7
Fixed bug in temp/getnotifications test
Sebastian-Webster Dec 29, 2023
f2d985f
Added test to see if retrieval works with lastNotificationId
Sebastian-Webster Dec 29, 2023
d07541f
Finalized tests for temp/getnotifications
Sebastian-Webster Dec 29, 2023
1dd900e
Fixed bug in temp/deletenotification
Sebastian-Webster Dec 29, 2023
31ae529
Fixed bug in temp/getnotifications test
Sebastian-Webster Dec 29, 2023
d7e69ed
Changed name of test
Sebastian-Webster Dec 29, 2023
fd19326
Fixed bugs in temp/getnotifications test
Sebastian-Webster Dec 30, 2023
b376822
Add sort by id to temp/getnotifications test
Sebastian-Webster Dec 31, 2023
3e6ef8b
Fixed bug in tests
Sebastian-Webster Dec 31, 2023
b2ee1f6
chore: merge main
Sebastian-Webster Jun 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const CONSTANTS = {
'deletePosts'
],
MAX_ACCOUNT_FOLLOW_REQUESTS_PER_API_CALL: 10,
MAX_NOTIFICATIONS_PER_API_CALL: 10, //Used in temp/getnotifications
VOTED_USERS_API_ALLOWED_POST_FORMATS: ['Image', 'Poll', 'Thread'],
VOTED_USERS_MAX_USERS_TO_SEND_PER_API_CALL: 10,
VOTED_USERS_API_ALLOWED_VOTE_TYPES: ["Up", "Down"],
Expand Down
133 changes: 133 additions & 0 deletions controllers/Temp.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const RefreshToken = require('../models/RefreshToken');
const Message = require('../models/Message');
const Comment = require('../models/Comment');
const CategoryMember = require('../models/CategoryMember');
const Notification = require('../models/Notification');

const HTTPWTLibrary = require('../libraries/HTTPWT');
const CONSTANTS = require('../constants');
Expand Down Expand Up @@ -51,6 +52,9 @@ const categoryHelper = new CategoryLibrary();
const UUIDLibrary = require('../libraries/UUID.js');
const uuidHelper = new UUIDLibrary();

const NotificationLibrary = require('../libraries/Notifications.js');
const notificationHelper = new NotificationLibrary();

const bcrypt = require('bcrypt')
const mongoose = require('mongoose')

Expand Down Expand Up @@ -6703,6 +6707,123 @@ class TempController {
})
}

static #clearnotifications = (userId) => {
return new Promise(resolve => {
if (typeof userId !== 'string') {
return resolve(HTTPWTHandler.badInput(`userId must be a string. Provided type: ${typeof userId}`))
}

if (!mongoose.isObjectIdOrHexString(userId)) {
return resolve(HTTPWTHandler.badInput('userId must be an ObjectId.'))
}

User.findOne({_id: {$eq: userId}}).lean().then(userFound => {
if (!userFound) return resolve(HTTPWTHandler.notFound('Could not find user with provided userId.'))

Notification.deleteMany({userId: {$eq: userId}}).then(() => {
return resolve(HTTPWTHandler.OK('Successfully deleted all notifications.'))
}).catch(error => {
console.error('An error occurred while deleting all notifications with userId:', userId, '. The error was:', error)
return resolve(HTTPWTHandler.serverError('An error occurred while deleting all notifications. Please try again.'))
})
}).catch(error => {
console.error('An error occurred while finding one user with id:', userId, '. The error was:', error);
return resolve(HTTPWTHandler.serverError('An error occurred while finding user. Please try again.'))
})
})
}

static #deletenotification = (userId, notificationId) => {
return new Promise(resolve => {
if (typeof userId !== 'string') {
return resolve(HTTPWTHandler.badInput(`userId must be a string. Provided type: ${typeof userId}`));
}

if (!mongoose.isObjectIdOrHexString(userId)) {
return resolve(HTTPWTHandler.badInput('userId must be an ObjectId.'))
}

if (typeof notificationId !== 'string') {
return resolve(HTTPWTHandler.badInput(`notificationId must be a string. Provided type: ${typeof notificationId}`))
}

if (!mongoose.isObjectIdOrHexString(notificationId)) {
return resolve(HTTPWTHandler.badInput('notificationId must be an ObjectId.'))
}

User.findOne({_id: {$eq: userId}}).lean().then(userFound => {
if (!userFound) return resolve(HTTPWTHandler.notFound('Could not find user with provided userId.'))

Notification.findOne({_id: {$eq: notificationId}}).then(notificationFound => {
if (!notificationFound) return resolve(HTTPWTHandler.notFound('Could not find notification.'))

if (String(notificationFound.userId) !== userId) return resolve(HTTPWTHandler.forbidden('You must be the notification owner to delete this notification.'))

Notification.deleteOne({_id: {$eq: notificationId}}).then(() => {
return resolve(HTTPWTHandler.OK('Successfully deleted notification'))
}).catch(error => {
console.error('An error occurred while deleting one notification with id:', notificationId, '. The error was:', error)
return resolve(HTTPWTHandler.serverError('An error occurred while deleting notification. Please try again.'))
})
})
}).catch(error => {
console.error('An error occurred while finding one user with id:', userId, '. The error was:', error)
return resolve(HTTPWTHandler.serverError('An error occurred while finding user. Please try again.'))
})
})
}

static #getnotifications = (userId, lastNotificationId) => {
return new Promise(resolve => {
if (typeof userId !== 'string') {
return resolve(HTTPWTHandler.badInput(`userId must be a string. Provided type: ${typeof userId}`))
}

if (!mongoose.isObjectIdOrHexString(userId)) {
return resolve(HTTPWTHandler.badInput('userId must be an ObjectId.'))
}

if (typeof lastNotificationId !== 'string' && lastNotificationId !== undefined) {
return resolve(HTTPWTHandler.badInput(`lastNotificationId must be a string or undefined. Provided type: ${typeof lastNotificationId}`))
}

if (typeof lastNotificationId === 'string' && !mongoose.isObjectIdOrHexString(lastNotificationId)) {
return resolve(HTTPWTHandler.badInput('lastNotificationId must be an ObjectId or undefined.'))
}

User.findOne({_id: {$eq: userId}}).lean().then(userFound => {
if (!userFound) return resolve(HTTPWTHandler.notFound('Could not find user with provided userId.'))

const notificationQuery = {
userId: {$eq: userId}
}

if (typeof lastNotificationId === 'string') {
notificationQuery._id = {$lt: lastNotificationId}
}

Notification.find(notificationQuery).sort({_id: -1}).limit(CONSTANTS.MAX_NOTIFICATIONS_PER_API_CALL).lean().then(notifications => {
if (notifications.length < 1) return resolve(HTTPWTHandler.OK('No notifications could be found', {notifications: [], noMoreNotifications: true}))

const notificationData = notificationHelper.returnNotificationDataToSend(notifications);

const toSend = {
notifications: notificationData,
noMoreNotifications: notifications.length < CONSTANTS.MAX_NOTIFICATIONS_PER_API_CALL
}

return resolve(HTTPWTHandler.OK('Found notifications', toSend));
}).catch(error => {
console.error('An error occurred while finding notifications with database query:', notificationQuery, '. The error was:', error)
return resolve(HTTPWTHandler.serverError('An error occurred while finding notifications. Please try again.'))
})
}).catch(error => {
console.error('An error occurred while finding one user with id:', userId, '. The error was:', error)
return resolve(HTTPWTHandler.serverError('An error occurred while finding user. Please try again.'))
})
})
}

static sendnotificationkey = async (userId, notificationKey, refreshTokenId) => {
return await this.#sendnotificationkey(userId, notificationKey, refreshTokenId)
}
Expand Down Expand Up @@ -7038,6 +7159,18 @@ class TempController {
static getpollvoteusers = async (userId, pollId, pollOption, lastItemId) => {
return await this.#getpollvoteusers(userId, pollId, pollOption, lastItemId)
}

static clearnotifications = async (userId) => {
return await this.#clearnotifications(userId);
}

static deletenotification = async (userId, notificationId) => {
return await this.#deletenotification(userId, notificationId);
}

static getnotifications = async (userId, lastNotificationId) => {
return await this.#getnotifications(userId, lastNotificationId);
}
}

module.exports = TempController;
11 changes: 11 additions & 0 deletions libraries/Notifications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Notifications {
returnNotificationDataToSend(rawNotificationData) {
return rawNotificationData.map(notification => {
delete notification.userId;
notification._id = String(notification._id)
return notification;
})
}
}

module.exports = Notifications;
15 changes: 15 additions & 0 deletions models/Notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const mongoose = require('mongoose');
const Schema = mongoose.Schema;

const NotificationSchema = new Schema({
userId: mongoose.Types.ObjectId,
profilePublicId: String,
postId: mongoose.Types.ObjectId,
postFormat: String,
dateCreated: Date,
text: String
})

const Notification = mongoose.model('Notification', NotificationSchema);

module.exports = Notification;
114 changes: 114 additions & 0 deletions routes/Temp.js
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,33 @@ const rateLimiters = {
skipFailedRequests: true,
keyGenerator: (req, res) => req.tokenData //Use req.tokenData (account _id in MongoDB) to identify clients and rate limit
}),
'/clearnotifications': rateLimit({
windowMs: 1000 * 60, //1 minute
max: 5,
standardHeaders: false,
legacyHeaders: false,
message: {status: "FAILED", message: "You have cleared your notifications too many times in the last minute. Please try again in 60 seconds."},
skipFailedRequests: true,
keyGenerator: (req, res) => req.tokenData //Use req.tokenData (account _id in MongoDB) to identify clients and rate limit
}),
'/deletenotification': rateLimit({
windowMs: 1000 * 60, //1 minute
max: 60,
standardHeaders: false,
legacyHeaders: false,
message: {status: "FAILED", message: "You have deleted too many notifications in the last minute. Please try again in 60 seconds."},
skipFailedRequests: true,
keyGenerator: (req, res) => req.tokenData //Use req.tokenData (account _id in MongoDB) to identify clients and rate limit
}),
'/getnotifications': rateLimit({
windowMs: 1000 * 60, //1 minute
max: 5,
standardHeaders: false,
legacyHeaders: false,
message: {status: "FAILED", message: "You have retrieved your notifications too many times in the last minute. Please try again in 60 seconds."},
skipFailedRequests: true,
keyGenerator: (req, res) => req.tokenData //Use req.tokenData (account _id in MongoDB) to identify clients and rate limit
}),
'/followuser': rateLimit({
windowMs: 1000 * 60, //1 minute
max: 6,
Expand Down Expand Up @@ -3119,6 +3146,93 @@ router.post('/removevoteonpost', rateLimiters['/removevoteonpost'], (req, res) =
})
});

router.post('/clearnotifications', rateLimiters['/clearnotifications'], (req, res) => {
let HTTPHeadersSent = false;
const worker = new Worker(workerPath, {
workerData: {
functionName: 'clearnotifications',
functionArgs: [req.tokenData]
}
})

worker.on('message', (result) => {
if (!HTTPHeadersSent) {
HTTPHeadersSent = true;
res.status(result.statusCode).json(result.data)
} else {
console.error('POST temp/clearnotifications controller function returned data to be sent to the client but HTTP headers have already been sent! Data attempted to send:', result)
}
})

worker.on('error', (error) => {
if (!HTTPHeadersSent) {
HTTPHeadersSent = true;
console.error('An error occurred from TempWorker for POST /clearnotifications:', error)
HTTPHandler.serverError(res, String(error))
} else {
console.error('POST temp/clearnotifications controller function encountered an error and tried to send it to the client but HTTP headers have already been sent! Error attempted to send:', error)
}
})
});

router.post('/deletenotification', rateLimiters['/deletenotification'], (req, res) => {
let HTTPHeadersSent = false;
const worker = new Worker(workerPath, {
workerData: {
functionName: 'deletenotification',
functionArgs: [req.tokenData, req.body.notificationId]
}
})

worker.on('message', (result) => {
if (!HTTPHeadersSent) {
HTTPHeadersSent = true;
res.status(result.statusCode).json(result.data)
} else {
console.error('POST temp/deletenotification controller function returned data to be sent to the client but HTTP headers have already been sent! Data attempted to send:', result)
}
})

worker.on('error', (error) => {
if (!HTTPHeadersSent) {
HTTPHeadersSent = true;
console.error('An error occurred from TempWorker for POST /deletenotification:', error)
HTTPHandler.serverError(res, String(error))
} else {
console.error('POST temp/deletenotification controller function encountered an error and tried to send it to the client but HTTP headers have already been sent! Error attempted to send:', error)
}
})
});

router.post('/getnotifications', rateLimiters['/getnotifications'], (req, res) => {
let HTTPHeadersSent = false;
const worker = new Worker(workerPath, {
workerData: {
functionName: 'getnotifications',
functionArgs: [req.tokenData, req.body.lastNotificationId]
}
})

worker.on('message', (result) => {
if (!HTTPHeadersSent) {
HTTPHeadersSent = true;
res.status(result.statusCode).json(result.data)
} else {
console.error('GET temp/getnotifications controller function returned data to be sent to the client but HTTP headers have already been sent! Data attempted to send:', result)
}
})

worker.on('error', (error) => {
if (!HTTPHeadersSent) {
HTTPHeadersSent = true;
console.error('An error occurred from TempWorker for GET /getnotifications:', error)
HTTPHandler.serverError(res, String(error))
} else {
console.error('GET temp/getnotifications controller function encountered an error and tried to send it to the client but HTTP headers have already been sent! Error attempted to send:', error)
}
})
});

router.post('/followuser', rateLimiters['/followuser'], (req, res) => {
let HTTPHeadersSent = false;
const worker = new Worker(workerPath, {
Expand Down
3 changes: 2 additions & 1 deletion tests/TEST_CONSTANTS.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const TEST_CONSTANTS = {
resolve(false);
})
})
}
},
RANDOM_OBJECTIDS: ["6560b01ec5ea35f173c645d4", "6560b0310a9b4c4ee26ce297", "6560b036dbc1f1384347ff61", "6560b047f1045829ebdb6e3b", "6560b0633f3b8652f95e4224"]
}

module.exports = TEST_CONSTANTS;
Loading