From b5bbcbaeaac1f77dcc6c15988300226da5322329 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Tue, 31 Mar 2020 20:54:10 -0400 Subject: [PATCH] feat: added POST and DELETE /api/v1/users/:uid/follow routes --- openapi.yaml | 47 +++++++++++++++++++++++++++++ public/src/client/account/header.js | 15 ++++----- src/controllers/write/users.js | 36 ++++++++++++++++++++++ src/routes/write/users.js | 3 ++ src/socket.io/user.js | 5 +++ 5 files changed, 99 insertions(+), 7 deletions(-) diff --git a/openapi.yaml b/openapi.yaml index 58200ee8d1..e58eca56cc 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -195,6 +195,53 @@ paths: $ref: '#/components/schemas/Status' response: type: object + '/{uid}/follow': + post: + tags: + - users + summary: follows a user + parameters: + - in: path + name: uid + schema: + type: integer + required: true + description: uid of the user to follow + responses: + '200': + description: successfully followed user + content: + application/json: + schema: + type: object + properties: + status: + $ref: '#/components/schemas/Status' + response: + type: object + delete: + tags: + - users + summary: unfollows a user + parameters: + - in: path + name: uid + schema: + type: integer + required: true + description: uid of the user to unfollow + responses: + '200': + description: successfully unfollowed user + content: + application/json: + schema: + type: object + properties: + status: + $ref: '#/components/schemas/Status' + response: + type: object components: schemas: Status: diff --git a/public/src/client/account/header.js b/public/src/client/account/header.js index 917d5b8791..fb22f4b443 100644 --- a/public/src/client/account/header.js +++ b/public/src/client/account/header.js @@ -115,17 +115,18 @@ define('forum/account/header', [ } function toggleFollow(type) { - socket.emit('user.' + type, { - uid: ajaxify.data.uid, - }, function (err) { - if (err) { - return app.alertError(err.message); - } - + $.ajax({ + url: config.relative_path + '/api/v1/users/' + ajaxify.data.uid + '/' + type, + method: type === 'follow' ? 'post' : 'delete', + }).done(function () { components.get('account/follow').toggleClass('hide', type === 'follow'); components.get('account/unfollow').toggleClass('hide', type === 'unfollow'); app.alertSuccess('[[global:alert.' + type + ', ' + ajaxify.data.username + ']]'); + }).fail(function (ev) { + console.log(ev); + app.alertError(ev.responseJSON.status.message); }); + return false; } diff --git a/src/controllers/write/users.js b/src/controllers/write/users.js index 7af31dde67..edd12b32f6 100644 --- a/src/controllers/write/users.js +++ b/src/controllers/write/users.js @@ -2,7 +2,9 @@ const user = require('../../user'); const groups = require('../../groups'); +const plugins = require('../../plugins'); const privileges = require('../../privileges'); +const notifications = require('../../notifications'); const meta = require('../../meta'); const events = require('../../events'); const helpers = require('../helpers'); @@ -119,3 +121,37 @@ Users.changePassword = async (req, res) => { helpers.formatApiResponse(200, res); }; + +Users.follow = async (req, res) => { + await user.follow(req.user.uid, req.params.uid); + plugins.fireHook('action:user.follow', { + fromUid: req.user.uid, + toUid: req.params.uid, + }); + + const userData = await user.getUserFields(req.user.uid, ['username', 'userslug']); + const notifObj = await notifications.create({ + type: 'follow', + bodyShort: '[[notifications:user_started_following_you, ' + userData.username + ']]', + nid: 'follow:' + req.params.uid + ':uid:' + req.user.uid, + from: req.user.uid, + path: '/uid/' + req.params.uid + '/followers', + mergeId: 'notifications:user_started_following_you', + }); + if (!notifObj) { + return; + } + notifObj.user = userData; + await notifications.push(notifObj, [req.params.uid]); + + helpers.formatApiResponse(200, res); +}; + +Users.unfollow = async (req, res) => { + await user.unfollow(req.user.uid, req.params.uid); + plugins.fireHook('action:user.unfollow', { + fromUid: req.user.uid, + toUid: req.params.uid, + }); + helpers.formatApiResponse(200, res); +}; diff --git a/src/routes/write/users.js b/src/routes/write/users.js index 18f027b67d..9e3993019c 100644 --- a/src/routes/write/users.js +++ b/src/routes/write/users.js @@ -29,6 +29,9 @@ function authenticatedRoutes() { setupApiRoute(router, '/:uid/password', middleware, [...middlewares, middleware.checkRequired.bind(null, ['newPassword'])], 'put', controllers.write.users.changePassword); + setupApiRoute(router, '/:uid/follow', middleware, [...middlewares], 'post', controllers.write.users.follow); + setupApiRoute(router, '/:uid/unfollow', middleware, [...middlewares], 'delete', controllers.write.users.unfollow); + // app.put('/:uid/follow', apiMiddleware.requireUser, function(req, res) { // Users.follow(req.user.uid, req.params.uid, function(err) { // return errorHandler.handle(err, res); diff --git a/src/socket.io/user.js b/src/socket.io/user.js index fd080e1eb1..b92baad6d0 100644 --- a/src/socket.io/user.js +++ b/src/socket.io/user.js @@ -18,6 +18,7 @@ const userController = require('../controllers/user'); const privileges = require('../privileges'); const utils = require('../utils'); const flags = require('../flags'); +const sockets = require('.'); const SocketUser = module.exports; @@ -157,6 +158,8 @@ SocketUser.isFollowing = async function (socket, data) { }; SocketUser.follow = async function (socket, data) { + sockets.warnDeprecated(socket, 'POST /api/v1/users/follow'); + if (!socket.uid || !data) { throw new Error('[[error:invalid-data]]'); } @@ -179,6 +182,8 @@ SocketUser.follow = async function (socket, data) { }; SocketUser.unfollow = async function (socket, data) { + sockets.warnDeprecated(socket, 'DELETE /api/v1/users/unfollow'); + if (!socket.uid || !data) { throw new Error('[[error:invalid-data]]'); }