diff --git a/test/api/v3/integration/groups/POST-groups_groupId_quests_invite.test.js b/test/api/v3/integration/groups/POST-groups_groupId_quests_invite.test.js index 4311cf7911..973a51a1a5 100644 --- a/test/api/v3/integration/groups/POST-groups_groupId_quests_invite.test.js +++ b/test/api/v3/integration/groups/POST-groups_groupId_quests_invite.test.js @@ -1,6 +1,7 @@ import { createAndPopulateGroup, translate as t, + sleep, } from '../../../../helpers/api-v3-integration.helper'; import { v4 as generateUUID } from 'uuid'; import { quests as questScrolls } from '../../../../../common/script/content'; @@ -122,8 +123,8 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => { ]); }); - xit('adds quest details to group object', async () => { - await leader.post(`groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + it('adds quest details to group object', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); await questingGroup.sync(); @@ -131,47 +132,61 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => { expect(quest.key).to.eql(PET_QUEST); expect(quest.active).to.eql(false); - expect(quest.leader).to.eql(false); - expect(quest.members).to.have.property(leader._id, null); + expect(quest.leader).to.eql(leader._id); + expect(quest.members).to.have.property(leader._id, true); expect(quest.members).to.have.property(member._id, null); expect(quest).to.have.property('progress'); }); - xit('adds quest details to user objects', async () => { - await leader.post(`groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + it('adds quest details to user objects', async () => { + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + await sleep(0.1); // member updates happen in the background await Promise.all([ leader.sync(), member.sync(), ]); - [leader, member].forEach((user) => { - let quest = user.party.quest; - - expect(quest.key).to.eql(PET_QUEST); - expect(quest.active).to.eql(false); - expect(quest.leader).to.eql(false); - expect(quest.members).to.have.property(leader._id, null); - expect(quest.members).to.have.property(member._id, null); - expect(quest).to.have.property('progress'); - }); + expect(leader.party.quest.key).to.eql(PET_QUEST); + expect(member.party.quest.key).to.eql(PET_QUEST); + expect(leader.party.quest.RSVPNeeded).to.eql(false); + expect(member.party.quest.RSVPNeeded).to.eql(true); }); - xit('sends back the quest object', async () => { - let inviteResponse = await leader.post(`groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + it('sends back the quest object', async () => { + let inviteResponse = await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); expect(inviteResponse.key).to.eql(PET_QUEST); expect(inviteResponse.active).to.eql(false); - expect(inviteResponse.leader).to.eql(false); - expect(inviteResponse.members).to.have.property(leader._id, null); + expect(inviteResponse.leader).to.eql(leader._id); + expect(inviteResponse.members).to.have.property(leader._id, true); expect(inviteResponse.members).to.have.property(member._id, null); expect(inviteResponse).to.have.property('progress'); }); - xit('allows non-leader party members to send invites', async () => { - let inviteResponse = await member.post(`groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + it('allows non-party-leader party members to send invites', async () => { + let inviteResponse = await member.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + await questingGroup.sync(); expect(inviteResponse.key).to.eql(PET_QUEST); + expect(questingGroup.quest.key).to.eql(PET_QUEST); + }); + + it('starts quest automatically if user is in a solo party', async () => { + let leaderDetails = { balance: 10 }; + leaderDetails[`items.quests.${PET_QUEST}`] = 1; + let { group, groupLeader } = await createAndPopulateGroup({ + groupDetails: { type: 'party', privacy: 'private' }, + leaderDetails, + }); + + await groupLeader.post(`/groups/${group._id}/quests/invite/${PET_QUEST}`); + + await group.sync(); + + expect(group.quest.active).to.eql(true); }); }); }); diff --git a/website/src/controllers/api-v3/quests.js b/website/src/controllers/api-v3/quests.js index 38020fcae5..77cec58d6a 100644 --- a/website/src/controllers/api-v3/quests.js +++ b/website/src/controllers/api-v3/quests.js @@ -1,14 +1,25 @@ +import _ from 'lodash'; +import Q from 'q'; import { authWithHeaders } from '../../middlewares/api-v3/auth'; import cron from '../../middlewares/api-v3/cron'; import { model as Group, } from '../../models/group'; +import { + model as User, +} from '../../models/user'; import { NotFound, NotAuthorized, } from '../../libs/api-v3/errors'; import { quests as questScrolls } from '../../../../common/script/content'; +function canStartQuestAutomatically (group) { + // If all members are either true (accepted) or false (rejected) return true + // If any member is null/undefined (undecided) return false + return _.every(group.quest.members, Boolean); +} + let api = {}; /** @@ -44,24 +55,43 @@ api.inviteToQuest = { if (user.stats.lvl < quest.lvl) throw new NotAuthorized(res.t('questLevelTooHigh', { level: quest.lvl })); if (group.quest.key) throw new NotAuthorized(res.t('questAlreadyUnderway')); + let members = await User.find({ 'party._id': group._id }, 'auth.facebook auth.local preferences.emailNotifications').exec(); + let backgroundOperations = []; + group.markModified('quest'); group.quest.key = questKey; group.quest.leader = user._id; group.quest.members = {}; + group.quest.members[user._id] = true; - // let memberUpdate = { - // '$set': { - // 'party.quest.key': questKey, - // 'party.quest.progress.down': 0, - // 'party.quest.completed': null, - // }, - // }; + user.party.quest.RSVPNeeded = false; + user.party.quest.key = questKey; - // TODO collect members of party - // TODO Logic for quest invite and send back quest object + _.each(members, (member) => { + if (member._id !== user._id) { + group.quest.members[member._id] = null; + member.party.quest.RSVPNeeded = true; + member.party.quest.key = questKey; + // TODO: Send Quest invite email + backgroundOperations.push(member.save()); + } + }); - await group.save(); - res.respond(200, {}); + if (canStartQuestAutomatically(group)) { + group.startQuest(user); + } + + let [savedGroup] = await Q.all([ + group.save(), + user.save(), + ]); + + res.respond(200, savedGroup.quest); + + Q.allSettled(backgroundOperations).catch(err => { + // TODO what to do about errors in background ops + throw err; + }); }, };