diff --git a/common/locales/en/quests.json b/common/locales/en/quests.json
index 7b3fe7af43..5edae258e5 100644
--- a/common/locales/en/quests.json
+++ b/common/locales/en/quests.json
@@ -78,5 +78,6 @@
"whichQuestStart": "Which quest do you want to start?",
"getMoreQuests": "Get more quests",
"unlockedAQuest": "You unlocked a quest!",
- "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!"
+ "leveledUpReceivedQuest": "You leveled up to Level <%= level %> and received a quest scroll!",
+ "questInvitationDoesNotExist": "No quest invitation has been sent out yet."
}
diff --git a/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js b/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js
new file mode 100644
index 0000000000..e5f7517eff
--- /dev/null
+++ b/test/api/v3/integration/quests/POST-groups_groupid_quests_reject.test.js
@@ -0,0 +1,98 @@
+import {
+ createAndPopulateGroup,
+ translate as t,
+} from '../../../../helpers/api-v3-integration.helper';
+import { v4 as generateUUID } from 'uuid';
+
+describe('POST /groups/:groupId/quests/invite/:questKey', () => {
+ let questingGroup;
+ let member;
+ const PET_QUEST = 'whale';
+ let userQuestUpdate = {
+ items: {
+ quests: {},
+ },
+ 'party.quest.RSVPNeeded': true,
+ 'party.quest.key': PET_QUEST,
+ };
+
+ before(async () => {
+ let { group, members } = await createAndPopulateGroup({
+ groupDetails: { type: 'party', privacy: 'private' },
+ members: 1,
+ });
+
+ questingGroup = group;
+ member = members[0];
+
+ userQuestUpdate.items.quests[PET_QUEST] = 1;
+ });
+
+ context('failure conditions', () => {
+ it('returns an error when group is not found', async () => {
+ await expect(member.post(`/groups/${generateUUID()}/quests/reject/${PET_QUEST}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('groupNotFound'),
+ });
+ });
+
+ it('returns an error when group is not a party', async () => {
+ let { group, groupLeader } = await createAndPopulateGroup({
+ groupDetails: { type: 'guild', privacy: 'private' },
+ });
+
+ await expect(groupLeader.post(`/groups/${group._id}/quests/reject/${PET_QUEST}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('guildQuestsNotSupported'),
+ });
+ });
+
+ it('returns an error when quest is not found', async () => {
+ let questKey = 'fakeQuestName';
+
+ await expect(member.post(`/groups/${questingGroup._id}/quests/reject/${questKey}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('questNotFound', { key: questKey }),
+ });
+ });
+
+ it('returns an error when user is not on the quest', async () => {
+ await expect(member.post(`/groups/${questingGroup._id}/quests/reject/${PET_QUEST}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 401,
+ error: 'NotAuthorized',
+ message: t('questNotOwned'),
+ });
+ });
+
+ it('returns an error when group is not on a quest', async () => {
+ await member.update(userQuestUpdate);
+
+ await expect(member.post(`/groups/${questingGroup._id}/quests/reject/${PET_QUEST}`))
+ .to.eventually.be.rejected.and.eql({
+ code: 404,
+ error: 'NotFound',
+ message: t('questInvitationDoesNotExist'),
+ });
+ });
+ });
+
+ context('successfully quest rejection', () => {
+ it('rejects a quest invitation', async () => {
+ await member.update(userQuestUpdate);
+ await questingGroup.update({'quest.key': PET_QUEST});
+
+ await member.post(`/groups/${questingGroup._id}/quests/reject/${PET_QUEST}`);
+
+ let userWithRejectInvitation = await member.get('/user');
+ expect(userWithRejectInvitation.party.quest.key).to.be.null;
+ expect(userWithRejectInvitation.party.quest.RSVPNeeded).to.be.false;
+ });
+ });
+});
diff --git a/website/src/controllers/api-v3/groups.js b/website/src/controllers/api-v3/groups.js
index b9b633c111..785ea564ba 100644
--- a/website/src/controllers/api-v3/groups.js
+++ b/website/src/controllers/api-v3/groups.js
@@ -17,6 +17,8 @@ import { removeFromArray } from '../../libs/api-v3/collectionManipulators';
import * as firebase from '../../libs/api-v3/firebase';
import { sendTxn as sendTxnEmail } from '../../libs/api-v3/email';
import { encrypt } from '../../libs/api-v3/encryption';
+import { quests as questScrolls } from '../../../../common/script/content';
+import { mockAnalyticsService } from '../../libs/api-v3/analyticsService';
let api = {};
@@ -616,4 +618,99 @@ api.inviteToGroup = {
},
};
+<<<<<<< e3c7d2834e6e5fa024afb71032c21177ca4124a7
+=======
+/**
+ * @api {post} /groups/:groupId/quests/invite Invite users to a quest
+ * @apiVersion 3.0.0
+ * @apiName InviteToQuest
+ * @apiGroup Group
+ *
+ * @apiParam {string} groupId The group _id (or 'party')
+ *
+ * @apiSuccess {Object} Quest Object
+ */
+api.inviteToQuest = {
+ method: 'POST',
+ url: '/groups/:groupId/quests/invite/:questKey',
+ middlewares: [authWithHeaders(), cron],
+ async handler (req, res) {
+ let user = res.locals.user;
+ let questKey = req.params.questKey;
+ let quest = questScrolls[questKey];
+
+ req.checkParams('groupId', res.t('groupIdRequired')).notEmpty();
+
+ let validationErrors = req.validationErrors();
+ if (validationErrors) throw validationErrors;
+
+ let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'type quest'});
+
+ if (!group) throw new NotFound(res.t('groupNotFound'));
+ if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported'));
+ if (!quest) throw new NotFound(res.t('questNotFound', { key: questKey }));
+ if (!user.items.quests[questKey]) throw new NotAuthorized(res.t('questNotOwned'));
+ 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'));
+
+ // TODO Logic for quest invite and send back quest object
+ res.respond(200, {});
+ },
+};
+
+/**
+ * @api {post} /groups/:groupId/quests/reject Reject a quest
+ * @apiVersion 3.0.0
+ * @apiName RejectQuest
+ * @apiGroup Group
+ *
+ * @apiParam {string} groupId The group _id (or 'party')
+ * @apiParam {string} questKey The quest _id
+ *
+ * @apiSuccess {Object} Quest Object
+ */
+api.rejectQuest = {
+ method: 'POST',
+ url: '/groups/:groupId/quests/reject/:questKey',
+ middlewares: [authWithHeaders(), cron],
+ async handler (req, res) {
+ let user = res.locals.user;
+ let questKey = req.params.questKey;
+ let quest = questScrolls[questKey];
+
+ req.checkParams('groupId', res.t('groupIdRequired')).notEmpty();
+
+ let validationErrors = req.validationErrors();
+ if (validationErrors) throw validationErrors;
+
+ let group = await Group.getGroup({user, groupId: req.params.groupId, fields: 'type quest'});
+ if (!group) throw new NotFound(res.t('groupNotFound'));
+ if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported'));
+ if (!quest) throw new NotFound(res.t('questNotFound', { key: questKey }));
+ if (!user.items.quests[questKey]) throw new NotAuthorized(res.t('questNotOwned'));
+ if (!group.quest.key) throw new NotFound(res.t('questInvitationDoesNotExist'));
+
+ let analyticsData = {
+ category: 'behavior',
+ owner: false,
+ response: 'reject',
+ gaLabel: 'reject',
+ questName: group.quest.key,
+ uuid: user._id,
+ };
+ mockAnalyticsService.track('quest', analyticsData);
+
+ // @TODO: Are we tracking members this way?
+ // group.quest.members[user._id] = false;
+
+ user.party.quest.RSVPNeeded = false;
+ user.party.quest.key = null;
+ await user.save();
+
+ // questStart(req,res,next);
+
+ res.respond(200, {});
+ },
+};
+>>>>>>> Added quest reject route and intial tests
export default api;