From 0a41741b7e135c07ac360f6e44910a8a0133e3b7 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Fri, 3 Sep 2021 15:25:26 -0400 Subject: [PATCH] refactor: deprecate picture update socket call, new API routes for picture update --- public/openapi/write.yaml | 2 + public/openapi/write/users/uid/picture.yaml | 43 +++++++++++++++++++++ public/src/modules/accounts/picture.js | 19 +++------ src/api/users.js | 41 ++++++++++++++++++++ src/controllers/write/users.js | 5 +++ src/routes/write/users.js | 1 + src/socket.io/user/picture.js | 39 +++---------------- 7 files changed, 103 insertions(+), 47 deletions(-) create mode 100644 public/openapi/write/users/uid/picture.yaml diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml index 8ac39fe982..734b64ff24 100644 --- a/public/openapi/write.yaml +++ b/public/openapi/write.yaml @@ -52,6 +52,8 @@ paths: $ref: 'write/users.yaml' /users/{uid}: $ref: 'write/users/uid.yaml' + /users/{uid}/picture: + $ref: 'write/users/uid/picture.yaml' /users/{uid}/content: $ref: 'write/users/uid/content.yaml' /users/{uid}/account: diff --git a/public/openapi/write/users/uid/picture.yaml b/public/openapi/write/users/uid/picture.yaml new file mode 100644 index 0000000000..d6498a0af2 --- /dev/null +++ b/public/openapi/write/users/uid/picture.yaml @@ -0,0 +1,43 @@ +put: + tags: + - users + summary: update user picture or icon background colour + parameters: + - in: path + name: uid + schema: + type: integer + required: true + description: uid of the user + example: 1 + requestBody: + content: + application/json: + schema: + type: object + properties: + type: + type: string + description: The source of the picture + enum: ['default', 'uploaded', 'external'] + example: default + url: + type: string + description: Only used for `external` type, specifies the source of the external image to use as avatar + example: '' + bgColor: + type: string + description: A hexadecimal colour representation + example: '#ff0000' + responses: + '200': + description: successfully updated user picture + content: + application/json: + schema: + type: object + properties: + status: + $ref: ../../../components/schemas/Status.yaml#/Status + response: + type: object \ No newline at end of file diff --git a/public/src/modules/accounts/picture.js b/public/src/modules/accounts/picture.js index a97e0fb891..3893f2e27a 100644 --- a/public/src/modules/accounts/picture.js +++ b/public/src/modules/accounts/picture.js @@ -2,7 +2,8 @@ define('accounts/picture', [ 'pictureCropper', -], (pictureCropper) => { + 'api', +], (pictureCropper, api) => { const Picture = {}; Picture.openChangeModal = () => { @@ -89,14 +90,10 @@ define('accounts/picture', [ var type = modal.find('.list-group-item.active').attr('data-type'); const iconBgColor = document.querySelector('.modal.picture-switcher input[type="radio"]:checked').value || 'transparent'; - changeUserPicture(type, iconBgColor, function (err) { - if (err) { - return app.alertError(err.message); - } - + changeUserPicture(type, iconBgColor).then(() => { Picture.updateHeader(type === 'default' ? '' : modal.find('.list-group-item.active img').attr('src'), iconBgColor); ajaxify.refresh(); - }); + }).catch(app.alertError); } function onCloseModal() { @@ -212,12 +209,8 @@ define('accounts/picture', [ }); } - function changeUserPicture(type, bgColor, callback) { - socket.emit('user.changePicture', { - type, - bgColor, - uid: ajaxify.data.theirid, - }, callback); + function changeUserPicture(type, bgColor) { + return api.put(`/users/${ajaxify.data.theirid}/picture`, { type, bgColor }); } return Picture; diff --git a/src/api/users.js b/src/api/users.js index b7deaed080..71d3a0c8f4 100644 --- a/src/api/users.js +++ b/src/api/users.js @@ -341,3 +341,44 @@ usersAPI.search = async function (caller, data) { filters: filters, }); }; + +usersAPI.changePicture = async (caller, data) => { + if (!data) { + throw new Error('[[error:invalid-data]]'); + } + + const { type, url } = data; + let picture = ''; + + await user.checkMinReputation(caller.uid, data.uid, 'min:rep:profile-picture'); + const canEdit = await privileges.users.canEdit(caller.uid, data.uid); + if (!canEdit) { + throw new Error('[[error:no-privileges]]'); + } + + if (type === 'default') { + picture = ''; + } else if (type === 'uploaded') { + picture = await user.getUserField(data.uid, 'uploadedpicture'); + } else if (type === 'external' && url) { + picture = validator.escape(url); + } else { + const returnData = await plugins.hooks.fire('filter:user.getPicture', { + uid: caller.uid, + type: type, + picture: undefined, + }); + picture = returnData && returnData.picture; + } + + const validBackgrounds = await user.getIconBackgrounds(caller.uid); + if (!validBackgrounds.includes(data.bgColor)) { + data.bgColor = validBackgrounds[0]; + } + + await user.updateProfile(caller.uid, { + uid: data.uid, + picture: picture, + 'icon:bgColor': data.bgColor, + }, ['picture', 'icon:bgColor']); +}; diff --git a/src/controllers/write/users.js b/src/controllers/write/users.js index 43aa2c0ccc..2469ae4b2a 100644 --- a/src/controllers/write/users.js +++ b/src/controllers/write/users.js @@ -76,6 +76,11 @@ Users.deleteMany = async (req, res) => { helpers.formatApiResponse(200, res); }; +Users.changePicture = async (req, res) => { + await api.users.changePicture(req, { ...req.body, uid: req.params.uid }); + helpers.formatApiResponse(200, res); +}; + Users.updateSettings = async (req, res) => { const settings = await api.users.updateSettings(req, { ...req.body, uid: req.params.uid }); helpers.formatApiResponse(200, res, settings); diff --git a/src/routes/write/users.js b/src/routes/write/users.js index 02a64e134f..418fa9897d 100644 --- a/src/routes/write/users.js +++ b/src/routes/write/users.js @@ -22,6 +22,7 @@ function authenticatedRoutes() { setupApiRoute(router, 'get', '/:uid', [...middlewares, middleware.assert.user], controllers.write.users.get); setupApiRoute(router, 'put', '/:uid', [...middlewares, middleware.assert.user], controllers.write.users.update); setupApiRoute(router, 'delete', '/:uid', [...middlewares, middleware.assert.user], controllers.write.users.delete); + setupApiRoute(router, 'put', '/:uid/picture', [...middlewares, middleware.assert.user], controllers.write.users.changePicture); setupApiRoute(router, 'delete', '/:uid/content', [...middlewares, middleware.assert.user], controllers.write.users.deleteContent); setupApiRoute(router, 'delete', '/:uid/account', [...middlewares, middleware.assert.user], controllers.write.users.deleteAccount); diff --git a/src/socket.io/user/picture.js b/src/socket.io/user/picture.js index a5a2fbbea7..186ab02835 100644 --- a/src/socket.io/user/picture.js +++ b/src/socket.io/user/picture.js @@ -3,42 +3,13 @@ const user = require('../../user'); const plugins = require('../../plugins'); +const websockets = require('../index'); +const api = require('../../api'); + module.exports = function (SocketUser) { SocketUser.changePicture = async function (socket, data) { - if (!socket.uid) { - throw new Error('[[error:invalid-uid]]'); - } - - if (!data) { - throw new Error('[[error:invalid-data]]'); - } - - const { type } = data; - let picture = ''; - await user.isAdminOrGlobalModOrSelf(socket.uid, data.uid); - if (type === 'default') { - picture = ''; - } else if (type === 'uploaded') { - picture = await user.getUserField(data.uid, 'uploadedpicture'); - } else { - const returnData = await plugins.hooks.fire('filter:user.getPicture', { - uid: socket.uid, - type: type, - picture: undefined, - }); - picture = returnData && returnData.picture; - } - - const validBackgrounds = await user.getIconBackgrounds(socket.uid); - if (!validBackgrounds.includes(data.bgColor)) { - data.bgColor = validBackgrounds[0]; - } - - await user.updateProfile(socket.uid, { - uid: data.uid, - picture: picture, - 'icon:bgColor': data.bgColor, - }, ['picture', 'icon:bgColor']); + websockets.warnDeprecated(socket, 'PUT /api/v3/users/:uid/picture'); + await api.users.changePicture(socket, data); }; SocketUser.removeUploadedPicture = async function (socket, data) {