diff --git a/client/src/javascript/actions/FloodActions.ts b/client/src/javascript/actions/FloodActions.ts index dc2870f0..ea500ef1 100644 --- a/client/src/javascript/actions/FloodActions.ts +++ b/client/src/javascript/actions/FloodActions.ts @@ -117,10 +117,7 @@ const FloodActions = { fetchNotifications: (options: NotificationFetchOptions) => axios .get(`${baseURI}api/notifications`, { - params: { - limit: options.limit, - start: options.start, - }, + params: options, }) .then((json) => json.data) .then( diff --git a/server/routes/api/index.ts b/server/routes/api/index.ts index 8aeece39..fb93a8b4 100644 --- a/server/routes/api/index.ts +++ b/server/routes/api/index.ts @@ -70,12 +70,43 @@ router.get('/history', ( ); }); +/** + * GET /api/notifications + * @summary Gets notifications + * @tags Flood + * @security User + * @param {NotificationFetchOptions} queries - options + * @return {{Notification[][], NotificationCount}} 200 - success response - application/json + * @return {Error} 500 - failure response - application/json + */ router.get('/notifications', (req, res) => { - req.services?.notificationService.getNotifications(req.query, getResponseFn(res)); + req.services?.notificationService.getNotifications(req.query).then( + (notifications) => { + res.status(200).json(notifications); + }, + (err: Error) => { + res.status(500).json({message: err.message}); + }, + ); }); +/** + * DELETE /api/notifications + * @summary Clears notifications + * @tags Flood + * @security User + * @return 200 - success response + * @return {Error} 500 - failure response - application/json + */ router.delete('/notifications', (req, res) => { - req.services?.notificationService.clearNotifications(getResponseFn(res)); + req.services?.notificationService.clearNotifications().then( + () => { + res.status(200).send(); + }, + (err: Error) => { + res.status(500).json({message: err.message}); + }, + ); }); /** diff --git a/server/services/notificationService.ts b/server/services/notificationService.ts index 3370a890..14ac59ce 100644 --- a/server/services/notificationService.ts +++ b/server/services/notificationService.ts @@ -1,4 +1,4 @@ -import Datastore from 'nedb'; +import Datastore from 'nedb-promises'; import path from 'path'; import type {Notification, NotificationCount, NotificationFetchOptions} from '@shared/types/Notification'; @@ -14,50 +14,18 @@ const DEFAULT_QUERY_LIMIT = 20; class NotificationService extends BaseService { count: NotificationCount = {read: 0, total: 0, unread: 0}; - db = this.loadDatabase(); + db = Datastore.create({ + autoload: true, + filename: path.join(config.dbPath, this.user._id, 'notifications.db'), + }); constructor(...args: ConstructorParameters) { super(...args); - this.onServicesUpdated = () => { - this.countNotifications(); - }; - } + (async () => { + const notifications = await this.db.find({}).catch(() => undefined); - addNotification(notifications: Array>) { - this.count.total += notifications.length; - this.count.unread += notifications.length; - - const timestamp = Date.now(); - const notificationsToInsert = notifications.map((notification) => ({ - ts: timestamp, - data: notification.data, - id: notification.id, - read: false, - })) as Notification[]; - - this.db.insert(notificationsToInsert, () => this.emitUpdate()); - } - - clearNotifications(callback: (data?: null, err?: Error) => void) { - this.db.remove({}, {multi: true}, (err) => { - if (err) { - callback(null, err); - return; - } - - this.count = {read: 0, total: 0, unread: 0}; - this.emitUpdate(); - - callback(); - }); - } - - countNotifications() { - this.db.find({}, (err: Error, notifications: Array) => { - if (err) { - this.count = {read: 0, total: 0, unread: 0}; - } else { + if (notifications != null) { notifications.forEach((notification) => { if (notification.read) { this.count.read += 1; @@ -70,7 +38,7 @@ class NotificationService extends BaseService { } this.emitUpdate(); - }); + })(); } emitUpdate = () => { @@ -84,49 +52,77 @@ class NotificationService extends BaseService { return this.count; } - getNotifications( - query: NotificationFetchOptions, - callback: ( - data: { - notifications: Notification[][]; - count: NotificationCount; - } | null, - err?: Error, - ) => void, - ) { - const sortedNotifications = this.db.find({}).sort({ts: -1}); - const queryCallback = (err: Error | null, notifications: Notification[][]) => { - if (err) { - callback(null, err); - return; - } + /** + * Adds notifications + * + * @param {Array} notifications - Notifications to add + * @return {Promise} - Rejects with error. + */ + async addNotification(notifications: Array>): Promise { + this.count.total += notifications.length; + this.count.unread += notifications.length; - callback({notifications, count: this.count}); - }; + await this.db + .insert( + notifications.map((notification) => ({ + ts: Date.now(), + data: notification.data, + id: notification.id, + read: false, + })), + ) + .catch(() => undefined); - if (query.allNotifications) { - sortedNotifications.exec(queryCallback); - } else if (query.start != null) { - sortedNotifications - .skip(Number(query.start)) - .limit(Number(query.limit) || DEFAULT_QUERY_LIMIT) - .exec(queryCallback); - } else { - sortedNotifications.limit(Number(query.limit) || DEFAULT_QUERY_LIMIT).exec(queryCallback); - } + this.emitUpdate(); } - loadDatabase(): Datastore> { - if (this.db != null) { - return this.db; + /** + * Clears notifications + * + * @return {Promise} - Rejects with error. + */ + async clearNotifications(): Promise { + await this.db.remove({}, {multi: true}); + + this.count = {read: 0, total: 0, unread: 0}; + this.emitUpdate(); + } + + /** + * Gets notifications + * + * @param {NotificationFetchOptions} - options - An object of options... + * @return {Promise<{Notification[][], NotificationCount}>} - Resolves with notifications and counts or rejects with error. + */ + async getNotifications({ + allNotifications, + start, + limit, + }: NotificationFetchOptions): Promise<{ + notifications: Notification[][]; + count: NotificationCount; + }> { + const sortedNotifications = this.db.find({}).sort({ts: -1}); + + if (allNotifications) { + return { + notifications: await sortedNotifications.exec(), + count: this.count, + }; + } else if (start != null) { + return { + notifications: await sortedNotifications + .skip(Number(start)) + .limit(Number(limit) || DEFAULT_QUERY_LIMIT) + .exec(), + count: this.count, + }; + } else { + return { + notifications: await sortedNotifications.limit(Number(limit) || DEFAULT_QUERY_LIMIT).exec(), + count: this.count, + }; } - - const db = new Datastore({ - autoload: true, - filename: path.join(config.dbPath, this.user._id, 'notifications.db'), - }); - - return db; } } diff --git a/shared/types/Notification.ts b/shared/types/Notification.ts index e2c8867c..bd82e99b 100644 --- a/shared/types/Notification.ts +++ b/shared/types/Notification.ts @@ -34,7 +34,7 @@ export interface NotificationState { } export interface NotificationFetchOptions { - id: string; + id?: string; limit: number; start: number; allNotifications?: boolean;