Compare commits

...

1 Commits

14 changed files with 260 additions and 43 deletions

View File

@@ -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

View File

@@ -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:
@@ -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:

View File

@@ -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}:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = {};
@@ -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;
});
@@ -68,7 +65,7 @@ define('notifications', [
callback();
});
});
});
}).catch(alerts.error);
};
Notifications.handleUnreadButton = function (notifList) {
@@ -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;

View File

@@ -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'),

22
src/api/notifications.js Normal file
View File

@@ -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 };
};

View File

@@ -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');

View File

@@ -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));
};

View File

@@ -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')());

View File

@@ -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;
};

View File

@@ -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) {