Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Socket Notifications Refactor #12180

Draft
wants to merge 1 commit into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 60 additions & 0 deletions public/openapi/components/schemas/NotificationObject.yaml
@@ -0,0 +1,60 @@
NotificationObject:
allOf:
- type: object
properties:
importance:
type: number
datetime:
type: number
path:
type: string
description: Relative path to the notification target
bodyShort:
type: string
nid:
type: string
datetimeISO:
type: string
read:
type: boolean
readClass:
type: string
- type: object
description: Optional properties that may or may not be present (except for `nid`, which is always present, and is only here as a hack to pass validation)
properties:
nid:
type: string
type:
type: string
description: Used to denote a classification of notification. These types are toggleable in the user ACP (so the user can opt to not receive notifications for certain types, etc.)
bodyLong:
type: string
from:
type: number
pid:
type: number
description: A post id, if the notification pertains to a post
tid:
type: number
description: A post id, if the notification pertains to a topic
user:
$ref: ./UserObject.yaml#/UserObjectTiny
subject:
type: string
description: A language key that would be used as an email subject, otherwise a generic "New Notification" message.
icon:
type: string
roomName:
type: string
description: The chat room name, if the notification is related to a chat message
roomIcon:
type: string
mergeId:
type: string
description: A common string used to denote related notifications that can be merged together (e.g. two new chat messages in same room)
image:
type: string
description: A URL to a media image for the notification (supercedes the user avatar if `user` is present)
nullable: true
required:
- nid
54 changes: 28 additions & 26 deletions public/openapi/components/schemas/UserObject.yaml
Expand Up @@ -609,6 +609,33 @@ UserObjectSlim:
type: string
description: An ISO 8601 formatted date string representing the moment a ban will be lifted, or the words "Not Banned"
example: Not Banned
UserObjectTiny:
type: object
properties:
username:
type: string
description: A friendly name for a given user account
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
removed, etc.)
picture:
type: string
uid:
type: number
description: A user identifier
icon:text:
type: string
description: A single-letter representation of a username. This is used in the
auto-generated icon given to users without
an avatar
icon:bgColor:
type: string
description: A six-character hexadecimal colour code assigned to the user. This
value is used in conjunction with
`icon:text` for the user's auto-generated
icon
example: "#f44336"
UserObjectACP:
type: object
required:
Expand Down Expand Up @@ -715,32 +742,7 @@ BanMuteArray:
fromUid:
type: number
user:
type: object
properties:
username:
type: string
description: A friendly name for a given user account
userslug:
type: string
description: An URL-safe variant of the username (i.e. lower-cased, spaces
removed, etc.)
picture:
type: string
uid:
type: number
description: A user identifier
icon:text:
type: string
description: A single-letter representation of a username. This is used in the
auto-generated icon given to users without
an avatar
icon:bgColor:
type: string
description: A six-character hexadecimal colour code assigned to the user. This
value is used in conjunction with
`icon:text` for the user's auto-generated
icon
example: "#f44336"
$ref: '#/UserObjectTiny'
until:
type: number
untilReadable:
Expand Down
6 changes: 6 additions & 0 deletions public/openapi/write.yaml
Expand Up @@ -164,6 +164,12 @@ paths:
$ref: 'write/topics/tid/read.yaml'
/topics/{tid}/bump:
$ref: 'write/topics/tid/bump.yaml'
/notifications:
$ref: 'write/notifications.yaml'
/notifications/{nid}:
$ref: 'write/notifications/nid.yaml'
/notifications/count:
$ref: 'write/notifications/count.yaml'
/tags/{tag}/follow:
$ref: 'write/tags/tag/follow.yaml'
/posts/{pid}:
Expand Down
26 changes: 26 additions & 0 deletions public/openapi/write/notifications.yaml
@@ -0,0 +1,26 @@
get:
tags:
- notifications
summary: list notifications
description: This operation returns two lists of notifications — read and unread.
responses:
'200':
description: notifications successfully listed
content:
application/json:
schema:
type: object
properties:
status:
$ref: ../components/schemas/Status.yaml#/Status
response:
type: object
properties:
read:
type: array
items:
$ref: ../components/schemas/NotificationObject.yaml#/NotificationObject
unread:
type: array
items:
$ref: ../components/schemas/NotificationObject.yaml#/NotificationObject
20 changes: 20 additions & 0 deletions public/openapi/write/notifications/count.yaml
@@ -0,0 +1,20 @@
get:
tags:
- notifications
summary: get unread notification count
description: This operation returns the calling user's unread notifications count
responses:
'200':
description: unread notifications count successfully retrieved
content:
application/json:
schema:
type: object
properties:
status:
$ref: ../../components/schemas/Status.yaml#/Status
response:
type: object
properties:
unread:
type: number
28 changes: 28 additions & 0 deletions public/openapi/write/notifications/nid.yaml
@@ -0,0 +1,28 @@
get:
tags:
- notifications
summary: get notification
description: This operation returns the content of a single notification
parameters:
- in: path
name: nid
schema:
type: number
required: true
description: The notification id to retrieve
example: uploads:export:1
responses:
'200':
description: notification successfully retrieved
content:
application/json:
schema:
type: object
properties:
status:
$ref: ../../components/schemas/Status.yaml#/Status
response:
type: object
properties:
notification:
$ref: ../../components/schemas/NotificationObject.yaml#/NotificationObject
21 changes: 7 additions & 14 deletions public/src/modules/notifications.js
Expand Up @@ -8,7 +8,8 @@ define('notifications', [
'tinycon',
'hooks',
'alerts',
], function (translator, components, navigator, Tinycon, hooks, alerts) {
'api',
], function (translator, components, navigator, Tinycon, hooks, alerts, api) {
const Notifications = {};

let unreadNotifs = {};
Expand All @@ -30,11 +31,7 @@ define('notifications', [

Notifications.loadNotifications = function (notifList, callback) {
callback = callback || function () {};
socket.emit('notifications.get', null, function (err, data) {
if (err) {
return alerts.error(err);
}

api.get('/notifications').then((data) => {
const notifs = data.unread.concat(data.read).sort(function (a, b) {
return parseInt(a.datetime, 10) > parseInt(b.datetime, 10) ? -1 : 1;
});
Expand Down Expand Up @@ -68,7 +65,7 @@ define('notifications', [
callback();
});
});
});
}).catch(alerts.error);
};

Notifications.handleUnreadButton = function (notifList) {
Expand All @@ -94,13 +91,9 @@ define('notifications', [
return;
}

socket.emit('notifications.getCount', function (err, count) {
if (err) {
return alerts.error(err);
}

Notifications.updateNotifCount(count);
});
api.get('/notifications/count').then(({ unread }) => {
Notifications.updateNotifCount(unread);
}).catch(alerts.error);

if (!unreadNotifs[notifData.nid]) {
unreadNotifs[notifData.nid] = true;
Expand Down
1 change: 1 addition & 0 deletions src/api/index.js
Expand Up @@ -5,6 +5,7 @@ module.exports = {
users: require('./users'),
groups: require('./groups'),
topics: require('./topics'),
notifications: require('./notifications'),
tags: require('./tags'),
posts: require('./posts'),
chats: require('./chats'),
Expand Down
22 changes: 22 additions & 0 deletions src/api/notifications.js
@@ -0,0 +1,22 @@
'use strict';

const user = require('../user');

const notificationsApi = module.exports;

notificationsApi.list = async (caller) => {
const { read, unread } = await user.notifications.get(caller.uid);
return { read, unread };
};

notificationsApi.get = async (caller, { nid }) => {
let notification = await user.notifications.getNotifications([nid], caller.uid);
notification = notification.pop();

return { notification };
};

notificationsApi.getCount = async (caller) => {
const unread = await user.notifications.getUnreadCount(caller.uid);
return { unread };
};
1 change: 1 addition & 0 deletions src/controllers/write/index.js
Expand Up @@ -6,6 +6,7 @@ Write.users = require('./users');
Write.groups = require('./groups');
Write.categories = require('./categories');
Write.topics = require('./topics');
Write.notifications = require('./notifications');
Write.tags = require('./tags');
Write.posts = require('./posts');
Write.chats = require('./chats');
Expand Down
22 changes: 22 additions & 0 deletions src/controllers/write/notifications.js
@@ -0,0 +1,22 @@
'use strict';

const api = require('../../api');

const helpers = require('../helpers');

const Notifications = module.exports;

Notifications.get = async (req, res) => {
let response;
if (req.params.nid) {
response = await api.notifications.get(req, { ...req.params });
} else {
response = await api.notifications.list(req);
}

helpers.formatApiResponse(200, res, response);
};

Notifications.getCount = async (req, res) => {
helpers.formatApiResponse(200, res, await api.notifications.getCount(req));
};
1 change: 1 addition & 0 deletions src/routes/write/index.js
Expand Up @@ -37,6 +37,7 @@ Write.reload = async (params) => {
router.use('/api/v3/groups', require('./groups')());
router.use('/api/v3/categories', require('./categories')());
router.use('/api/v3/topics', require('./topics')());
router.use('/api/v3/notifications', require('./notifications')());
router.use('/api/v3/tags', require('./tags')());
router.use('/api/v3/posts', require('./posts')());
router.use('/api/v3/chats', require('./chats')());
Expand Down
18 changes: 18 additions & 0 deletions src/routes/write/notifications.js
@@ -0,0 +1,18 @@
'use strict';

const router = require('express').Router();
const middleware = require('../../middleware');
const controllers = require('../../controllers');
const routeHelpers = require('../helpers');

const { setupApiRoute } = routeHelpers;

module.exports = function () {
const middlewares = [middleware.ensureLoggedIn];

setupApiRoute(router, 'get', '/count', [...middlewares], controllers.write.notifications.getCount);

setupApiRoute(router, 'get', '/:nid?', [...middlewares], controllers.write.notifications.get);

return router;
};
23 changes: 20 additions & 3 deletions src/socket.io/notifications.js
Expand Up @@ -2,18 +2,35 @@

const user = require('../user');
const notifications = require('../notifications');
const api = require('../api');

const sockets = require('.');

const SocketNotifs = module.exports;

SocketNotifs.get = async function (socket, data) {
sockets.warnDeprecated(socket, 'GET /api/v3/notifications/(:nid)');

// Passing in multiple nids is no longer supported in apiv3
if (data && Array.isArray(data.nids) && socket.uid) {
return await user.notifications.getNotifications(data.nids, socket.uid);
const notifications = await Promise.all(data.nids.map(async (nid) => {
const { notification } = await api.notifications.get(socket, { nid });
return notification;
}));

return notifications;
}
return await user.notifications.get(socket.uid);

const response = await api.notifications.list(socket);
response.uid = socket.uid;
return response;
};

SocketNotifs.getCount = async function (socket) {
return await user.notifications.getUnreadCount(socket.uid);
sockets.warnDeprecated(socket, 'GET /api/v3/notifications/count');

const { unread } = await api.notifications.getCount(socket);
return unread;
};

SocketNotifs.deleteAll = async function (socket) {
Expand Down