diff --git a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_score_direction.test.js b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_score_direction.test.js index 192808ca8b..8ac329b1b9 100644 --- a/test/api/v3/integration/tasks/groups/POST-group_tasks_id_score_direction.test.js +++ b/test/api/v3/integration/tasks/groups/POST-group_tasks_id_score_direction.test.js @@ -34,6 +34,10 @@ describe('POST /tasks/:id/score/:direction', () => { }); it('prevents user from scoring a task that needs to be approved', async () => { + await user.update({ + 'preferences.language': 'cs', + }); + let memberTasks = await member.get('/tasks/user'); let syncedTask = find(memberTasks, findAssignedTask); @@ -52,7 +56,7 @@ describe('POST /tasks/:id/score/:direction', () => { expect(user.notifications[0].data.message).to.equal(t('userHasRequestedTaskApproval', { user: member.auth.local.username, taskName: updatedTask.text, - })); + }, 'cs')); // This test only works if we have the notification translated expect(user.notifications[0].data.groupId).to.equal(guild._id); expect(updatedTask.group.approval.requested).to.equal(true); diff --git a/test/api/v3/unit/models/group.test.js b/test/api/v3/unit/models/group.test.js index 04b545d45e..38695730db 100644 --- a/test/api/v3/unit/models/group.test.js +++ b/test/api/v3/unit/models/group.test.js @@ -809,6 +809,20 @@ describe('Group Model', () => { expect(party.chat).to.have.a.lengthOf(200); }); + it('cuts down chat to 400 messages when group is subcribed', () => { + party.purchased.plan.customerId = 'test-customer-id'; + + for (let i = 0; i < 420; i++) { + party.chat.push({ text: 'a message' }); + } + + expect(party.chat).to.have.a.lengthOf(420); + + party.sendChat('message'); + + expect(party.chat).to.have.a.lengthOf(400); + }); + it('updates users about new messages in party', () => { party.sendChat('message'); diff --git a/test/client-old/spec/controllers/groupTaskActionsCtrlSpec.js b/test/client-old/spec/controllers/groupTaskActionsCtrlSpec.js new file mode 100644 index 0000000000..1e2b5cc7e1 --- /dev/null +++ b/test/client-old/spec/controllers/groupTaskActionsCtrlSpec.js @@ -0,0 +1,63 @@ +describe('Group Tasks Meta Actions Controller', () => { + let rootScope, scope, user, userSerivce; + + beforeEach(() => { + module(function($provide) { + $provide.value('User', {}); + }); + + inject(($rootScope, $controller) => { + rootScope = $rootScope; + + user = specHelper.newUser(); + user._id = "unique-user-id"; + userSerivce = {user: user}; + + scope = $rootScope.$new(); + + scope.task = { + group: { + assignedUsers: [], + approval: { + required: false, + } + }, + }; + scope.task._edit = angular.copy(scope.task); + + $controller('GroupTaskActionsCtrl', {$scope: scope, User: userSerivce}); + }); + }); + + describe('toggleTaskRequiresApproval', function () { + it('toggles task approval required field from false to true', function () { + scope.toggleTaskRequiresApproval(); + expect(scope.task._edit.group.approval.required).to.be.true; + }); + + it('toggles task approval required field from true to false', function () { + scope.task._edit.group.approval.required = true; + scope.toggleTaskRequiresApproval(); + expect(scope.task._edit.group.approval.required).to.be.false; + }); + }); + + + describe('assign events', function () { + it('adds a group member to assigned users on "addedGroupMember" event ', () => { + var testId = 'test-id'; + rootScope.$broadcast('addedGroupMember', testId); + expect(scope.task.group.assignedUsers).to.contain(testId); + expect(scope.task._edit.group.assignedUsers).to.contain(testId); + }); + + it('removes a group member to assigned users on "addedGroupMember" event ', () => { + var testId = 'test-id'; + scope.task.group.assignedUsers.push(testId); + scope.task._edit.group.assignedUsers.push(testId); + rootScope.$broadcast('removedGroupMember', testId); + expect(scope.task.group.assignedUsers).to.not.contain(testId); + expect(scope.task._edit.group.assignedUsers).to.not.contain(testId); + }); + }); +}); diff --git a/test/client-old/spec/controllers/groupTasksMetaActionCtrlSpec.js b/test/client-old/spec/controllers/groupTasksMetaActionCtrlSpec.js new file mode 100644 index 0000000000..5fb30739bd --- /dev/null +++ b/test/client-old/spec/controllers/groupTasksMetaActionCtrlSpec.js @@ -0,0 +1,42 @@ +describe('Group Task Actions Controller', () => { + let scope, user, userSerivce; + + beforeEach(() => { + module(function($provide) { + $provide.value('User', {}); + }); + + inject(($rootScope, $controller) => { + user = specHelper.newUser(); + user._id = "unique-user-id"; + userSerivce = {user: user}; + userSerivce.sync = sandbox.stub(); + + scope = $rootScope.$new(); + + $controller('GroupTaskMetaActionsCtrl', {$scope: scope, User: userSerivce}); + + scope.task = { + group: { + assignedUsers: [], + }, + }; + }); + }); + + describe('claim', () => { + beforeEach(() => { + sandbox.stub(window, 'confirm').returns(true); + }); + + it('adds user to assigned users of scope task ', () => { + scope.claim(); + expect(scope.task.group.assignedUsers).to.contain(user._id); + }); + + it('syncs user tasks ', () => { + scope.claim(); + expect(userSerivce.sync).to.be.calledOnce; + }); + }); +}); diff --git a/test/client-old/spec/karma.conf.js b/test/client-old/spec/karma.conf.js index 826a9d67ae..3cceb2404b 100644 --- a/test/client-old/spec/karma.conf.js +++ b/test/client-old/spec/karma.conf.js @@ -44,6 +44,7 @@ module.exports = function karmaConfig (config) { '../../../website/client-old/js/filters/**/*.js', '../../../website/client-old/js/directives/**/*.js', '../../../website/client-old/js/controllers/**/*.js', + '../../../website/client-old/js/components/**/*.js', '../../../test/client-old/spec/specHelper.js', '../../../test/client-old/spec/**/*.js', diff --git a/website/client-old/js/components/groupMembersAutocomplete/groupMembersAutocompleteDirective.js b/website/client-old/js/components/groupMembersAutocomplete/groupMembersAutocompleteDirective.js index cb45e9b9f7..48a26d4e8b 100644 --- a/website/client-old/js/components/groupMembersAutocomplete/groupMembersAutocompleteDirective.js +++ b/website/client-old/js/components/groupMembersAutocomplete/groupMembersAutocompleteDirective.js @@ -37,6 +37,7 @@ allowedTags: allowedTags, allowDuplicates: false, preserveCase: true, + delimeter: '|', placeholder: window.env.t('assignFieldPlaceholder'), onBeforeTagAdd: function(event, tag) { return confirm(window.env.t('confirmAddTag', {tag: tag})); diff --git a/website/client-old/js/components/groupTaskActions/groupTaskActionsController.js b/website/client-old/js/components/groupTaskActions/groupTaskActionsController.js index fc4a5d7276..43c19ade9d 100644 --- a/website/client-old/js/components/groupTaskActions/groupTaskActionsController.js +++ b/website/client-old/js/components/groupTaskActions/groupTaskActionsController.js @@ -3,16 +3,30 @@ habitrpg.controller('GroupTaskActionsCtrl', ['$scope', 'Shared', 'Tasks', 'User' $scope.assignedMembers = []; $scope.user = User.user; + // We must use a separate field here, because task.group is private. So, instead, we send this tmp field to alter the approval. $scope.task._edit.requiresApproval = false; if ($scope.task.group.approval.required) { $scope.task._edit.requiresApproval = $scope.task.group.approval.required; } + $scope.toggleTaskRequiresApproval = function () { + $scope.task._edit.group.approval.required = !$scope.task._edit.group.approval.required; + $scope.task._edit.requiresApproval = $scope.task._edit.group.approval.required; + } + $scope.$on('addedGroupMember', function(evt, userId) { + $scope.task.group.assignedUsers.push(userId); + if ($scope.task._edit) $scope.task._edit.group.assignedUsers.push(userId); Tasks.assignTask($scope.task.id, userId); }); $scope.$on('removedGroupMember', function(evt, userId) { + var index = $scope.task.group.assignedUsers.indexOf(userId); + $scope.task.group.assignedUsers.splice(index, 1); + if ($scope.task._edit) { + var index = $scope.task._edit.group.assignedUsers.indexOf(userId); + $scope.task._edit.group.assignedUsers.splice(index, 1); + } Tasks.unAssignTask($scope.task.id, userId); }); }]); diff --git a/website/client-old/js/components/groupTaskMetaActions/groupTaskMetaActionsController.js b/website/client-old/js/components/groupTaskMetaActions/groupTaskMetaActionsController.js index 03574b2443..05d8e65e55 100644 --- a/website/client-old/js/components/groupTaskMetaActions/groupTaskMetaActionsController.js +++ b/website/client-old/js/components/groupTaskMetaActions/groupTaskMetaActionsController.js @@ -7,6 +7,7 @@ habitrpg.controller('GroupTaskMetaActionsCtrl', ['$scope', 'Shared', 'Tasks', 'U if (!confirm("Are you sure you want to claim this task?")) return; Tasks.assignTask($scope.task.id, $scope.user._id); $scope.task.group.assignedUsers.push($scope.user._id); + User.sync(); }; $scope.userIsAssigned = function () { diff --git a/website/client-old/js/components/groupTasks/groupTasksController.js b/website/client-old/js/components/groupTasks/groupTasksController.js index 192c0ee9bd..a5a14c2a3f 100644 --- a/website/client-old/js/components/groupTasks/groupTasksController.js +++ b/website/client-old/js/components/groupTasks/groupTasksController.js @@ -177,7 +177,7 @@ habitrpg.controller('GroupTasksCtrl', ['$scope', 'Shared', 'Tasks', 'User', func var claimingUsers = []; task.group.assignedUsers.forEach(function (userId) { - claimingUsers.push(memberIdToProfileNameMap[userId]); + claimingUsers.push('"' + memberIdToProfileNameMap[userId] + '"'); }) if (claimingUsers.length > 0) content += window.env.t('claimedBy', {claimingUsers: claimingUsers.join(', ')}); diff --git a/website/client-old/js/controllers/notificationCtrl.js b/website/client-old/js/controllers/notificationCtrl.js index 9f1679150f..831b8f69e4 100644 --- a/website/client-old/js/controllers/notificationCtrl.js +++ b/website/client-old/js/controllers/notificationCtrl.js @@ -171,6 +171,7 @@ habitrpg.controller('NotificationCtrl', if (scoreTaskNotification) { Notification.markdown(scoreTaskNotification.data.message); User.score({params:{task: scoreTaskNotification.data.scoreTask, direction: "up"}}); + User.sync(); } }); } diff --git a/website/client-old/js/controllers/tasksCtrl.js b/website/client-old/js/controllers/tasksCtrl.js index 0e2a5c2851..2d5ddc1bed 100644 --- a/website/client-old/js/controllers/tasksCtrl.js +++ b/website/client-old/js/controllers/tasksCtrl.js @@ -109,6 +109,8 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N } else { $scope.score(task, "down"); } + + if (task.group && task.group.approval && task.group.approval.required && !task.group.approval.approved) task.completed = false; }; $scope.saveTask = function(task, stayOpen, isSaveAndClose) { diff --git a/website/common/locales/en/groups.json b/website/common/locales/en/groups.json index efb10b5d8e..bf6fdeaba6 100644 --- a/website/common/locales/en/groups.json +++ b/website/common/locales/en/groups.json @@ -247,7 +247,7 @@ "approvalsTitle": "Tasks Awaiting Approval", "upgradeTitle": "Upgrade", "blankApprovalsDescription": "When your group completes tasks that need your approval, they'll appear here! Adjust approval requirement settings under task editing.", - "userIsClamingTask": "<%= username %> has claimed \"<%= task %>\"", + "userIsClamingTask": "`<%= username %> has claimed \"<%= task %>\"`", "approvalRequested": "Approval Requested", "refreshApprovals": "Refresh Approvals", "refreshGroupTasks": "Refresh Group Tasks", diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js index a07282e1a3..a3732fd733 100644 --- a/website/server/controllers/api-v3/tasks.js +++ b/website/server/controllers/api-v3/tasks.js @@ -354,7 +354,7 @@ api.scoreTask = { message: res.t('userHasRequestedTaskApproval', { user: user.profile.name, taskName: task.text, - }), + }, groupLeader.preferences.language), groupId: group._id, }); diff --git a/website/server/models/group.js b/website/server/models/group.js index b41c9deac1..7f345b6da9 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -1,3 +1,4 @@ +import moment from 'moment'; import mongoose from 'mongoose'; import { model as User, @@ -392,7 +393,17 @@ schema.methods.sendChat = function sendChat (message, user) { let newMessage = chatDefaults(message, user); this.chat.unshift(newMessage); - this.chat.splice(200); + + const MAX_CHAT_COUNT = 200; + const MAX_SUBBED_GROUP_CHAT_COUNT = 400; + + let maxCount = MAX_CHAT_COUNT; + + if (this.isSubscribed()) { + maxCount = MAX_SUBBED_GROUP_CHAT_COUNT; + } + + this.chat.splice(maxCount); // do not send notifications for guilds with more than 5000 users and for the tavern if (NO_CHAT_NOTIFICATIONS.indexOf(this._id) !== -1 || this.memberCount > LARGE_GROUP_COUNT_MESSAGE_CUTOFF) { @@ -882,8 +893,7 @@ schema.methods.leave = async function leaveGroup (user, keep = 'keep-all') { let group = this; let update = {}; - let plan = group.purchased.plan; - if (group.memberCount <= 1 && group.privacy === 'private' && plan && plan.customerId && !plan.dateTerminated) { + if (group.memberCount <= 1 && group.privacy === 'private' && group.isSubscribed()) { throw new NotAuthorized(shared.i18n.t('cannotDeleteActiveGroup')); } @@ -1136,6 +1146,12 @@ schema.methods.removeTask = async function groupRemoveTask (task) { }, {multi: true}).exec(); }; +schema.methods.isSubscribed = function isSubscribed () { + let now = new Date(); + let plan = this.purchased.plan; + return plan && plan.customerId && (!plan.dateTerminated || moment(plan.dateTerminated).isAfter(now)); +}; + export let model = mongoose.model('Group', schema); // initialize tavern if !exists (fresh installs) diff --git a/website/views/options/social/groups/group-tasks-actions.jade b/website/views/options/social/groups/group-tasks-actions.jade index ce274cd345..38d256abd4 100644 --- a/website/views/options/social/groups/group-tasks-actions.jade +++ b/website/views/options/social/groups/group-tasks-actions.jade @@ -5,6 +5,6 @@ script(type='text/ng-template', id='partials/groups.tasks.actions.html') ul.priority-multiplier li {{requiresApproval}} - button(type='button', ng-class='{active: task._edit.requiresApproval==true}', - ng-click='task._edit.requiresApproval = !task._edit.requiresApproval') + button(type='button', ng-class='{active: task._edit.group.approval.required == true}', + ng-click='toggleTaskRequiresApproval()') =env.t('approvalRequired') diff --git a/website/views/shared/tasks/edit/advanced_options.jade b/website/views/shared/tasks/edit/advanced_options.jade index 763eb3c533..0cfc7b6c53 100644 --- a/website/views/shared/tasks/edit/advanced_options.jade +++ b/website/views/shared/tasks/edit/advanced_options.jade @@ -1,4 +1,4 @@ -div(ng-if='::task.type!="reward"') +div(ng-if='(task.type !== "reward") || (!obj.auth && obj.purchased && obj.purchased.active)') button.advanced-options-toggle.option-title.mega(type='button', ng-class='{active: task._edit._advanced}', ng-click='task._edit._advanced = !task._edit._advanced', tooltip=env.t('expandCollapse'))