mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-19 15:48:04 +01:00
commit 00affb306655a543f5d29b3af6361e686b577a97 Author: SabreCat <sabe@habitica.com> Date: Tue May 2 09:47:25 2023 -0500 fix(tests): account for invite limit changes commit 47661117f9fd661b8bc8f63b7cc7c8d5f8fa0fd7 Author: SabreCat <sabe@habitica.com> Date: Mon May 1 17:39:29 2023 -0500 fix(lfp): final polish commit 6a1e5af1db0dd90be3ced7e223f53c9183a206f5 Merge: 728ed2ddad9e0777bb42Author: SabreCat <sabe@habitica.com> Date: Mon May 1 16:54:12 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit 728ed2ddad7f0962d28f1ab0a271e3555b19296c Author: SabreCat <sabe@habitica.com> Date: Thu Apr 27 16:51:06 2023 -0500 fix(lfp): loading layout and page visit event commit 8a56ab329bff922e05963e3ef78fbc26ff273924 Author: SabreCat <sabe@habitica.com> Date: Wed Apr 26 16:54:46 2023 -0500 fix(faq): copy and style updates commit 6fd00d7f30150a1802e5a37edbb914ef120caf9a Author: SabreCat <sabe@habitica.com> Date: Fri Apr 21 17:12:52 2023 -0500 feat(lfp): fixes, analytics, FAQ commit 4b5d7304ad7cfc5f72320b23456ed2898e53caac Author: SabreCat <sabe@habitica.com> Date: Mon Apr 17 15:13:03 2023 -0500 fix(lfp): smol tweaks commit 9a5476a2558eb17a603f4aae1b5b2d35773be8b4 Author: SabreCat <sabe@habitica.com> Date: Thu Apr 13 16:03:33 2023 -0500 feat(lfp): refresh button commit aa58f5018469f38a9a9d31c3bffa26bb88a8c672 Merge: bbb03d006ec8adf20804Author: SabreCat <sabe@habitica.com> Date: Tue Apr 11 17:44:56 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit bbb03d006e8b122bb7206bdc778a31de422167bb Author: SabreCat <sabe@habitica.com> Date: Tue Apr 4 18:30:50 2023 -0500 fix(lint): whitespace and const commit 23683ad29a4cce0b0da061ad6c030982034c0a9c Author: SabreCat <sabe@habitica.com> Date: Tue Apr 4 17:02:57 2023 -0500 chore(LFP): add analytics also re-fix loading state commit 4477d84f5266c87f5583368029b72153f00f0568 Author: SabreCat <sabe@habitica.com> Date: Mon Apr 3 16:24:26 2023 -0500 fix(LFP): address issues with loading commit bdc5154f24bb5e50963376c3c0c9cc73c0b05ccc Merge: 81923eef6f229ed46425Author: SabreCat <sabe@habitica.com> Date: Mon Apr 3 15:58:12 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit 81923eef6f0c627d079475a28f9d93d8e4628934 Author: SabreCat <sabe@habitica.com> Date: Thu Mar 30 16:44:49 2023 -0500 feat(LFP): release candidate commit fe1f8939fc6b09d36cfaf0b6e5838df04e41009d Author: SabreCat <sabe@habitica.com> Date: Wed Mar 29 17:35:54 2023 -0500 WIP(LFP): fixes commit afc361f5a9f806cbd814ad910d1274e3a6609efd Merge: d6b5cbdebc7ede3acd01Author: SabreCat <sabe@habitica.com> Date: Wed Mar 29 16:24:39 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit d6b5cbdebc2829e9325ea57fb5deeccc128c1635 Author: SabreCat <sabe@habitica.com> Date: Tue Mar 28 16:13:18 2023 -0500 WIP(LFP): change copy, add close X, fix API response commit 4274a4625862351ef0ecf33c8a3249ca5ebec7cb Author: SabreCat <sabe@habitica.com> Date: Mon Mar 27 17:07:36 2023 -0500 fix(LFP): layout, unset when stopping commit 95abfcfa5f13c9cce6385206947a47f85b76d11d Merge: 4a360eedd853c536b525Author: SabreCat <sabe@habitica.com> Date: Mon Mar 27 16:32:46 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit 4a360eedd8b9cf41d3a0fe7a4cfaa72c5bd7bd26 Author: SabreCat <sabe@habitica.com> Date: Thu Mar 23 16:54:49 2023 -0500 feat(LFP): completed style and infinite scroll commit bbc439d9d03c9631a450236eb33af66f0428fa50 Author: SabreCat <sabe@habitica.com> Date: Tue Mar 21 10:40:26 2023 -0500 WIP(LFP): nicer layout, buffs fix commit 1658688597456663477ab19da61ae1b9bc85cf2a Merge: 664507434f027e61a93eAuthor: SabreCat <sabe@habitica.com> Date: Tue Mar 21 09:29:02 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit 664507434f2f76e6bf3b61cdc9e3daddb81204af Author: SabreCat <sabe@habitica.com> Date: Fri Mar 17 17:13:08 2023 -0500 WIP(LFP): API and client adjustments commit 2f0b6f2517f9e2d634cb23ee303cfb4542e998ce Merge: 0db1704325a16098ccdaAuthor: SabreCat <sabe@habitica.com> Date: Fri Mar 17 16:45:13 2023 -0500 Merge branch 'release' into sabrecat/party-seeking commit 0db1704325c3555f0b5d9c8d1dfc44177e90c093 Author: SabreCat <sabe@habitica.com> Date: Mon Mar 13 14:48:10 2023 -0500 fix(modal): scrollbar for squashed viewports commit 733c35192e0a4e31e1bebfdd7488cfc1f7587f36 Author: SabreCat <sabe@habitica.com> Date: Thu Mar 9 12:51:02 2023 -0600 WIP(party): seekers functional rough commit d4b854410b557db26eec6e6a26b6d174c02cee3a Merge: 7fe919825a0b6b967753Author: SabreCat <sabe@habitica.com> Date: Thu Mar 9 10:07:28 2023 -0600 Merge branch 'release' into sabrecat/party-seeking commit 7fe919825abfb6d518cb93b91f5997d3831bd0b5 Author: SabreCat <sabe@habitica.com> Date: Thu Mar 2 14:40:09 2023 -0600 feat(party): several adjustments to seeking feature commit c93900efcf925f7aaa4c4cb56b4451f19adfb1b3 Author: SabreCat <sabe@habitica.com> Date: Wed Mar 1 20:37:11 2023 -0600 feat(party): initial Seeking API commit 8bb784daeceb14c23992a6f3af1054a900fc26c1 Merge: e19a661a21f327795761Author: SabreCat <sabe@habitica.com> Date: Wed Mar 1 18:58:20 2023 -0600 Merge branch 'release' into sabrecat/party-seeking commit e19a661a2163a50307a286379bffb44201ed392e Author: SabreCat <sabe@habitica.com> Date: Fri Feb 24 15:51:42 2023 -0600 WIP(parties): add seeking flag and modal toggle
269 lines
8.4 KiB
JavaScript
269 lines
8.4 KiB
JavaScript
import _ from 'lodash';
|
|
|
|
import { encrypt } from '../encryption';
|
|
import { sendNotification as sendPushNotification } from '../pushNotifications';
|
|
import {
|
|
NotFound,
|
|
BadRequest,
|
|
NotAuthorized,
|
|
} from '../errors';
|
|
import { sendTxn as sendTxnEmail } from '../email';
|
|
import { model as EmailUnsubscription } from '../../models/emailUnsubscription';
|
|
import {
|
|
model as User,
|
|
} from '../../models/user';
|
|
import {
|
|
model as Group,
|
|
} from '../../models/group';
|
|
|
|
function sendInvitePushNotification (userToInvite, groupLabel, group, publicGuild, res) {
|
|
if (userToInvite.preferences.pushNotifications[`invited${groupLabel}`] === false) return;
|
|
|
|
const identifier = group.type === 'guild' ? 'invitedGuild' : 'invitedParty';
|
|
|
|
sendPushNotification(
|
|
userToInvite,
|
|
{
|
|
title: group.name,
|
|
message: res.t(identifier, userToInvite.preferences.language),
|
|
identifier,
|
|
payload: { groupID: group._id, publicGuild },
|
|
},
|
|
);
|
|
}
|
|
|
|
function sendInviteEmail (userToInvite, groupLabel, group, inviter) {
|
|
if (userToInvite.preferences.emailNotifications[`invited${groupLabel}`] === false) return;
|
|
const groupTemplate = group.type === 'guild' ? 'guild' : 'party';
|
|
|
|
const emailVars = [
|
|
{ name: 'INVITER', content: inviter.profile.name },
|
|
];
|
|
|
|
if (group.type === 'guild') {
|
|
emailVars.push(
|
|
{ name: 'GUILD_NAME', content: group.name },
|
|
{ name: 'GUILD_URL', content: '/groups/discovery' },
|
|
);
|
|
} else {
|
|
emailVars.push(
|
|
{ name: 'PARTY_NAME', content: group.name },
|
|
{ name: 'PARTY_URL', content: '/party' },
|
|
);
|
|
}
|
|
|
|
sendTxnEmail(userToInvite, `invited-${groupTemplate}`, emailVars);
|
|
}
|
|
|
|
function inviteUserToGuild (userToInvite, group, inviter, publicGuild, res) {
|
|
const uuid = userToInvite._id;
|
|
|
|
if (_.includes(userToInvite.guilds, group._id)) {
|
|
throw new NotAuthorized(res.t('userAlreadyInGroup', { userId: uuid, username: userToInvite.profile.name }));
|
|
}
|
|
|
|
if (_.find(userToInvite.invitations.guilds, { id: group._id })) {
|
|
throw new NotAuthorized(res.t('userAlreadyInvitedToGroup', { userId: uuid, username: userToInvite.profile.name }));
|
|
}
|
|
|
|
const guildInvite = {
|
|
id: group._id,
|
|
name: group.name,
|
|
inviter: inviter._id,
|
|
publicGuild,
|
|
};
|
|
|
|
if (group.hasActiveGroupPlan() && !group.hasNotCancelled()) guildInvite.cancelledPlan = true;
|
|
|
|
userToInvite.invitations.guilds.push(guildInvite);
|
|
}
|
|
|
|
async function inviteUserToParty (userToInvite, group, inviter, res) {
|
|
const uuid = userToInvite._id;
|
|
|
|
// Do not add to invitations.parties array if the user is already invited to that party
|
|
if (_.find(userToInvite.invitations.parties, { id: group._id })) {
|
|
throw new NotAuthorized(res.t('userAlreadyPendingInvitation', { userId: uuid, username: userToInvite.profile.name }));
|
|
}
|
|
|
|
if (userToInvite.party._id) {
|
|
const userParty = await Group.getGroup({ user: userToInvite, groupId: 'party', fields: '_id' });
|
|
|
|
if (userParty) throw new NotAuthorized(res.t('userAlreadyInAParty', { userId: uuid, username: userToInvite.profile.name }));
|
|
}
|
|
|
|
const partyInvite = { id: group._id, name: group.name, inviter: inviter._id };
|
|
if (group.hasActiveGroupPlan() && !group.hasNotCancelled()) partyInvite.cancelledPlan = true;
|
|
|
|
userToInvite.invitations.parties.push(partyInvite);
|
|
userToInvite.invitations.party = partyInvite;
|
|
}
|
|
|
|
async function addInvitationToUser (userToInvite, group, inviter, res) {
|
|
const publicGuild = group.type === 'guild' && group.privacy === 'public';
|
|
|
|
if (group.type === 'guild') {
|
|
inviteUserToGuild(userToInvite, group, inviter, publicGuild, res);
|
|
} else if (group.type === 'party') {
|
|
await inviteUserToParty(userToInvite, group, inviter, res);
|
|
}
|
|
|
|
const groupLabel = group.type === 'guild' ? 'Guild' : 'Party';
|
|
sendInviteEmail(userToInvite, groupLabel, group, inviter);
|
|
sendInvitePushNotification(userToInvite, groupLabel, group, publicGuild, res);
|
|
|
|
const userInvited = await userToInvite.save();
|
|
if (group.type === 'guild') {
|
|
return userInvited.invitations.guilds[userToInvite.invitations.guilds.length - 1];
|
|
}
|
|
|
|
if (group.type === 'party') {
|
|
return userInvited.invitations.parties[userToInvite.invitations.parties.length - 1];
|
|
}
|
|
|
|
throw new Error('Invalid group type');
|
|
}
|
|
|
|
async function inviteByUUID (uuid, group, inviter, req, res) {
|
|
const userToInvite = await User.findById(uuid).exec();
|
|
|
|
if (!userToInvite) {
|
|
throw new NotFound(res.t('userWithIDNotFound', { userId: uuid }));
|
|
} else if (inviter._id === userToInvite._id) {
|
|
throw new BadRequest(res.t('cannotInviteSelfToGroup'));
|
|
}
|
|
|
|
const objections = inviter.getObjectionsToInteraction('group-invitation', userToInvite);
|
|
if (objections.length > 0) {
|
|
throw new NotAuthorized(res.t(
|
|
objections[0],
|
|
{ userId: uuid, username: userToInvite.profile.name },
|
|
));
|
|
}
|
|
|
|
const analyticsObject = {
|
|
hitType: 'event',
|
|
category: 'behavior',
|
|
uuid: inviter._id,
|
|
invitee: uuid,
|
|
groupId: group._id,
|
|
groupType: group.type,
|
|
headers: req.headers,
|
|
};
|
|
|
|
if (group.type === 'party') {
|
|
analyticsObject.seekingParty = Boolean(userToInvite.party.seeking);
|
|
}
|
|
|
|
res.analytics.track('group invite', analyticsObject);
|
|
|
|
return addInvitationToUser(userToInvite, group, inviter, res);
|
|
}
|
|
|
|
async function inviteByEmail (invite, group, inviter, req, res) {
|
|
let userReturnInfo;
|
|
|
|
if (!invite.email) throw new BadRequest(res.t('inviteMissingEmail'));
|
|
|
|
const userToContact = await User.findOne({
|
|
$or: [
|
|
{ 'auth.local.email': invite.email },
|
|
{ 'auth.facebook.emails.value': invite.email },
|
|
{ 'auth.google.emails.value': invite.email },
|
|
{ 'auth.apple.emails.value': invite.email },
|
|
],
|
|
})
|
|
.select({ _id: true, 'preferences.emailNotifications': true })
|
|
.exec();
|
|
|
|
if (userToContact) {
|
|
userReturnInfo = await inviteByUUID(userToContact._id, group, inviter, req, res);
|
|
} else {
|
|
userReturnInfo = invite.email;
|
|
|
|
const cancelledPlan = group.hasActiveGroupPlan() && !group.hasNotCancelled();
|
|
const groupQueryString = JSON.stringify({
|
|
id: group._id,
|
|
inviter: inviter._id,
|
|
publicGuild: group.type === 'guild' && group.privacy === 'public',
|
|
sentAt: Date.now(), // so we can let it expire
|
|
cancelledPlan,
|
|
});
|
|
const link = `/static/home?groupInvite=${encrypt(groupQueryString)}`;
|
|
|
|
const variables = [
|
|
{ name: 'LINK', content: link },
|
|
{ name: 'INVITER', content: req.body.inviter || inviter.profile.name },
|
|
];
|
|
|
|
if (group.type === 'guild') {
|
|
variables.push({ name: 'GUILD_NAME', content: group.name });
|
|
}
|
|
|
|
// Check for the email address not to be unsubscribed
|
|
const userIsUnsubscribed = await EmailUnsubscription.findOne({ email: invite.email }).exec();
|
|
const groupLabel = group.type === 'guild' ? '-guild' : '';
|
|
if (!userIsUnsubscribed) sendTxnEmail(invite, `invite-friend${groupLabel}`, variables);
|
|
|
|
const analyticsObject = {
|
|
hitType: 'event',
|
|
category: 'behavior',
|
|
uuid: inviter._id,
|
|
invitee: 'email',
|
|
groupId: group._id,
|
|
groupType: group.type,
|
|
headers: req.headers,
|
|
};
|
|
|
|
res.analytics.track('group invite', analyticsObject);
|
|
}
|
|
|
|
return userReturnInfo;
|
|
}
|
|
|
|
async function inviteByUserName (username, group, inviter, req, res) {
|
|
if (username.indexOf('@') === 0) username = username.slice(1, username.length); // eslint-disable-line no-param-reassign
|
|
username = username.toLowerCase(); // eslint-disable-line no-param-reassign
|
|
const userToInvite = await User.findOne({ 'auth.local.lowerCaseUsername': username }).exec();
|
|
|
|
if (!userToInvite) {
|
|
throw new NotFound(res.t('userWithUsernameNotFound', { username }));
|
|
}
|
|
|
|
if (inviter._id === userToInvite._id) {
|
|
throw new BadRequest(res.t('cannotInviteSelfToGroup'));
|
|
}
|
|
|
|
const objections = inviter.getObjectionsToInteraction('group-invitation', userToInvite);
|
|
if (objections.length > 0) {
|
|
throw new NotAuthorized(res.t(
|
|
objections[0],
|
|
{ userId: userToInvite._id, username: userToInvite.profile.name },
|
|
));
|
|
}
|
|
|
|
const analyticsObject = {
|
|
hitType: 'event',
|
|
category: 'behavior',
|
|
uuid: inviter._id,
|
|
invitee: userToInvite._id,
|
|
groupId: group._id,
|
|
groupType: group.type,
|
|
headers: req.headers,
|
|
};
|
|
|
|
if (group.type === 'party') {
|
|
analyticsObject.seekingParty = Boolean(userToInvite.party.seeking);
|
|
}
|
|
|
|
res.analytics.track('group invite', analyticsObject);
|
|
|
|
return addInvitationToUser(userToInvite, group, inviter, res);
|
|
}
|
|
|
|
export {
|
|
inviteByUUID,
|
|
inviteByEmail,
|
|
inviteByUserName,
|
|
};
|