mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
Merge branch 'develop' into release
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
import {
|
import {
|
||||||
validateItemPath,
|
validateItemPath,
|
||||||
getDefaultOwnedGear,
|
getDefaultOwnedGear,
|
||||||
|
castItemVal,
|
||||||
} from '../../../../../website/server/libs/items/utils';
|
} from '../../../../../website/server/libs/items/utils';
|
||||||
|
|
||||||
describe('Items Utils', () => {
|
describe('Items Utils', () => {
|
||||||
@@ -64,4 +65,49 @@ describe('Items Utils', () => {
|
|||||||
expect(validateItemPath('items.quests.invalid')).to.equal(false);
|
expect(validateItemPath('items.quests.invalid')).to.equal(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('castItemVal', () => {
|
||||||
|
it('returns the item val untouched if not an item path', () => {
|
||||||
|
expect(castItemVal('notitems.gear.owned.item', 'a string')).to.equal('a string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the item val untouched if an unsupported path', () => {
|
||||||
|
expect(castItemVal('items.gear.equipped.weapon', 'a string')).to.equal('a string');
|
||||||
|
expect(castItemVal('items.currentPet', 'a string')).to.equal('a string');
|
||||||
|
expect(castItemVal('items.special.snowball', 'a string')).to.equal('a string');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts values for pets paths to numbers', () => {
|
||||||
|
expect(castItemVal('items.pets.Wolf-CottonCandyPink', '5')).to.equal(5);
|
||||||
|
expect(castItemVal('items.pets.Wolf-Invalid', '5')).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts values for eggs paths to numbers', () => {
|
||||||
|
expect(castItemVal('items.eggs.LionCub', '5')).to.equal(5);
|
||||||
|
expect(castItemVal('items.eggs.Armadillo', '5')).to.equal(5);
|
||||||
|
expect(castItemVal('items.eggs.NotAnArmadillo', '5')).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts values for hatching potions paths to numbers', () => {
|
||||||
|
expect(castItemVal('items.hatchingPotions.Base', '5')).to.equal(5);
|
||||||
|
expect(castItemVal('items.hatchingPotions.StarryNight', '5')).to.equal(5);
|
||||||
|
expect(castItemVal('items.hatchingPotions.Invalid', '5')).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts values for food paths to numbers', () => {
|
||||||
|
expect(castItemVal('items.food.Cake_Base', '5')).to.equal(5);
|
||||||
|
expect(castItemVal('items.food.Cake_Invalid', '5')).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts values for mounts paths to numbers', () => {
|
||||||
|
expect(castItemVal('items.mounts.Cactus-Base', '5')).to.equal(5);
|
||||||
|
expect(castItemVal('items.mounts.Aether-Invisible', '5')).to.equal(5);
|
||||||
|
expect(castItemVal('items.mounts.Aether-Invalid', '5')).to.equal(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('converts values for quests paths to numbers', () => {
|
||||||
|
expect(castItemVal('items.quests.atom3', '5')).to.equal(5);
|
||||||
|
expect(castItemVal('items.quests.invalid', '5')).to.equal(5);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -60,4 +60,10 @@ describe('GET /inbox/messages', () => {
|
|||||||
|
|
||||||
expect(messages.length).to.equal(4);
|
expect(messages.length).to.equal(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('returns only the messages of one conversation', async () => {
|
||||||
|
const messages = await user.get(`/inbox/messages?conversation=${otherUser.id}`);
|
||||||
|
|
||||||
|
expect(messages.length).to.equal(3);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -63,6 +63,38 @@ describe('Groups DELETE /tasks/:id', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('removes deleted taskʾs approval pending notifications from managers', async () => {
|
||||||
|
await user.post(`/groups/${guild._id}/add-manager`, {
|
||||||
|
managerId: member2._id,
|
||||||
|
});
|
||||||
|
await user.put(`/tasks/${task._id}/`, {
|
||||||
|
requiresApproval: true,
|
||||||
|
});
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
await member2.sync();
|
||||||
|
expect(user.notifications.length).to.equal(2);
|
||||||
|
expect(user.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
|
||||||
|
expect(member2.notifications.length).to.equal(2);
|
||||||
|
expect(member2.notifications[1].type).to.equal('GROUP_TASK_APPROVAL');
|
||||||
|
|
||||||
|
await member2.del(`/tasks/${task._id}`);
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
await member2.sync();
|
||||||
|
|
||||||
|
expect(user.notifications.length).to.equal(1);
|
||||||
|
expect(member2.notifications.length).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('unlinks assigned user', async () => {
|
it('unlinks assigned user', async () => {
|
||||||
await user.del(`/tasks/${task._id}`);
|
await user.del(`/tasks/${task._id}`);
|
||||||
|
|
||||||
|
|||||||
@@ -53,18 +53,29 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
|||||||
|
|
||||||
it('approves an assigned user', async () => {
|
it('approves an assigned user', async () => {
|
||||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
await user.post(`/tasks/${task._id}/approve/${member._id}`);
|
|
||||||
|
|
||||||
let memberTasks = await member.get('/tasks/user');
|
let memberTasks = await member.get('/tasks/user');
|
||||||
let syncedTask = find(memberTasks, findAssignedTask);
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||||
|
|
||||||
await member.sync();
|
await member.sync();
|
||||||
|
|
||||||
expect(member.notifications.length).to.equal(2);
|
expect(member.notifications.length).to.equal(3);
|
||||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED');
|
expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED');
|
||||||
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
|
||||||
expect(member.notifications[1].type).to.equal('SCORED_TASK');
|
|
||||||
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||||
|
expect(member.notifications[2].type).to.equal('SCORED_TASK');
|
||||||
|
expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||||
|
|
||||||
|
memberTasks = await member.get('/tasks/user');
|
||||||
|
syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
expect(syncedTask.group.approval.approved).to.be.true;
|
expect(syncedTask.group.approval.approved).to.be.true;
|
||||||
expect(syncedTask.group.approval.approvingUser).to.equal(user._id);
|
expect(syncedTask.group.approval.approvingUser).to.equal(user._id);
|
||||||
@@ -77,18 +88,28 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
|
||||||
|
|
||||||
let memberTasks = await member.get('/tasks/user');
|
let memberTasks = await member.get('/tasks/user');
|
||||||
let syncedTask = find(memberTasks, findAssignedTask);
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
|
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||||
await member.sync();
|
await member.sync();
|
||||||
|
|
||||||
expect(member.notifications.length).to.equal(2);
|
expect(member.notifications.length).to.equal(3);
|
||||||
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVED');
|
expect(member.notifications[1].type).to.equal('GROUP_TASK_APPROVED');
|
||||||
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
|
||||||
expect(member.notifications[1].type).to.equal('SCORED_TASK');
|
|
||||||
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
expect(member.notifications[1].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||||
|
expect(member.notifications[2].type).to.equal('SCORED_TASK');
|
||||||
|
expect(member.notifications[2].data.message).to.equal(t('yourTaskHasBeenApproved', {taskText: task.text}));
|
||||||
|
|
||||||
|
memberTasks = await member.get('/tasks/user');
|
||||||
|
syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
expect(syncedTask.group.approval.approved).to.be.true;
|
expect(syncedTask.group.approval.approved).to.be.true;
|
||||||
expect(syncedTask.group.approval.approvingUser).to.equal(member2._id);
|
expect(syncedTask.group.approval.approvingUser).to.equal(member2._id);
|
||||||
@@ -132,6 +153,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||||
await expect(user.post(`/tasks/${task._id}/approve/${member._id}`))
|
await expect(user.post(`/tasks/${task._id}/approve/${member._id}`))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
@@ -141,6 +172,17 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('prevents approving a task if it is not waiting for approval', async () => {
|
||||||
|
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
|
||||||
|
await expect(user.post(`/tasks/${task._id}/approve/${member._id}`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalWasNotRequested'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('completes master task when single-completion task is approved', async () => {
|
it('completes master task when single-completion task is approved', async () => {
|
||||||
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
|
let sharedCompletionTask = await user.post(`/tasks/group/${guild._id}`, {
|
||||||
text: 'shared completion todo',
|
text: 'shared completion todo',
|
||||||
@@ -151,6 +193,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
|||||||
|
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||||
|
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||||
|
|
||||||
let groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`);
|
let groupTasks = await user.get(`/tasks/group/${guild._id}?type=completedTodos`);
|
||||||
@@ -172,6 +224,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
|||||||
|
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||||
|
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||||
|
|
||||||
let member2Tasks = await member2.get('/tasks/user');
|
let member2Tasks = await member2.get('/tasks/user');
|
||||||
@@ -193,6 +255,16 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
|||||||
|
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||||
|
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||||
|
|
||||||
let groupTasks = await user.get(`/tasks/group/${guild._id}`);
|
let groupTasks = await user.get(`/tasks/group/${guild._id}`);
|
||||||
@@ -214,6 +286,25 @@ describe('POST /tasks/:id/approve/:userId', () => {
|
|||||||
|
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member._id}`);
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/assign/${member2._id}`);
|
||||||
|
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
|
let member2Tasks = await member2.get('/tasks/user');
|
||||||
|
let member2SyncedTask = find(member2Tasks, findAssignedTask);
|
||||||
|
await expect(member2.post(`/tasks/${member2SyncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member._id}`);
|
||||||
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member2._id}`);
|
await user.post(`/tasks/${sharedCompletionTask._id}/approve/${member2._id}`);
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('marks as task as needing more work', async () => {
|
it('marks a task as needing more work', async () => {
|
||||||
const initialNotifications = member.notifications.length;
|
const initialNotifications = member.notifications.length;
|
||||||
|
|
||||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
@@ -77,7 +77,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
|
|||||||
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
||||||
|
|
||||||
// Check that the notification is correct
|
// Check that the notification is correct
|
||||||
expect(member.notifications.length).to.equal(initialNotifications + 1);
|
expect(member.notifications.length).to.equal(initialNotifications + 2);
|
||||||
const notification = member.notifications[member.notifications.length - 1];
|
const notification = member.notifications[member.notifications.length - 1];
|
||||||
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
|
|||||||
expect(syncedTask.group.approval.requested).to.equal(false);
|
expect(syncedTask.group.approval.requested).to.equal(false);
|
||||||
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
expect(syncedTask.group.approval.requestedDate).to.equal(undefined);
|
||||||
|
|
||||||
expect(member.notifications.length).to.equal(initialNotifications + 1);
|
expect(member.notifications.length).to.equal(initialNotifications + 2);
|
||||||
const notification = member.notifications[member.notifications.length - 1];
|
const notification = member.notifications[member.notifications.length - 1];
|
||||||
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
expect(notification.type).to.equal('GROUP_TASK_NEEDS_WORK');
|
||||||
|
|
||||||
@@ -167,6 +167,17 @@ describe('POST /tasks/:id/needs-work/:userId', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
await member2.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
await member2.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||||
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
await expect(user.post(`/tasks/${task._id}/needs-work/${member._id}`))
|
||||||
.to.eventually.be.rejected.and.to.eql({
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
|||||||
@@ -129,6 +129,13 @@ describe('POST /tasks/:id/score/:direction', () => {
|
|||||||
let memberTasks = await member.get('/tasks/user');
|
let memberTasks = await member.get('/tasks/user');
|
||||||
let syncedTask = find(memberTasks, findAssignedTask);
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskApprovalHasBeenRequested'),
|
||||||
|
});
|
||||||
|
|
||||||
await user.post(`/tasks/${task._id}/approve/${member._id}`);
|
await user.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||||
|
|
||||||
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||||
|
|||||||
@@ -113,6 +113,17 @@ describe('POST /tasks/:taskId/assign/:memberId', () => {
|
|||||||
expect(syncedTask).to.exist;
|
expect(syncedTask).to.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('sends a notification to assigned user', async () => {
|
||||||
|
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
await member.sync();
|
||||||
|
|
||||||
|
let groupTask = await user.get(`/tasks/group/${guild._id}`);
|
||||||
|
|
||||||
|
expect(member.notifications.length).to.equal(1);
|
||||||
|
expect(member.notifications[0].type).to.equal('GROUP_TASK_ASSIGNED');
|
||||||
|
expect(member.notifications[0].taskId).to.equal(groupTask._id);
|
||||||
|
});
|
||||||
|
|
||||||
it('assigns a task to multiple users', async () => {
|
it('assigns a task to multiple users', async () => {
|
||||||
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
await user.post(`/tasks/${task._id}/assign/${member2._id}`);
|
await user.post(`/tasks/${task._id}/assign/${member2._id}`);
|
||||||
|
|||||||
@@ -86,6 +86,13 @@ describe('POST /tasks/:taskId/unassign/:memberId', () => {
|
|||||||
expect(syncedTask).to.not.exist;
|
expect(syncedTask).to.not.exist;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('removes task assignment notification from unassigned user', async () => {
|
||||||
|
await user.post(`/tasks/${task._id}/unassign/${member._id}`);
|
||||||
|
|
||||||
|
await member.sync();
|
||||||
|
expect(member.notifications.length).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
it('unassigns a user and only that user from a task', async () => {
|
it('unassigns a user and only that user from a task', async () => {
|
||||||
await user.post(`/tasks/${task._id}/assign/${member2._id}`);
|
await user.post(`/tasks/${task._id}/assign/${member2._id}`);
|
||||||
|
|
||||||
|
|||||||
44
test/api/v4/inbox/GET-inbox-conversations.test.js
Normal file
44
test/api/v4/inbox/GET-inbox-conversations.test.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
} from '../../../helpers/api-integration/v4';
|
||||||
|
|
||||||
|
describe('GET /inbox/conversations', () => {
|
||||||
|
let user;
|
||||||
|
let otherUser;
|
||||||
|
let thirdUser;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
[user, otherUser, thirdUser] = await Promise.all([generateUser(), generateUser(), generateUser()]);
|
||||||
|
|
||||||
|
await otherUser.post('/members/send-private-message', {
|
||||||
|
toUserId: user.id,
|
||||||
|
message: 'first',
|
||||||
|
});
|
||||||
|
await user.post('/members/send-private-message', {
|
||||||
|
toUserId: otherUser.id,
|
||||||
|
message: 'second',
|
||||||
|
});
|
||||||
|
await user.post('/members/send-private-message', {
|
||||||
|
toUserId: thirdUser.id,
|
||||||
|
message: 'third',
|
||||||
|
});
|
||||||
|
await otherUser.post('/members/send-private-message', {
|
||||||
|
toUserId: user.id,
|
||||||
|
message: 'fourth',
|
||||||
|
});
|
||||||
|
|
||||||
|
// message to yourself
|
||||||
|
await user.post('/members/send-private-message', {
|
||||||
|
toUserId: user.id,
|
||||||
|
message: 'fifth',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the conversations', async () => {
|
||||||
|
const result = await user.get('/inbox/conversations');
|
||||||
|
|
||||||
|
expect(result.length).to.be.equal(3);
|
||||||
|
expect(result[0].user).to.be.equal(user.profile.name);
|
||||||
|
expect(result[0].username).to.be.equal(user.auth.local.username);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
b-modal#rebirth(:title="$t('modalAchievement')", size='md', :hide-footer="true", @hidden="reloadPage()")
|
b-modal#rebirth(:title="$t('modalAchievement')", size='md', :hide-footer="true")
|
||||||
.modal-body
|
.modal-body
|
||||||
.col-12
|
.col-12
|
||||||
// @TODO: +achievementAvatar('sun',0)
|
// @TODO: +achievementAvatar('sun',0)
|
||||||
@@ -41,9 +41,6 @@
|
|||||||
close () {
|
close () {
|
||||||
this.$root.$emit('bv::hide::modal', 'rebirth');
|
this.$root.$emit('bv::hide::modal', 'rebirth');
|
||||||
},
|
},
|
||||||
reloadPage () {
|
|
||||||
window.location.reload(true);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -376,6 +376,8 @@ export default {
|
|||||||
openMemberProgressModal (member) {
|
openMemberProgressModal (member) {
|
||||||
this.$root.$emit('habitica:challenge:member-progress', {
|
this.$root.$emit('habitica:challenge:member-progress', {
|
||||||
progressMemberId: member._id,
|
progressMemberId: member._id,
|
||||||
|
isLeader: this.isLeader,
|
||||||
|
isAdmin: this.isAdmin,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async exportChallengeCsv () {
|
async exportChallengeCsv () {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
b-modal#challenge-member-modal(title="User Progress", size='lg')
|
b-modal#challenge-member-modal(title="User Progress", size='lg')
|
||||||
.row.award-row
|
.row.award-row(v-if='isLeader || isAdmin')
|
||||||
.col-12.text-center
|
.col-12.text-center
|
||||||
button.btn.btn-primary(v-once, @click='closeChallenge()') {{ $t('awardWinners') }}
|
button.btn.btn-primary(v-once, @click='closeChallenge()') {{ $t('awardWinners') }}
|
||||||
.row
|
.row
|
||||||
@@ -37,12 +37,16 @@ export default {
|
|||||||
reward: [],
|
reward: [],
|
||||||
},
|
},
|
||||||
memberId: '',
|
memberId: '',
|
||||||
|
isLeader: false,
|
||||||
|
isAdmin: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
this.$root.$on('habitica:challenge:member-progress', (data) => {
|
this.$root.$on('habitica:challenge:member-progress', (data) => {
|
||||||
if (!data.progressMemberId) return;
|
if (!data.progressMemberId) return;
|
||||||
this.memberId = data.progressMemberId;
|
this.memberId = data.progressMemberId;
|
||||||
|
this.isLeader = data.isLeader;
|
||||||
|
this.isAdmin = data.isAdmin;
|
||||||
this.$root.$emit('bv::show::modal', 'challenge-member-modal');
|
this.$root.$emit('bv::show::modal', 'challenge-member-modal');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -16,54 +16,7 @@
|
|||||||
h1 {{ $t('groupTasksTitle') }}
|
h1 {{ $t('groupTasksTitle') }}
|
||||||
// @TODO: Abstract to component!
|
// @TODO: Abstract to component!
|
||||||
.col-12.col-md-4
|
.col-12.col-md-4
|
||||||
.input-group
|
|
||||||
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
|
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model="searchText")
|
||||||
.filter-panel(v-if="isFilterPanelOpen")
|
|
||||||
.tags-category(v-for="tagsType in tagsByType", v-if="tagsType.tags.length > 0", :key="tagsType.key")
|
|
||||||
.tags-header.col-12
|
|
||||||
strong(v-once) {{ $t(tagsType.key) }}
|
|
||||||
a.d-block(v-if="tagsType.key === 'tags' && !editingTags", @click="editTags()") {{ $t('editTags2') }}
|
|
||||||
.tags-list.container.col-12
|
|
||||||
.row(:class="{'no-gutters': !editingTags}")
|
|
||||||
template(v-if="editingTags && tagsType.key === 'tags'")
|
|
||||||
.col-12.col-md-6(v-for="(tag, tagIndex) in tagsSnap")
|
|
||||||
.inline-edit-input-group.tag-edit-item.input-group
|
|
||||||
input.tag-edit-input.inline-edit-input.form-control(type="text", :value="tag.name")
|
|
||||||
.input-group-append(@click="removeTag(tagIndex)")
|
|
||||||
.svg-icon.destroy-icon(v-html="icons.destroy")
|
|
||||||
.col-12.col-md-6
|
|
||||||
input.new-tag-item.edit-tag-item.inline-edit-input.form-control(type="text", :placeholder="$t('newTag')", @keydown.enter="addTag($event)", v-model="newTag")
|
|
||||||
template(v-else)
|
|
||||||
.col-12.col-md-6(v-for="(tag, tagIndex) in tagsType.tags")
|
|
||||||
.custom-control.custom-checkbox
|
|
||||||
input.custom-control-input(
|
|
||||||
type="checkbox",
|
|
||||||
:checked="isTagSelected(tag)",
|
|
||||||
@change="toggleTag(tag)",
|
|
||||||
:id="`tag-${tagsType.key}-${tagIndex}`",
|
|
||||||
)
|
|
||||||
label.custom-control-label(:for="`tag-${tagsType.key}-${tagIndex}`") {{ tag.name }}
|
|
||||||
|
|
||||||
.filter-panel-footer.clearfix
|
|
||||||
template(v-if="editingTags === true")
|
|
||||||
.text-center
|
|
||||||
a.mr-3.btn-filters-primary(@click="saveTags()", v-once) {{ $t('saveEdits') }}
|
|
||||||
a.btn-filters-secondary(@click="cancelTagsEditing()", v-once) {{ $t('cancel') }}
|
|
||||||
template(v-else)
|
|
||||||
.float-left
|
|
||||||
a.btn-filters-danger(@click="resetFilters()", v-once) {{ $t('resetFilters') }}
|
|
||||||
.float-right
|
|
||||||
a.mr-3.btn-filters-primary(@click="applyFilters()", v-once) {{ $t('applyFilters') }}
|
|
||||||
a.btn-filters-secondary(@click="closeFilterPanel()", v-once) {{ $t('cancel') }}
|
|
||||||
span.input-group-append
|
|
||||||
button.btn.btn-secondary.filter-button(
|
|
||||||
type="button",
|
|
||||||
@click="toggleFilterPanel()",
|
|
||||||
:class="{'filter-button-open': selectedTags.length > 0}",
|
|
||||||
)
|
|
||||||
.d-flex.align-items-center
|
|
||||||
span(v-once) {{ $t('filter') }}
|
|
||||||
.svg-icon.filter-icon(v-html="icons.filter")
|
|
||||||
.create-task-area.d-flex(v-if='canCreateTasks')
|
.create-task-area.d-flex(v-if='canCreateTasks')
|
||||||
transition(name="slide-tasks-btns")
|
transition(name="slide-tasks-btns")
|
||||||
.d-flex(v-if="openCreateBtn")
|
.d-flex(v-if="openCreateBtn")
|
||||||
@@ -99,10 +52,6 @@
|
|||||||
@import '~client/assets/scss/colors.scss';
|
@import '~client/assets/scss/colors.scss';
|
||||||
@import '~client/assets/scss/create-task.scss';
|
@import '~client/assets/scss/create-task.scss';
|
||||||
|
|
||||||
.user-tasks-page {
|
|
||||||
padding-top: 31px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tasks-navigation {
|
.tasks-navigation {
|
||||||
margin-bottom: 40px;
|
margin-bottom: 40px;
|
||||||
}
|
}
|
||||||
@@ -114,133 +63,6 @@
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
padding-top: 6px;
|
padding-top: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdown-icon-item .svg-icon {
|
|
||||||
width: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button.btn.btn-secondary.filter-button {
|
|
||||||
box-shadow: none;
|
|
||||||
border-radius: 2px;
|
|
||||||
border: 1px solid $gray-400 !important;
|
|
||||||
|
|
||||||
&:hover, &:active, &:focus, &.open {
|
|
||||||
box-shadow: none;
|
|
||||||
border-color: $purple-500 !important;
|
|
||||||
color: $gray-50 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.filter-button-open {
|
|
||||||
color: $purple-200 !important;
|
|
||||||
|
|
||||||
.filter-icon {
|
|
||||||
color: $purple-200 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-icon {
|
|
||||||
height: 10px;
|
|
||||||
width: 12px;
|
|
||||||
color: $gray-50;
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-panel {
|
|
||||||
position: absolute;
|
|
||||||
padding-left: 24px;
|
|
||||||
padding-right: 24px;
|
|
||||||
max-width: 40vw;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 9999;
|
|
||||||
background: $white;
|
|
||||||
border-radius: 2px;
|
|
||||||
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
|
|
||||||
top: 44px;
|
|
||||||
left: 20vw;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.43;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
.tags-category {
|
|
||||||
border-bottom: 1px solid $gray-600;
|
|
||||||
padding-bottom: 24px;
|
|
||||||
padding-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tags-header {
|
|
||||||
flex-basis: 96px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 1.33;
|
|
||||||
color: $blue-10;
|
|
||||||
margin-top: 4px;
|
|
||||||
|
|
||||||
&:focus, &:hover, &:active {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-edit-input {
|
|
||||||
border-bottom: 1px solid $gray-500 !important;
|
|
||||||
|
|
||||||
&:focus, &:focus ~ .input-group-append {
|
|
||||||
border-color: $purple-500 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-tag-item {
|
|
||||||
width: 100%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center left 10px;
|
|
||||||
border-bottom: 1px solid $gray-500 !important;
|
|
||||||
background-size: 10px 10px;
|
|
||||||
padding-left: 40px;
|
|
||||||
background-image: url(~client/assets/svg/for-css/positive.svg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tag-edit-item .input-group-append {
|
|
||||||
border-bottom: 1px solid $gray-500 !important;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
border-color: $purple-500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-control-label {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-panel-footer {
|
|
||||||
padding-top: 16px;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
&:focus, &:hover, &:active {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-filters-danger {
|
|
||||||
color: $red-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-filters-primary {
|
|
||||||
color: $blue-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-filters-secondary {
|
|
||||||
color: $gray-300;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-label {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
base-notification(
|
||||||
|
:can-remove="canRemove",
|
||||||
|
:has-icon="false",
|
||||||
|
:notification="notification",
|
||||||
|
:read-after-click="true",
|
||||||
|
@click="action",
|
||||||
|
)
|
||||||
|
div(slot="content", v-html="notification.data.message")
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseNotification from './base';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['notification', 'canRemove'],
|
||||||
|
components: {
|
||||||
|
BaseNotification,
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
action () {
|
||||||
|
this.$router.push({ name: 'tasks'});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -87,6 +87,7 @@ import CHALLENGE_INVITATION from './notifications/challengeInvitation';
|
|||||||
import QUEST_INVITATION from './notifications/questInvitation';
|
import QUEST_INVITATION from './notifications/questInvitation';
|
||||||
import GROUP_TASK_APPROVAL from './notifications/groupTaskApproval';
|
import GROUP_TASK_APPROVAL from './notifications/groupTaskApproval';
|
||||||
import GROUP_TASK_APPROVED from './notifications/groupTaskApproved';
|
import GROUP_TASK_APPROVED from './notifications/groupTaskApproved';
|
||||||
|
import GROUP_TASK_ASSIGNED from './notifications/groupTaskAssigned';
|
||||||
import UNALLOCATED_STATS_POINTS from './notifications/unallocatedStatsPoints';
|
import UNALLOCATED_STATS_POINTS from './notifications/unallocatedStatsPoints';
|
||||||
import NEW_MYSTERY_ITEMS from './notifications/newMysteryItems';
|
import NEW_MYSTERY_ITEMS from './notifications/newMysteryItems';
|
||||||
import CARD_RECEIVED from './notifications/cardReceived';
|
import CARD_RECEIVED from './notifications/cardReceived';
|
||||||
@@ -102,7 +103,7 @@ export default {
|
|||||||
// One component for each type
|
// One component for each type
|
||||||
NEW_STUFF, GROUP_TASK_NEEDS_WORK,
|
NEW_STUFF, GROUP_TASK_NEEDS_WORK,
|
||||||
GUILD_INVITATION, PARTY_INVITATION, CHALLENGE_INVITATION,
|
GUILD_INVITATION, PARTY_INVITATION, CHALLENGE_INVITATION,
|
||||||
QUEST_INVITATION, GROUP_TASK_APPROVAL, GROUP_TASK_APPROVED,
|
QUEST_INVITATION, GROUP_TASK_APPROVAL, GROUP_TASK_APPROVED, GROUP_TASK_ASSIGNED,
|
||||||
UNALLOCATED_STATS_POINTS, NEW_MYSTERY_ITEMS, CARD_RECEIVED,
|
UNALLOCATED_STATS_POINTS, NEW_MYSTERY_ITEMS, CARD_RECEIVED,
|
||||||
NEW_INBOX_MESSAGE, NEW_CHAT_MESSAGE,
|
NEW_INBOX_MESSAGE, NEW_CHAT_MESSAGE,
|
||||||
WorldBoss: WORLD_BOSS,
|
WorldBoss: WORLD_BOSS,
|
||||||
@@ -118,7 +119,7 @@ export default {
|
|||||||
openStatus: undefined,
|
openStatus: undefined,
|
||||||
actionableNotifications: [
|
actionableNotifications: [
|
||||||
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
|
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
|
||||||
'QUEST_INVITATION', 'GROUP_TASK_NEEDS_WORK',
|
'QUEST_INVITATION', 'GROUP_TASK_NEEDS_WORK', 'GROUP_TASK_APPROVAL',
|
||||||
],
|
],
|
||||||
// A list of notifications handled by this component,
|
// A list of notifications handled by this component,
|
||||||
// listed in the order they should appear in the notifications panel.
|
// listed in the order they should appear in the notifications panel.
|
||||||
@@ -126,7 +127,7 @@ export default {
|
|||||||
handledNotifications: [
|
handledNotifications: [
|
||||||
'NEW_STUFF', 'GROUP_TASK_NEEDS_WORK',
|
'NEW_STUFF', 'GROUP_TASK_NEEDS_WORK',
|
||||||
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
|
'GUILD_INVITATION', 'PARTY_INVITATION', 'CHALLENGE_INVITATION',
|
||||||
'QUEST_INVITATION', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED',
|
'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED',
|
||||||
'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
|
'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
|
||||||
'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS',
|
'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS',
|
||||||
'VERIFY_USERNAME',
|
'VERIFY_USERNAME',
|
||||||
|
|||||||
@@ -132,11 +132,6 @@ const NOTIFICATIONS = {
|
|||||||
label: ($t) => `${$t('achievement')}: ${$t('gearAchievementNotification')}`,
|
label: ($t) => `${$t('achievement')}: ${$t('gearAchievementNotification')}`,
|
||||||
modalId: 'ultimate-gear',
|
modalId: 'ultimate-gear',
|
||||||
},
|
},
|
||||||
REBIRTH_ACHIEVEMENT: {
|
|
||||||
label: ($t) => `${$t('achievement')}: ${$t('rebirthBegan')}`,
|
|
||||||
achievement: true,
|
|
||||||
modalId: 'rebirth',
|
|
||||||
},
|
|
||||||
GUILD_JOINED_ACHIEVEMENT: {
|
GUILD_JOINED_ACHIEVEMENT: {
|
||||||
label: ($t) => `${$t('achievement')}: ${$t('joinedGuild')}`,
|
label: ($t) => `${$t('achievement')}: ${$t('joinedGuild')}`,
|
||||||
achievement: true,
|
achievement: true,
|
||||||
@@ -360,18 +355,7 @@ export default {
|
|||||||
this.playSound(config.sound);
|
this.playSound(config.sound);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'REBIRTH_ACHIEVEMENT') {
|
if (forceToModal) {
|
||||||
// reload if the user hasn't clicked on the notification
|
|
||||||
const timeOut = setTimeout(() => {
|
|
||||||
window.location.reload(true);
|
|
||||||
}, 60000);
|
|
||||||
|
|
||||||
this.text(config.label(this.$t), () => {
|
|
||||||
// prevent the current reload timeout
|
|
||||||
clearTimeout(timeOut);
|
|
||||||
this.$root.$emit('bv::show::modal', config.modalId);
|
|
||||||
}, false);
|
|
||||||
} else if (forceToModal) {
|
|
||||||
this.$root.$emit('bv::show::modal', config.modalId);
|
this.$root.$emit('bv::show::modal', config.modalId);
|
||||||
} else {
|
} else {
|
||||||
this.text(config.label(this.$t), () => {
|
this.text(config.label(this.$t), () => {
|
||||||
@@ -573,8 +557,11 @@ export default {
|
|||||||
}, this.user.preferences.suppressModals.streak);
|
}, this.user.preferences.suppressModals.streak);
|
||||||
this.playSound('Achievement_Unlocked');
|
this.playSound('Achievement_Unlocked');
|
||||||
break;
|
break;
|
||||||
case 'ULTIMATE_GEAR_ACHIEVEMENT':
|
|
||||||
case 'REBIRTH_ACHIEVEMENT':
|
case 'REBIRTH_ACHIEVEMENT':
|
||||||
|
this.playSound('Achievement_Unlocked');
|
||||||
|
this.$root.$emit('bv::show::modal', 'rebirth');
|
||||||
|
break;
|
||||||
|
case 'ULTIMATE_GEAR_ACHIEVEMENT':
|
||||||
case 'GUILD_JOINED_ACHIEVEMENT':
|
case 'GUILD_JOINED_ACHIEVEMENT':
|
||||||
case 'CHALLENGE_JOINED_ACHIEVEMENT':
|
case 'CHALLENGE_JOINED_ACHIEVEMENT':
|
||||||
case 'INVITED_FRIEND_ACHIEVEMENT':
|
case 'INVITED_FRIEND_ACHIEVEMENT':
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
|
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
|
||||||
) {{ $t('buyNow') }}
|
) {{ $t('buyNow') }}
|
||||||
|
|
||||||
div.limitedTime(v-if="item.event")
|
div.limitedTime(v-if="item.event && item.owned == null")
|
||||||
span.svg-icon.inline.icon-16(v-html="icons.clock")
|
span.svg-icon.inline.icon-16(v-html="icons.clock")
|
||||||
span.limitedString {{ limitedString }}
|
span.limitedString {{ limitedString }}
|
||||||
|
|
||||||
@@ -397,6 +397,10 @@
|
|||||||
|
|
||||||
this.$emit('buyPressed', this.item);
|
this.$emit('buyPressed', this.item);
|
||||||
this.hideDialog();
|
this.hideDialog();
|
||||||
|
|
||||||
|
if (this.item.key === 'rebirth_orb') {
|
||||||
|
window.location.reload(true);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
purchaseGems () {
|
purchaseGems () {
|
||||||
if (this.item.key === 'rebirth_orb') {
|
if (this.item.key === 'rebirth_orb') {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ div
|
|||||||
slot(name="itemBadge", :item="item", :emptyItem="emptyItem")
|
slot(name="itemBadge", :item="item", :emptyItem="emptyItem")
|
||||||
|
|
||||||
span.badge.badge-pill.badge-item.badge-clock(
|
span.badge.badge-pill.badge-item.badge-clock(
|
||||||
v-if="item.event && showEventBadge",
|
v-if="item.event && item.owned == null && showEventBadge",
|
||||||
)
|
)
|
||||||
span.svg-icon.inline.clock(v-html="icons.clock")
|
span.svg-icon.inline.clock(v-html="icons.clock")
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
.tags-popup
|
.tags-popup
|
||||||
.tags-category.d-flex
|
.tags-category.d-flex(
|
||||||
|
v-for="tagsType in tagsByType",
|
||||||
|
v-if="tagsType.tags.length > 0 || tagsType.key === 'tags'",
|
||||||
|
:key="tagsType.key"
|
||||||
|
)
|
||||||
.tags-header
|
.tags-header
|
||||||
strong(v-once) {{ $t('tags') }}
|
strong(v-once) {{ $t(tagsType.key) }}
|
||||||
.tags-list.container
|
.tags-list.container
|
||||||
.row
|
.row
|
||||||
.col-4(v-for="tag in tags")
|
.col-4(v-for="(tag, tagIndex) in tagsType.tags")
|
||||||
.custom-control.custom-checkbox
|
.custom-control.custom-checkbox
|
||||||
input.custom-control-input(type="checkbox", :value="tag.id", v-model="selectedTags", :id="`tag-${tag.id}`")
|
input.custom-control-input(type="checkbox", :value="tag.id", v-model="selectedTags", :id="`tag-${tag.id}`")
|
||||||
label.custom-control-label(:title="tag.name", :for="`tag-${tag.id}`", v-markdown="tag.name")
|
label.custom-control-label(:title="tag.name", :for="`tag-${tag.id}`", v-markdown="tag.name")
|
||||||
@@ -103,6 +107,34 @@ export default {
|
|||||||
selectedTags: [],
|
selectedTags: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
tagsByType () {
|
||||||
|
const tagsByType = {
|
||||||
|
challenges: {
|
||||||
|
key: 'challenges',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
groups: {
|
||||||
|
key: 'groups',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
key: 'tags',
|
||||||
|
tags: [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
this.$props.tags.forEach(t => {
|
||||||
|
if (t.group) {
|
||||||
|
tagsByType.groups.tags.push(t);
|
||||||
|
} else if (t.challenge) {
|
||||||
|
tagsByType.challenges.tags.push(t);
|
||||||
|
} else {
|
||||||
|
tagsByType.user.tags.push(t);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return tagsByType;
|
||||||
|
},
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
selectedTags () {
|
selectedTags () {
|
||||||
this.$emit('input', this.selectedTags);
|
this.$emit('input', this.selectedTags);
|
||||||
|
|||||||
@@ -159,6 +159,15 @@
|
|||||||
| {{ $t(frequency) }}
|
| {{ $t(frequency) }}
|
||||||
|
|
||||||
.option.group-options(v-if='groupId')
|
.option.group-options(v-if='groupId')
|
||||||
|
.form-group(v-if="task.type === 'todo'")
|
||||||
|
label(v-once) {{ $t('sharedCompletion') }}
|
||||||
|
b-dropdown.inline-dropdown(:text="$t(sharedCompletion)")
|
||||||
|
b-dropdown-item(
|
||||||
|
v-for="completionOption in ['recurringCompletion', 'singleCompletion', 'allAssignedCompletion']",
|
||||||
|
:key="completionOption",
|
||||||
|
@click="sharedCompletion = completionOption",
|
||||||
|
:class="{active: sharedCompletion === completionOption}"
|
||||||
|
) {{ $t(completionOption) }}
|
||||||
.form-group.row
|
.form-group.row
|
||||||
label.col-12(v-once) {{ $t('assignedTo') }}
|
label.col-12(v-once) {{ $t('assignedTo') }}
|
||||||
.col-12.mt-2
|
.col-12.mt-2
|
||||||
@@ -179,23 +188,12 @@
|
|||||||
|
|
||||||
.row
|
.row
|
||||||
button.btn.btn-primary(@click.stop.prevent="showAssignedSelect = !showAssignedSelect") {{$t('close')}}
|
button.btn.btn-primary(@click.stop.prevent="showAssignedSelect = !showAssignedSelect") {{$t('close')}}
|
||||||
|
|
||||||
.option.group-options(v-if='groupId')
|
|
||||||
.form-group
|
.form-group
|
||||||
label(v-once) {{ $t('approvalRequired') }}
|
label(v-once) {{ $t('approvalRequired') }}
|
||||||
toggle-switch.d-inline-block(
|
toggle-switch.d-inline-block(
|
||||||
:checked="requiresApproval",
|
:checked="requiresApproval",
|
||||||
@change="updateRequiresApproval"
|
@change="updateRequiresApproval"
|
||||||
)
|
)
|
||||||
.form-group(v-if="task.type === 'todo'")
|
|
||||||
label(v-once) {{ $t('sharedCompletion') }}
|
|
||||||
b-dropdown.inline-dropdown(:text="$t(sharedCompletion)")
|
|
||||||
b-dropdown-item(
|
|
||||||
v-for="completionOption in ['recurringCompletion', 'singleCompletion', 'allAssignedCompletion']",
|
|
||||||
:key="completionOption",
|
|
||||||
@click="sharedCompletion = completionOption",
|
|
||||||
:class="{active: sharedCompletion === completionOption}"
|
|
||||||
) {{ $t(completionOption) }}
|
|
||||||
|
|
||||||
.advanced-settings(v-if="task.type !== 'reward'")
|
.advanced-settings(v-if="task.type !== 'reward'")
|
||||||
.advanced-settings-toggle.d-flex.justify-content-between.align-items-center(@click = "showAdvancedOptions = !showAdvancedOptions")
|
.advanced-settings-toggle.d-flex.justify-content-between.align-items-center(@click = "showAdvancedOptions = !showAdvancedOptions")
|
||||||
@@ -360,8 +358,8 @@
|
|||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
label {
|
.custom-control-label p {
|
||||||
max-height: 30px;
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -711,7 +709,7 @@ export default {
|
|||||||
calendar: calendarIcon,
|
calendar: calendarIcon,
|
||||||
}),
|
}),
|
||||||
requiresApproval: false, // We can't set task.group fields so we use this field to toggle
|
requiresApproval: false, // We can't set task.group fields so we use this field to toggle
|
||||||
sharedCompletion: 'recurringCompletion',
|
sharedCompletion: 'singleCompletion',
|
||||||
members: [],
|
members: [],
|
||||||
memberNamesById: {},
|
memberNamesById: {},
|
||||||
assignedMembers: [],
|
assignedMembers: [],
|
||||||
@@ -842,7 +840,7 @@ export default {
|
|||||||
});
|
});
|
||||||
this.assignedMembers = [];
|
this.assignedMembers = [];
|
||||||
if (this.task.group && this.task.group.assignedUsers) this.assignedMembers = this.task.group.assignedUsers;
|
if (this.task.group && this.task.group.assignedUsers) this.assignedMembers = this.task.group.assignedUsers;
|
||||||
if (this.task.group) this.sharedCompletion = this.task.group.sharedCompletion || 'recurringCompletion';
|
if (this.task.group) this.sharedCompletion = this.task.group.sharedCompletion || 'singleCompletion';
|
||||||
}
|
}
|
||||||
|
|
||||||
// @TODO: This whole component is mutating a prop and that causes issues. We need to not copy the prop similar to group modals
|
// @TODO: This whole component is mutating a prop and that causes issues. We need to not copy the prop similar to group modals
|
||||||
@@ -926,7 +924,6 @@ export default {
|
|||||||
|
|
||||||
// TODO Fix up permissions on task.group so we don't have to keep doing these hacks
|
// TODO Fix up permissions on task.group so we don't have to keep doing these hacks
|
||||||
if (this.groupId) {
|
if (this.groupId) {
|
||||||
this.task.group.assignedUsers = this.assignedMembers;
|
|
||||||
this.task.requiresApproval = this.requiresApproval;
|
this.task.requiresApproval = this.requiresApproval;
|
||||||
this.task.group.approval.required = this.requiresApproval;
|
this.task.group.approval.required = this.requiresApproval;
|
||||||
this.task.sharedCompletion = this.sharedCompletion;
|
this.task.sharedCompletion = this.sharedCompletion;
|
||||||
@@ -954,6 +951,7 @@ export default {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
Promise.all(promises);
|
Promise.all(promises);
|
||||||
|
this.task.group.assignedUsers = this.assignedMembers;
|
||||||
this.$emit('taskCreated', this.task);
|
this.$emit('taskCreated', this.task);
|
||||||
} else {
|
} else {
|
||||||
this.createTask(this.task);
|
this.createTask(this.task);
|
||||||
|
|||||||
@@ -201,6 +201,7 @@
|
|||||||
|
|
||||||
.custom-control-label {
|
.custom-control-label {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
word-break: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-panel-footer {
|
.filter-panel-footer {
|
||||||
|
|||||||
@@ -51,10 +51,8 @@ export async function set (store, changes) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
axios.put('/api/v4/user', changes);
|
let response = await axios.put('/api/v4/user', changes);
|
||||||
// TODO
|
return response.data.data;
|
||||||
// .then((res) => console.log('set', res))
|
|
||||||
// .catch((err) => console.error('set', err));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sleep (store) {
|
export async function sleep (store) {
|
||||||
|
|||||||
@@ -285,7 +285,8 @@
|
|||||||
"claim": "Claim",
|
"claim": "Claim",
|
||||||
"removeClaim": "Remove Claim",
|
"removeClaim": "Remove Claim",
|
||||||
"onlyGroupLeaderCanManageSubscription": "Only the group leader can manage the group's subscription",
|
"onlyGroupLeaderCanManageSubscription": "Only the group leader can manage the group's subscription",
|
||||||
"yourTaskHasBeenApproved": "Your task <span class=\"notification-green\"><%= taskText %></span> has been approved.",
|
"youHaveBeenAssignedTask": "<%= managerName %> has assigned you the task <span class=\"notification-bold\"><%= taskText %></span>.",
|
||||||
|
"yourTaskHasBeenApproved": "Your task <span class=\"notification-green notification-bold\"><%= taskText %></span> has been approved.",
|
||||||
"taskNeedsWork": "<span class=\"notification-bold\"><%= managerName %></span> marked <span class=\"notification-bold\"><%= taskText %></span> as needing additional work.",
|
"taskNeedsWork": "<span class=\"notification-bold\"><%= managerName %></span> marked <span class=\"notification-bold\"><%= taskText %></span> as needing additional work.",
|
||||||
"userHasRequestedTaskApproval": "<span class=\"notification-bold\"><%= user %></span> requests approval for <span class=\"notification-bold\"><%= taskName %></span>",
|
"userHasRequestedTaskApproval": "<span class=\"notification-bold\"><%= user %></span> requests approval for <span class=\"notification-bold\"><%= taskName %></span>",
|
||||||
"approve": "Approve",
|
"approve": "Approve",
|
||||||
|
|||||||
@@ -173,7 +173,7 @@
|
|||||||
"habitCounterDown": "Negative Counter (Resets <%= frequency %>)",
|
"habitCounterDown": "Negative Counter (Resets <%= frequency %>)",
|
||||||
"taskRequiresApproval": "This task must be approved before you can complete it. Approval has already been requested",
|
"taskRequiresApproval": "This task must be approved before you can complete it. Approval has already been requested",
|
||||||
"taskApprovalHasBeenRequested": "Approval has been requested",
|
"taskApprovalHasBeenRequested": "Approval has been requested",
|
||||||
"taskApprovalWasNotRequested": "Only a task waiting for approval can be marked as needing more work",
|
"taskApprovalWasNotRequested": "Approval has not been requested for this task.",
|
||||||
"approvals": "Approvals",
|
"approvals": "Approvals",
|
||||||
"approvalRequired": "Needs Approval",
|
"approvalRequired": "Needs Approval",
|
||||||
"repeatZero": "Daily is never due",
|
"repeatZero": "Daily is never due",
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import {
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import apiError from '../../libs/apiError';
|
import apiError from '../../libs/apiError';
|
||||||
import validator from 'validator';
|
import validator from 'validator';
|
||||||
import { validateItemPath } from '../../libs/items/utils';
|
import {
|
||||||
|
validateItemPath,
|
||||||
|
castItemVal,
|
||||||
|
} from '../../libs/items/utils';
|
||||||
|
|
||||||
|
|
||||||
let api = {};
|
let api = {};
|
||||||
@@ -271,7 +274,7 @@ api.updateHero = {
|
|||||||
hero.markModified('items.pets');
|
hero.markModified('items.pets');
|
||||||
}
|
}
|
||||||
if (updateData.itemPath && updateData.itemVal && validateItemPath(updateData.itemPath)) {
|
if (updateData.itemPath && updateData.itemVal && validateItemPath(updateData.itemPath)) {
|
||||||
_.set(hero, updateData.itemPath, updateData.itemVal); // Sanitization at 5c30944 (deemed unnecessary)
|
_.set(hero, updateData.itemPath, castItemVal(updateData.itemPath, updateData.itemVal)); // Sanitization at 5c30944 (deemed unnecessary)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updateData.auth && updateData.auth.blocked === true) {
|
if (updateData.auth && updateData.auth.blocked === true) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ let api = {};
|
|||||||
* @apiDescription Get inbox messages for a user
|
* @apiDescription Get inbox messages for a user
|
||||||
*
|
*
|
||||||
* @apiParam (Query) {Number} page Load the messages of the selected Page - 10 Messages per Page
|
* @apiParam (Query) {Number} page Load the messages of the selected Page - 10 Messages per Page
|
||||||
|
* @apiParam (Query) {GUID} conversation Loads only the messages of a conversation
|
||||||
*
|
*
|
||||||
* @apiSuccess {Array} data An array of inbox messages
|
* @apiSuccess {Array} data An array of inbox messages
|
||||||
*/
|
*/
|
||||||
@@ -22,9 +23,10 @@ api.getInboxMessages = {
|
|||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
const user = res.locals.user;
|
const user = res.locals.user;
|
||||||
const page = req.query.page;
|
const page = req.query.page;
|
||||||
|
const conversation = req.query.conversation;
|
||||||
|
|
||||||
const userInbox = await inboxLib.getUserInbox(user, {
|
const userInbox = await inboxLib.getUserInbox(user, {
|
||||||
page,
|
page, conversation,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.respond(200, userInbox);
|
res.respond(200, userInbox);
|
||||||
|
|||||||
@@ -206,6 +206,14 @@ api.assignTask = {
|
|||||||
let message = res.t('userIsClamingTask', {username: user.profile.name, task: task.text});
|
let message = res.t('userIsClamingTask', {username: user.profile.name, task: task.text});
|
||||||
const newMessage = group.sendChat(message);
|
const newMessage = group.sendChat(message);
|
||||||
promises.push(newMessage.save());
|
promises.push(newMessage.save());
|
||||||
|
} else {
|
||||||
|
const taskText = task.text;
|
||||||
|
const managerName = user.profile.name;
|
||||||
|
|
||||||
|
assignedUser.addNotification('GROUP_TASK_ASSIGNED', {
|
||||||
|
message: res.t('youHaveBeenAssignedTask', {managerName, taskText}),
|
||||||
|
taskId: task._id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
promises.push(group.syncTask(task, assignedUser));
|
promises.push(group.syncTask(task, assignedUser));
|
||||||
@@ -261,6 +269,15 @@ api.unassignTask = {
|
|||||||
|
|
||||||
await group.unlinkTask(task, assignedUser);
|
await group.unlinkTask(task, assignedUser);
|
||||||
|
|
||||||
|
let notificationIndex = assignedUser.notifications.findIndex(function findNotification (notification) {
|
||||||
|
return notification && notification.data && notification.type === 'GROUP_TASK_ASSIGNED' && notification.data.taskId === task._id;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (notificationIndex !== -1) {
|
||||||
|
assignedUser.notifications.splice(notificationIndex, 1);
|
||||||
|
await assignedUser.save();
|
||||||
|
}
|
||||||
|
|
||||||
res.respond(200, task);
|
res.respond(200, task);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -308,6 +325,9 @@ api.approveTask = {
|
|||||||
|
|
||||||
if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||||
if (task.group.approval.approved === true) throw new NotAuthorized(res.t('canOnlyApproveTaskOnce'));
|
if (task.group.approval.approved === true) throw new NotAuthorized(res.t('canOnlyApproveTaskOnce'));
|
||||||
|
if (!task.group.approval.requested) {
|
||||||
|
throw new NotAuthorized(res.t('taskApprovalWasNotRequested'));
|
||||||
|
}
|
||||||
|
|
||||||
task.group.approval.dateApproved = new Date();
|
task.group.approval.dateApproved = new Date();
|
||||||
task.group.approval.approvingUser = user._id;
|
task.group.approval.approvingUser = user._id;
|
||||||
|
|||||||
@@ -72,4 +72,25 @@ api.clearMessages = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /inbox/conversations Get the conversations for a user
|
||||||
|
* @apiName conversations
|
||||||
|
* @apiGroup Inbox
|
||||||
|
* @apiDescription Get the conversations for a user
|
||||||
|
*
|
||||||
|
* @apiSuccess {Array} data An array of inbox conversations
|
||||||
|
*/
|
||||||
|
api.conversations = {
|
||||||
|
method: 'GET',
|
||||||
|
middlewares: [authWithHeaders()],
|
||||||
|
url: '/inbox/conversations',
|
||||||
|
async handler (req, res) {
|
||||||
|
const user = res.locals.user;
|
||||||
|
|
||||||
|
const result = await inboxLib.listConversations(user);
|
||||||
|
|
||||||
|
res.respond(200, result);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = api;
|
module.exports = api;
|
||||||
|
|||||||
@@ -1,14 +1,25 @@
|
|||||||
import {inboxModel as Inbox} from '../../models/message';
|
import {inboxModel as Inbox} from '../../models/message';
|
||||||
|
import {
|
||||||
|
model as User,
|
||||||
|
} from '../../models/user';
|
||||||
|
import orderBy from 'lodash/orderBy';
|
||||||
|
import keyBy from 'lodash/keyBy';
|
||||||
|
|
||||||
const PM_PER_PAGE = 10;
|
const PM_PER_PAGE = 10;
|
||||||
|
|
||||||
export async function getUserInbox (user, options = {asArray: true, page: 0}) {
|
export async function getUserInbox (user, options = {asArray: true, page: 0, conversation: null}) {
|
||||||
if (typeof options.asArray === 'undefined') {
|
if (typeof options.asArray === 'undefined') {
|
||||||
options.asArray = true;
|
options.asArray = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const findObj = {ownerId: user._id};
|
||||||
|
|
||||||
|
if (options.conversation) {
|
||||||
|
findObj.uuid = options.conversation;
|
||||||
|
}
|
||||||
|
|
||||||
let query = Inbox
|
let query = Inbox
|
||||||
.find({ownerId: user._id})
|
.find(findObj)
|
||||||
.sort({timestamp: -1});
|
.sort({timestamp: -1});
|
||||||
|
|
||||||
if (typeof options.page !== 'undefined') {
|
if (typeof options.page !== 'undefined') {
|
||||||
@@ -29,6 +40,39 @@ export async function getUserInbox (user, options = {asArray: true, page: 0}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function listConversations (user) {
|
||||||
|
let query = Inbox
|
||||||
|
.aggregate([
|
||||||
|
{
|
||||||
|
$match: {
|
||||||
|
ownerId: user._id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$group: {
|
||||||
|
_id: '$uuid',
|
||||||
|
timestamp: {$max: '$timestamp'}, // sort before group doesn't work - use the max value to sort it again after
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const conversationsList = orderBy(await query.exec(), ['timestamp'], ['desc']).map(c => c._id);
|
||||||
|
|
||||||
|
const users = await User.find({_id: {$in: conversationsList}})
|
||||||
|
.select('_id profile.name auth.local.username')
|
||||||
|
.lean()
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
const usersMap = keyBy(users, '_id');
|
||||||
|
const conversations = conversationsList.map(userId => ({
|
||||||
|
uuid: usersMap[userId]._id,
|
||||||
|
user: usersMap[userId].profile.name,
|
||||||
|
username: usersMap[userId].auth.local.username,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return conversations;
|
||||||
|
}
|
||||||
|
|
||||||
export async function getUserInboxMessage (user, messageId) {
|
export async function getUserInboxMessage (user, messageId) {
|
||||||
return Inbox.findOne({ownerId: user._id, _id: messageId}).exec();
|
return Inbox.findOne({ownerId: user._id, _id: messageId}).exec();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,3 +55,23 @@ export function validateItemPath (itemPath) {
|
|||||||
return Boolean(shared.content.quests[key]);
|
return Boolean(shared.content.quests[key]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When passed a value of an item in the user object it'll convert the
|
||||||
|
// value to the correct format.
|
||||||
|
// Example a numeric string like "5" applied to a food item (expecting an interger)
|
||||||
|
// will be converted to the number 5
|
||||||
|
// TODO cast the correct value for `items.gear.owned`
|
||||||
|
export function castItemVal (itemPath, itemVal) {
|
||||||
|
if (
|
||||||
|
itemPath.indexOf('items.pets') === 0 ||
|
||||||
|
itemPath.indexOf('items.eggs') === 0 ||
|
||||||
|
itemPath.indexOf('items.hatchingPotions') === 0 ||
|
||||||
|
itemPath.indexOf('items.food') === 0 ||
|
||||||
|
itemPath.indexOf('items.mounts') === 0 ||
|
||||||
|
itemPath.indexOf('items.quests') === 0
|
||||||
|
) {
|
||||||
|
return Number(itemVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemVal;
|
||||||
|
}
|
||||||
@@ -1341,7 +1341,7 @@ schema.methods.syncTask = async function groupSyncTask (taskToSync, user) {
|
|||||||
matchingTask.group.id = taskToSync.group.id;
|
matchingTask.group.id = taskToSync.group.id;
|
||||||
matchingTask.userId = user._id;
|
matchingTask.userId = user._id;
|
||||||
matchingTask.group.taskId = taskToSync._id;
|
matchingTask.group.taskId = taskToSync._id;
|
||||||
user.tasksOrder[`${taskToSync.type}s`].push(matchingTask._id);
|
user.tasksOrder[`${taskToSync.type}s`].unshift(matchingTask._id);
|
||||||
} else {
|
} else {
|
||||||
_.merge(matchingTask, syncableAttrs(taskToSync));
|
_.merge(matchingTask, syncableAttrs(taskToSync));
|
||||||
// Make sure the task is in user.tasksOrder
|
// Make sure the task is in user.tasksOrder
|
||||||
@@ -1419,9 +1419,29 @@ schema.methods.removeTask = async function groupRemoveTask (task) {
|
|||||||
$set: {'group.broken': 'TASK_DELETED'},
|
$set: {'group.broken': 'TASK_DELETED'},
|
||||||
}, {multi: true}).exec();
|
}, {multi: true}).exec();
|
||||||
|
|
||||||
|
// Get Managers
|
||||||
|
const managerIds = Object.keys(group.managers);
|
||||||
|
managerIds.push(group.leader);
|
||||||
|
const managers = await User.find({_id: managerIds}, 'notifications').exec(); // Use this method so we can get access to notifications
|
||||||
|
|
||||||
|
// Remove old notifications
|
||||||
|
let removalPromises = [];
|
||||||
|
managers.forEach((manager) => {
|
||||||
|
let notificationIndex = manager.notifications.findIndex(function findNotification (notification) {
|
||||||
|
return notification && notification.data && notification.data.groupTaskId === task._id && notification.type === 'GROUP_TASK_APPROVAL';
|
||||||
|
});
|
||||||
|
|
||||||
|
if (notificationIndex !== -1) {
|
||||||
|
manager.notifications.splice(notificationIndex, 1);
|
||||||
|
removalPromises.push(manager.save());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
removeFromArray(group.tasksOrder[`${task.type}s`], task._id);
|
removeFromArray(group.tasksOrder[`${task.type}s`], task._id);
|
||||||
group.markModified('tasksOrder');
|
group.markModified('tasksOrder');
|
||||||
return await group.save();
|
removalPromises.push(group.save());
|
||||||
|
|
||||||
|
return await Promise.all(removalPromises);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns true if the user has reached the spam message limit
|
// Returns true if the user has reached the spam message limit
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export let TaskSchema = new Schema({
|
|||||||
requested: {$type: Boolean, default: false},
|
requested: {$type: Boolean, default: false},
|
||||||
requestedDate: {$type: Date},
|
requestedDate: {$type: Date},
|
||||||
},
|
},
|
||||||
sharedCompletion: {$type: String, enum: _.values(SHARED_COMPLETION), default: SHARED_COMPLETION.default},
|
sharedCompletion: {$type: String, enum: _.values(SHARED_COMPLETION), default: SHARED_COMPLETION.single},
|
||||||
},
|
},
|
||||||
|
|
||||||
reminders: [{
|
reminders: [{
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const NOTIFICATION_TYPES = [
|
|||||||
'CRON',
|
'CRON',
|
||||||
'GROUP_TASK_APPROVAL',
|
'GROUP_TASK_APPROVAL',
|
||||||
'GROUP_TASK_APPROVED',
|
'GROUP_TASK_APPROVED',
|
||||||
|
'GROUP_TASK_ASSIGNED',
|
||||||
'GROUP_TASK_NEEDS_WORK',
|
'GROUP_TASK_NEEDS_WORK',
|
||||||
'LOGIN_INCENTIVE',
|
'LOGIN_INCENTIVE',
|
||||||
'GROUP_INVITE_ACCEPTED',
|
'GROUP_INVITE_ACCEPTED',
|
||||||
|
|||||||
Reference in New Issue
Block a user