From c3e03502e4f81aab80c769aac95c1a5c5208adae Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Sat, 22 Jun 2019 01:50:58 -0400 Subject: [PATCH 01/12] Fixes for status message text presence issues Added statusText to several api endpoints Changed statusMessage to statusText since that is what it is called everywhere --- app/api/server/v1/channels.js | 2 +- app/api/server/v1/groups.js | 2 +- app/api/server/v1/im.js | 2 +- app/api/server/v1/users.js | 4 +- app/lib/lib/roomTypes/direct.js | 9 ++-- app/lib/server/functions/index.js | 2 +- app/lib/server/functions/saveUser.js | 4 +- app/lib/server/functions/setStatusMessage.js | 45 ---------------- app/lib/server/functions/setStatusText.js | 53 +++++++++++++++++++ app/ui-flextab/client/tabs/userInfo.js | 5 +- app/ui-sidenav/client/sidebarHeader.js | 9 ++-- app/ui/client/components/header/headerRoom.js | 14 +++-- .../server/methods/setUserStatus.js | 4 +- imports/startup/client/listenActiveUsers.js | 4 +- imports/users-presence/server/activeUsers.js | 2 + server/publications/spotlight.js | 1 + 16 files changed, 86 insertions(+), 76 deletions(-) delete mode 100644 app/lib/server/functions/setStatusMessage.js create mode 100644 app/lib/server/functions/setStatusText.js diff --git a/app/api/server/v1/channels.js b/app/api/server/v1/channels.js index b1d9552bca30..f92132a7d4e5 100644 --- a/app/api/server/v1/channels.js +++ b/app/api/server/v1/channels.js @@ -539,7 +539,7 @@ API.v1.addRoute('channels.members', { authRequired: true }, { const members = subscriptions.fetch().map((s) => s.u && s.u._id); const users = Users.find({ _id: { $in: members } }, { - fields: { _id: 1, username: 1, name: 1, status: 1, utcOffset: 1 }, + fields: { _id: 1, username: 1, name: 1, status: 1, statusText: 1, utcOffset: 1 }, sort: { username: sort.username != null ? sort.username : 1 }, }).fetch(); diff --git a/app/api/server/v1/groups.js b/app/api/server/v1/groups.js index 32cd1609bd7f..b84c1d171a06 100644 --- a/app/api/server/v1/groups.js +++ b/app/api/server/v1/groups.js @@ -497,7 +497,7 @@ API.v1.addRoute('groups.members', { authRequired: true }, { const members = subscriptions.fetch().map((s) => s.u && s.u._id); const users = Users.find({ _id: { $in: members } }, { - fields: { _id: 1, username: 1, name: 1, status: 1, utcOffset: 1 }, + fields: { _id: 1, username: 1, name: 1, status: 1, statusText: 1, utcOffset: 1 }, sort: { username: sort.username != null ? sort.username : 1 }, }).fetch(); diff --git a/app/api/server/v1/im.js b/app/api/server/v1/im.js index 13502c083a64..5afd41d05cb1 100644 --- a/app/api/server/v1/im.js +++ b/app/api/server/v1/im.js @@ -205,7 +205,7 @@ API.v1.addRoute(['dm.members', 'im.members'], { authRequired: true }, { const members = cursor.fetch().map((s) => s.u && s.u.username); const users = Users.find({ username: { $in: members } }, { - fields: { _id: 1, username: 1, name: 1, status: 1, utcOffset: 1 }, + fields: { _id: 1, username: 1, name: 1, status: 1, statusText: 1, utcOffset: 1 }, sort: { username: sort && sort.username ? sort.username : 1 }, }).fetch(); diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index 82596a4e46f6..8512a5032526 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -18,7 +18,7 @@ import { } from '../../../lib'; import { getFullUserData } from '../../../lib/server/functions/getFullUserData'; import { API } from '../api'; -import { setStatusMessage } from '../../../lib/server'; +import { setStatusText } from '../../../lib/server'; API.v1.addRoute('users.create', { authRequired: true }, { post() { @@ -370,7 +370,7 @@ API.v1.addRoute('users.setStatus', { authRequired: true }, { Meteor.runAsUser(user._id, () => { if (this.bodyParams.message) { - setStatusMessage(user._id, this.bodyParams.message); + setStatusText(user._id, this.bodyParams.message); } if (this.bodyParams.status) { const validStatus = ['online', 'away', 'offline', 'busy']; diff --git a/app/lib/lib/roomTypes/direct.js b/app/lib/lib/roomTypes/direct.js index 0f55c1388ee3..c5b163bbb916 100644 --- a/app/lib/lib/roomTypes/direct.js +++ b/app/lib/lib/roomTypes/direct.js @@ -93,11 +93,12 @@ export class DirectMessageRoomType extends RoomTypeConfig { } getUserStatusText(roomId) { - const userId = roomId.replace(Meteor.userId(), ''); - const userData = Users.findOne({ _id: userId }); - if (userData && userData.statusText) { - return userData.statusText; + const subscription = Subscriptions.findOne({ rid: roomId }); + if (subscription == null) { + return; } + + return Session.get(`user_${ subscription.name }_status_text`); } getDisplayName(room) { diff --git a/app/lib/server/functions/index.js b/app/lib/server/functions/index.js index b5a307a4e148..aa4cb4c0df62 100644 --- a/app/lib/server/functions/index.js +++ b/app/lib/server/functions/index.js @@ -23,7 +23,7 @@ export { saveUser } from './saveUser'; export { sendMessage } from './sendMessage'; export { setEmail } from './setEmail'; export { setRealName, _setRealName } from './setRealName'; -export { setStatusMessage, _setStatusMessage } from './setStatusMessage'; +export { setStatusText, _setStatusText } from './setStatusText'; export { setUserAvatar } from './setUserAvatar'; export { _setUsername, setUsername } from './setUsername'; export { unarchiveRoom } from './unarchiveRoom'; diff --git a/app/lib/server/functions/saveUser.js b/app/lib/server/functions/saveUser.js index ea9c8772b6bc..081dc7755996 100644 --- a/app/lib/server/functions/saveUser.js +++ b/app/lib/server/functions/saveUser.js @@ -10,7 +10,7 @@ import { settings } from '../../../settings'; import PasswordPolicy from '../lib/PasswordPolicyClass'; import { validateEmailDomain } from '../lib'; -import { checkEmailAvailability, checkUsernameAvailability, setUserAvatar, setEmail, setRealName, setUsername, setStatusMessage } from '.'; +import { checkEmailAvailability, checkUsernameAvailability, setUserAvatar, setEmail, setRealName, setUsername, setStatusText } from '.'; const passwordPolicy = new PasswordPolicy(); @@ -256,7 +256,7 @@ export const saveUser = function(userId, userData) { } if (typeof userData.statusText === 'string') { - setStatusMessage(userData._id, userData.statusText); + setStatusText(userData._id, userData.statusText); } if (userData.email) { diff --git a/app/lib/server/functions/setStatusMessage.js b/app/lib/server/functions/setStatusMessage.js deleted file mode 100644 index ceae68be66f8..000000000000 --- a/app/lib/server/functions/setStatusMessage.js +++ /dev/null @@ -1,45 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import s from 'underscore.string'; - -import { Users } from '../../../models'; -import { Notifications } from '../../../notifications'; -import { hasPermission } from '../../../authorization'; -import { RateLimiter } from '../lib'; - -export const _setStatusMessage = function(userId, statusMessage) { - statusMessage = s.trim(statusMessage); - if (statusMessage.length > 120) { - statusMessage = statusMessage.substr(0, 120); - } - - if (!userId) { - return false; - } - - const user = Users.findOneById(userId); - - // User already has desired statusMessage, return - if (user.statusText === statusMessage) { - return user; - } - - // Set new statusMessage - Users.updateStatusText(user._id, statusMessage); - user.statusText = statusMessage; - - Notifications.notifyLogged('Users:StatusMessageChanged', { - _id: user._id, - name: user.name, - username: user.username, - statusText: user.statusText, - }); - - return true; -}; - -export const setStatusMessage = RateLimiter.limitFunction(_setStatusMessage, 1, 60000, { - 0() { - // Administrators have permission to change others status, so don't limit those - return !Meteor.userId() || !hasPermission(Meteor.userId(), 'edit-other-user-info'); - }, -}); diff --git a/app/lib/server/functions/setStatusText.js b/app/lib/server/functions/setStatusText.js new file mode 100644 index 000000000000..c852f21a6ab0 --- /dev/null +++ b/app/lib/server/functions/setStatusText.js @@ -0,0 +1,53 @@ +import { Meteor } from 'meteor/meteor'; +import s from 'underscore.string'; + +import { Users } from '../../../models'; +import { Notifications } from '../../../notifications'; +import { hasPermission } from '../../../authorization'; +import { RateLimiter } from '../lib'; + +// mirror of object in /imports/startup/client/listenActiveUsers.js - keep updated +const STATUS_MAP = { + offline: 0, + online: 1, + away: 2, + busy: 3, +}; + +export const _setStatusText = function(userId, statusText) { + statusText = s.trim(statusText); + if (statusText.length > 120) { + statusText = statusText.substr(0, 120); + } + + if (!userId) { + return false; + } + + const user = Users.findOneById(userId); + + // User already has desired statusText, return + if (user.statusText === statusText) { + return user; + } + + // Set new statusText + Users.updateStatusText(user._id, statusText); + user.statusText = statusText; + + Notifications.notifyLogged('user-status', [ + user._id, + user.username, + STATUS_MAP[user.status], + statusText, + ]); + + return true; +}; + +export const setStatusText = RateLimiter.limitFunction(_setStatusText, 1, 60000, { + 0() { + // Administrators have permission to change others status, so don't limit those + return !Meteor.userId() || !hasPermission(Meteor.userId(), 'edit-other-user-info'); + }, +}); diff --git a/app/ui-flextab/client/tabs/userInfo.js b/app/ui-flextab/client/tabs/userInfo.js index 68a68e265cf4..f27a7c468f95 100644 --- a/app/ui-flextab/client/tabs/userInfo.js +++ b/app/ui-flextab/client/tabs/userInfo.js @@ -83,7 +83,7 @@ Template.userInfo.helpers({ userStatus() { const user = Template.instance().user.get(); const userStatus = Session.get(`user_${ user.username }_status`); - return userStatus || 'offline'; + return userStatus || TAPi18n.__('offline'); }, userStatusText() { @@ -92,7 +92,8 @@ Template.userInfo.helpers({ } const user = Template.instance().user.get(); - return t(Session.get(`user_${ user.username }_status`)); + const userStatus = Session.get(`user_${ user.username }_status`); + return userStatus || TAPi18n.__('offline'); }, email() { diff --git a/app/ui-sidenav/client/sidebarHeader.js b/app/ui-sidenav/client/sidebarHeader.js index ea81eb21761b..ebdc9137ae88 100644 --- a/app/ui-sidenav/client/sidebarHeader.js +++ b/app/ui-sidenav/client/sidebarHeader.js @@ -295,7 +295,7 @@ Template.sidebarHeader.helpers({ }; } return id && Meteor.users.findOne(id, { fields: { - username: 1, status: 1, + username: 1, status: 1, statusText: 1, } }); }, toolbarButtons() { @@ -317,9 +317,10 @@ Template.sidebarHeader.events({ if (!(Meteor.userId() == null && settings.get('Accounts_AllowAnonymousRead'))) { const user = Meteor.user(); + const statusText = user.statusText || t(user.status); + const userStatusList = Object.keys(userStatus.list).map((key) => { const status = userStatus.list[key]; - const customName = status.localizeName ? null : status.name; const name = status.localizeName ? t(status.name) : status.name; const modifier = status.statusType || user.status; @@ -327,12 +328,10 @@ Template.sidebarHeader.events({ icon: 'circle', name, modifier, - action: () => setStatus(status.statusType, customName), + action: () => setStatus(status.statusType, statusText), }; }); - const statusText = user.statusText || t(user.status); - userStatusList.push({ icon: 'edit', name: t('Edit_Status'), diff --git a/app/ui/client/components/header/headerRoom.js b/app/ui/client/components/header/headerRoom.js index 7e78dae45b8b..dec71cff6eb7 100644 --- a/app/ui/client/components/header/headerRoom.js +++ b/app/ui/client/components/header/headerRoom.js @@ -28,6 +28,11 @@ const getUserStatus = (id) => { return roomTypes.getUserStatus(roomData.t, id) || 'offline'; }; +const getUserStatusText = (id) => { + const roomData = Session.get(`roomData${ id }`); + return roomTypes.getUserStatusText(roomData.t, id) || 'offline'; +}; + Template.headerRoom.helpers({ back() { return Template.instance().data.back; @@ -115,14 +120,7 @@ Template.headerRoom.helpers({ }, userStatusText() { - const roomData = Session.get(`roomData${ this._id }`); - const statusText = roomTypes.getUserStatusText(roomData.t, this._id); - - if (s.trim(statusText)) { - return statusText; - } - - return t(getUserStatus(this._id)); + return getUserStatusText(this._id); }, showToggleFavorite() { diff --git a/app/user-status/server/methods/setUserStatus.js b/app/user-status/server/methods/setUserStatus.js index 3ea7d4e07ff9..ebda968e3a8b 100644 --- a/app/user-status/server/methods/setUserStatus.js +++ b/app/user-status/server/methods/setUserStatus.js @@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { check } from 'meteor/check'; import { settings } from '../../../settings'; -import { RateLimiter, setStatusMessage } from '../../../lib'; +import { RateLimiter, setStatusText } from '../../../lib'; Meteor.methods({ setUserStatus(statusType, statusText) { @@ -20,7 +20,7 @@ Meteor.methods({ } const userId = Meteor.userId(); - setStatusMessage(userId, statusText); + setStatusText(userId, statusText); } }, }); diff --git a/imports/startup/client/listenActiveUsers.js b/imports/startup/client/listenActiveUsers.js index 19a33ff66632..a559034926af 100644 --- a/imports/startup/client/listenActiveUsers.js +++ b/imports/startup/client/listenActiveUsers.js @@ -86,13 +86,13 @@ Tracker.autorun(() => { }); Meteor.startup(function() { - Notifications.onLogged('user-status', ([_id, username, status]) => { + Notifications.onLogged('user-status', ([_id, username, status, statusText]) => { // only set after first request completed if (lastStatusChange) { lastStatusChange = new Date(); } - saveUser({ _id, username, status: STATUS_MAP[status] }, true); + saveUser({ _id, username, status: STATUS_MAP[status], statusText }, true); }); Notifications.onLogged('Users:NameChanged', ({ _id, username }) => { diff --git a/imports/users-presence/server/activeUsers.js b/imports/users-presence/server/activeUsers.js index 142a678db16b..cd076842b8f5 100644 --- a/imports/users-presence/server/activeUsers.js +++ b/imports/users-presence/server/activeUsers.js @@ -14,6 +14,7 @@ UserPresenceEvents.on('setUserStatus', (user, status/* , statusConnection*/) => const { _id, username, + statusText, } = user; // since this callback can be called by only one instance in the cluster @@ -22,5 +23,6 @@ UserPresenceEvents.on('setUserStatus', (user, status/* , statusConnection*/) => _id, username, STATUS_MAP[status], + statusText, ]); }); diff --git a/server/publications/spotlight.js b/server/publications/spotlight.js index 93e313facf6c..0da321b49dfd 100644 --- a/server/publications/spotlight.js +++ b/server/publications/spotlight.js @@ -60,6 +60,7 @@ Meteor.methods({ username: 1, name: 1, status: 1, + statusText: 1, }, sort: {}, }; From fda62049b833b9fc3c8c5ede51769bf0784fa756 Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Sat, 22 Jun 2019 01:56:30 -0400 Subject: [PATCH 02/12] eslint fixes --- app/lib/lib/roomTypes/direct.js | 2 +- app/ui-flextab/client/tabs/userInfo.js | 2 +- app/ui/client/components/header/headerRoom.js | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/lib/lib/roomTypes/direct.js b/app/lib/lib/roomTypes/direct.js index c5b163bbb916..8499332c8918 100644 --- a/app/lib/lib/roomTypes/direct.js +++ b/app/lib/lib/roomTypes/direct.js @@ -1,7 +1,7 @@ import { Meteor } from 'meteor/meteor'; import { Session } from 'meteor/session'; -import { ChatRoom, Subscriptions, Users } from '../../../models'; +import { ChatRoom, Subscriptions } from '../../../models'; import { openRoom } from '../../../ui-utils'; import { getUserPreference, RoomTypeConfig, RoomTypeRouteConfig, RoomSettingsEnum, UiTextContext } from '../../../utils'; import { hasPermission, hasAtLeastOnePermission } from '../../../authorization'; diff --git a/app/ui-flextab/client/tabs/userInfo.js b/app/ui-flextab/client/tabs/userInfo.js index f27a7c468f95..1ee515ef3fe8 100644 --- a/app/ui-flextab/client/tabs/userInfo.js +++ b/app/ui-flextab/client/tabs/userInfo.js @@ -8,7 +8,7 @@ import moment from 'moment'; import { DateFormat } from '../../../lib'; import { popover } from '../../../ui-utils'; -import { t, templateVarHandler } from '../../../utils'; +import { templateVarHandler } from '../../../utils'; import { RoomRoles, UserRoles, Roles } from '../../../models'; import { settings } from '../../../settings'; import FullUser from '../../../models/client/models/FullUser'; diff --git a/app/ui/client/components/header/headerRoom.js b/app/ui/client/components/header/headerRoom.js index dec71cff6eb7..674c7e782b4f 100644 --- a/app/ui/client/components/header/headerRoom.js +++ b/app/ui/client/components/header/headerRoom.js @@ -4,7 +4,6 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { Session } from 'meteor/session'; import { Template } from 'meteor/templating'; import { FlowRouter } from 'meteor/kadira:flow-router'; -import s from 'underscore.string'; import { t, roomTypes, handleError } from '../../../../utils'; import { TabBar, fireGlobalEvent, call } from '../../../../ui-utils'; From 01aa6359d9a900083839dc261f5e99baefb860b2 Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Sat, 22 Jun 2019 02:37:07 -0400 Subject: [PATCH 03/12] Fixed slash command for changing status --- app/slashcommands-status/lib/status.js | 40 ++++++++++++-------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/app/slashcommands-status/lib/status.js b/app/slashcommands-status/lib/status.js index 8188ed41a598..32d7c7d7a912 100644 --- a/app/slashcommands-status/lib/status.js +++ b/app/slashcommands-status/lib/status.js @@ -8,35 +8,33 @@ import { Notifications } from '../../notifications'; function Status(command, params, item) { if (command === 'status') { - if ((Meteor.isClient && hasPermission('edit-other-user-info')) || (Meteor.isServer && hasPermission(Meteor.userId(), 'edit-other-user-info'))) { - const user = Meteor.users.findOne(Meteor.userId()); + const user = Meteor.users.findOne(Meteor.userId()); - Meteor.call('setUserStatus', null, params, (err) => { - if (err) { - if (Meteor.isClient) { - return handleError(err); - } - - if (err.error === 'error-not-allowed') { - Notifications.notifyUser(Meteor.userId(), 'message', { - _id: Random.id(), - rid: item.rid, - ts: new Date(), - msg: TAPi18n.__('StatusMessage_Change_Disabled', null, user.language), - }); - } + Meteor.call('setUserStatus', null, params, (err) => { + if (err) { + if (Meteor.isClient) { + return handleError(err); + } - throw err; - } else { + if (err.error === 'error-not-allowed') { Notifications.notifyUser(Meteor.userId(), 'message', { _id: Random.id(), rid: item.rid, ts: new Date(), - msg: TAPi18n.__('StatusMessage_Changed_Successfully', null, user.language), + msg: TAPi18n.__('StatusMessage_Change_Disabled', null, user.language), }); } - }); - } + + throw err; + } else { + Notifications.notifyUser(Meteor.userId(), 'message', { + _id: Random.id(), + rid: item.rid, + ts: new Date(), + msg: TAPi18n.__('StatusMessage_Changed_Successfully', null, user.language), + }); + } + }); } } From 316aff4334af68862a0a11bcb2a09676233bf52f Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Sat, 22 Jun 2019 02:39:04 -0400 Subject: [PATCH 04/12] lint --- app/slashcommands-status/lib/status.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/slashcommands-status/lib/status.js b/app/slashcommands-status/lib/status.js index 32d7c7d7a912..a03a9e516117 100644 --- a/app/slashcommands-status/lib/status.js +++ b/app/slashcommands-status/lib/status.js @@ -3,7 +3,6 @@ import { TAPi18n } from 'meteor/tap:i18n'; import { Random } from 'meteor/random'; import { handleError, slashCommands } from '../../utils'; -import { hasPermission } from '../../authorization'; import { Notifications } from '../../notifications'; function Status(command, params, item) { From 7d8d7bc2b0e1fe6247fe8f11eebd6a91dcd222b0 Mon Sep 17 00:00:00 2001 From: Will Reiske Date: Sat, 22 Jun 2019 03:06:21 -0400 Subject: [PATCH 05/12] Fixed the "name is required" issue --- app/user-status/client/admin/userStatusEdit.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/user-status/client/admin/userStatusEdit.html b/app/user-status/client/admin/userStatusEdit.html index d0ac4103511b..70a4ff47cd28 100644 --- a/app/user-status/client/admin/userStatusEdit.html +++ b/app/user-status/client/admin/userStatusEdit.html @@ -13,7 +13,7 @@

{{_ "Custom_User_Status_Add"}}

@@ -22,7 +22,7 @@

{{_ "Custom_User_Status_Add"}}