From e1ffbf92728a09667776e50a38fadc30f82aefe7 Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Tue, 21 Feb 2023 22:31:30 -0800 Subject: [PATCH 01/11] Update zapier triggers to work for multiple groups at same time Also add new_post trigger This requires a new params object on zapier triggers which stores extra parameters for the trigger, in this case post types that the trigger is watching for. --- api/graphql/index.js | 2 +- api/graphql/mutations/zapier.js | 16 +++++---- api/graphql/schema.graphql | 10 ++++-- api/models/Group.js | 3 +- api/models/Post.js | 34 +++++++++++++++++-- api/models/User.js | 4 +-- api/models/ZapierTrigger.js | 14 ++++++-- api/models/ZapierTriggerGroup.js | 13 +++++++ api/models/post/createPost.js | 5 +-- ...1155959_zapier-triggers-multiple-groups.js | 28 +++++++++++++++ package.json | 4 ++- 11 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 api/models/ZapierTriggerGroup.js create mode 100644 migrations/20230221155959_zapier-triggers-multiple-groups.js diff --git a/api/graphql/index.js b/api/graphql/index.js index 7f6132d2e..75ceff6a1 100644 --- a/api/graphql/index.js +++ b/api/graphql/index.js @@ -336,7 +336,7 @@ export function makeMutations (expressContext, userId, isAdmin, fetchOne) { createSavedSearch: (root, { data }) => createSavedSearch(data), - createZapierTrigger: (root, { groupId, targetUrl, type }) => createZapierTrigger(userId, groupId, targetUrl, type), + createZapierTrigger: (root, { groupIds, targetUrl, type, params }) => createZapierTrigger(userId, groupIds, targetUrl, type, params), joinGroup: (root, { groupId }) => joinGroup(groupId, userId), diff --git a/api/graphql/mutations/zapier.js b/api/graphql/mutations/zapier.js index 302fef561..f0acb249e 100644 --- a/api/graphql/mutations/zapier.js +++ b/api/graphql/mutations/zapier.js @@ -1,13 +1,15 @@ -export async function createZapierTrigger (userId, groupId, targetUrl, type) { - if (groupId) { - const membership = await GroupMembership.forPair(userId, groupId) - if (!membership) { - throw new GraphQLYogaError('You don\'t have access to a group with this ID') +export async function createZapierTrigger (userId, groupIds, targetUrl, type, params) { + const trigger = ZapierTrigger.forge({ user_id: userId, target_url: targetUrl, type, params }) + + if (groupIds && groupIds.length > 0) { + const memberships = await GroupMembership.query(q => q.where({ user_id: userId }).whereIn('group_id', groupIds)).fetchAll() + if (!memberships || memberships.length === 0) { + throw new GraphQLYogaError('You don\'t have access to any of these groups') } + trigger.groups().attach(memberships.map(m => m.group_id)) } - const trigger = await ZapierTrigger.forge({ user_id: userId, group_id: groupId, target_url: targetUrl, type }).save() - return trigger + return trigger.save() } export async function deleteZapierTrigger (userId, id) { diff --git a/api/graphql/schema.graphql b/api/graphql/schema.graphql index bdc7b0ec5..f3729ce5f 100644 --- a/api/graphql/schema.graphql +++ b/api/graphql/schema.graphql @@ -1872,7 +1872,13 @@ type ZapierTrigger { groupId: ID # The Zapier URL to call when this type is triggered targetUrl: String - # When this gets triggered. Options: 'new_member' = when 1 or more members are added to a group + """ + When this gets triggered. Options: + 'new_member' = when 1 or more members are added to a group + 'member_leaves' = when a person leaves or is kicked out of a group + 'member_updated' = when a member of a group updates their profile + 'new_post' = when a new post is created + """ type: String } @@ -1954,7 +1960,7 @@ type Mutation { # Create a new topic within a Group createTopic(topicName: String, groupId: ID, isDefault: Boolean, isSubscribing: Boolean): Topic # Add a new Zapier trigger (called by Zapier) - createZapierTrigger(groupId: ID, targetUrl: String, type: String): ZapierTrigger + createZapierTrigger(groupIds: [ID], targetUrl: String, type: String, params: JSON): ZapierTrigger # Used by current logged in user to deactivate their account deactivateMe(id: ID): GenericResult # Used by current logged in user to delete their account permanently diff --git a/api/models/Group.js b/api/models/Group.js index b2e67de3d..017302f07 100644 --- a/api/models/Group.js +++ b/api/models/Group.js @@ -544,7 +544,8 @@ module.exports = bookshelf.Model.extend(merge({ // Background task to do additional work/tasks when new members are added to a group async afterAddMembers({ groupId, newUserIds, reactivatedUserIds }) { - const zapierTriggers = await ZapierTrigger.query(q => q.where({ group_id: groupId, type: 'new_member' })).fetchAll() + const zapierTriggers = await ZapierTrigger.forTypeAndGroups('new_member', groupId).fetchAll() + if (zapierTriggers && zapierTriggers.length > 0) { const members = await User.query(q => q.whereIn('id', newUserIds.concat(reactivatedUserIds))).fetchAll() for (const trigger of zapierTriggers) { diff --git a/api/models/Post.js b/api/models/Post.js index 4fb5dca5a..a4e6aa03f 100644 --- a/api/models/Post.js +++ b/api/models/Post.js @@ -662,7 +662,35 @@ module.exports = bookshelf.Model.extend(Object.assign({ Post.find(postId, {withRelated: ['groups', 'user', 'relatedUsers']}) .then(post => { if (!post) return - const slackCommunities = post.relations.groups.filter(c => c.get('slack_hook_url')) - return Promise.map(slackCommunities, c => Group.notifySlack(c.id, post)) - }) + const slackCommunities = post.relations.groups.filter(g => g.get('slack_hook_url')) + return Promise.map(slackCommunities, g => Group.notifySlack(g.id, post)) + }), + + // Background task to fire zapier triggers on new_post + zapierTriggers: async ({ postId }) => { + const post = await Post.find(postId, { withRelated: ['groups'] }) + if (!post) return + const groupIds = post.relations.groups.map(g => g.id) + const zapierTriggers = await ZapierTrigger.forTypeAndGroups('new_post', groupIds).fetchAll() + if (zapierTriggers && zapierTriggers.length > 0) { + const members = await User.query(q => q.whereIn('id', newUserIds.concat(reactivatedUserIds))).fetchAll() + for (const trigger of zapierTriggers) { + // Check if this trigger is only for certain post types and if so whether it matches this post type + if (trigger.get('params')?.types?.length > 0 && !trigger.get('params').types.includes(post.get('type'))) { + continue + } + const response = await fetch(trigger.get('target_url'), { + method: 'post', + body: JSON.stringify(members.map(m => ({ + id: m.id, + name: m.get('name'), + reactivated: reactivatedUserIds.includes(m.id) + }))), + headers: { 'Content-Type': 'application/json' } + }) + // TODO: what to do with the response? check if succeeded or not? + } + } + } + }) diff --git a/api/models/User.js b/api/models/User.js index fd38cddfa..d2eaeafd6 100644 --- a/api/models/User.js +++ b/api/models/User.js @@ -776,7 +776,7 @@ module.exports = bookshelf.Model.extend(merge({ // Background jobs async afterLeaveGroup({ removedByModerator, groupId, userId }) { - const zapierTriggers = await ZapierTrigger.query(q => q.where({ group_id: groupId, type: 'member_leaves' })).fetchAll() + const zapierTriggers = await ZapierTrigger.forTypeAndGroups('member_leaves', groupId).fetchAll() if (zapierTriggers && zapierTriggers.length > 0) { const user = await User.find(userId) for (const trigger of zapierTriggers) { @@ -795,7 +795,7 @@ module.exports = bookshelf.Model.extend(merge({ if (user) { const memberships = await user.memberships().fetch() memberships.models.forEach(async (membership) => { - const zapierTriggers = await ZapierTrigger.query(q => q.where({ group_id: membership.get('group_id'), type: 'member_updated' })).fetchAll() + const zapierTriggers = await ZapierTrigger.forTypeAndGroups('member_udpated', membership.get('group_id')).fetchAll() for (const trigger of zapierTriggers) { const response = await fetch(trigger.get('target_url'), { method: 'post', diff --git a/api/models/ZapierTrigger.js b/api/models/ZapierTrigger.js index 09605a5b9..c51e6998d 100644 --- a/api/models/ZapierTrigger.js +++ b/api/models/ZapierTrigger.js @@ -1,4 +1,4 @@ -import { isEmpty, isEqual, difference } from 'lodash' +import { castArray, isEmpty, isEqual, difference } from 'lodash' module.exports = bookshelf.Model.extend(Object.assign({ tableName: 'zapier_triggers', @@ -9,8 +9,8 @@ module.exports = bookshelf.Model.extend(Object.assign({ return this.belongsTo(User) }, - group () { - return this.belongsTo(Group) + groups () { + return this.belongsToMany(Group).through(ZapierTriggerGroup) } }), { @@ -19,6 +19,14 @@ module.exports = bookshelf.Model.extend(Object.assign({ if (!id) return Promise.resolve(null) const where = { id } return this.where(where).fetch(opts) + }, + + forTypeAndGroups (type, groupIdOrArray) { + const groupIds = castArray(groupIdOrArray) + return ZapierTrigger.query(q => { + q.join('zapier_triggers_groups', 'trigger_id', 'zapier_triggers.id') + q.where({ type }).whereIn('group_id', groupIds) + }) } }) diff --git a/api/models/ZapierTriggerGroup.js b/api/models/ZapierTriggerGroup.js new file mode 100644 index 000000000..953540736 --- /dev/null +++ b/api/models/ZapierTriggerGroup.js @@ -0,0 +1,13 @@ +module.exports = bookshelf.Model.extend({ + tableName: 'zapier_triggers_groups', + requireFetch: false, + hasTimestamps: true, + + trigger: function () { + return this.belongsTo(ZapierTrigger) + }, + + group: function () { + return this.belongsTo(Group) + } +}) diff --git a/api/models/post/createPost.js b/api/models/post/createPost.js index a2251044a..45e63c732 100644 --- a/api/models/post/createPost.js +++ b/api/models/post/createPost.js @@ -87,8 +87,9 @@ export function afterCreatingPost (post, opts) { .then(() => post.isEvent() && post.updateEventInvitees(opts.eventInviteeIds || [], userId, trxOpts)) .then(() => Tag.updateForPost(post, opts.topicNames, userId, trx)) .then(() => updateTagsAndGroups(post, trx)) - .then(() => Queue.classMethod('Post', 'createActivities', {postId: post.id})) - .then(() => Queue.classMethod('Post', 'notifySlack', {postId: post.id})) + .then(() => Queue.classMethod('Post', 'createActivities', { postId: post.id })) + .then(() => Queue.classMethod('Post', 'notifySlack', { postId: post.id })) + .then(() => Queue.classMethod('Post', 'zapierTriggers', { postId: post.id })) } async function updateTagsAndGroups (post, trx) { diff --git a/migrations/20230221155959_zapier-triggers-multiple-groups.js b/migrations/20230221155959_zapier-triggers-multiple-groups.js new file mode 100644 index 000000000..58d3cf22d --- /dev/null +++ b/migrations/20230221155959_zapier-triggers-multiple-groups.js @@ -0,0 +1,28 @@ + +exports.up = async function (knex) { + await knex.schema.createTable('zapier_triggers_groups', table => { + table.increments().primary() + table.bigInteger('trigger_id').references('id').inTable('zapier_triggers') + table.bigInteger('group_id').references('id').inTable('groups') + table.index(['trigger_id']) + }) + + await knex.raw('alter table zapier_triggers_groups alter constraint zapier_triggers_groups_trigger_id_foreign deferrable initially deferred') + await knex.raw('alter table zapier_triggers_groups alter constraint zapier_triggers_groups_group_id_foreign deferrable initially deferred') + + await knex.raw('insert into zapier_triggers_groups (trigger_id, group_id) (select id, group_id from zapier_triggers)') + + await knex.schema.table('zapier_triggers', table => { + table.dropColumn('group_id') + table.jsonb('params') + }) +} + +exports.down = async function (knex) { + await knex.schema.table('zapier_triggers', table => { + table.dropColumn('params') + table.bigInteger('group_id').references('id').inTable('groups') + }) + await knex.raw('UPDATE zapier_triggers SET group_id = \'externalLink\' WHERE type= \'externalLink\'') + await knex.schema.dropTable('zapier_triggers_groups') +} diff --git a/package.json b/package.json index 6177fead1..1823ad16c 100644 --- a/package.json +++ b/package.json @@ -242,7 +242,9 @@ "UserExternalData", "UserSession", "UserVerificationCode", - "Validation" + "Validation", + "ZapierTrigger", + "ZapierTriggerGroup" ] }, "nodemonConfig": { From b68622d2e5c1b6937f9de131092d8b9703f9ed08 Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Tue, 21 Feb 2023 23:10:21 -0800 Subject: [PATCH 02/11] Fix creation of zapier triggers with group connections --- api/graphql/mutations/zapier.js | 18 ++++++++++-------- api/models/ZapierTrigger.js | 2 +- api/models/ZapierTriggerGroup.js | 2 +- ...21155959_zapier-triggers-multiple-groups.js | 8 ++++---- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/api/graphql/mutations/zapier.js b/api/graphql/mutations/zapier.js index f0acb249e..8801d9ad2 100644 --- a/api/graphql/mutations/zapier.js +++ b/api/graphql/mutations/zapier.js @@ -1,15 +1,17 @@ export async function createZapierTrigger (userId, groupIds, targetUrl, type, params) { - const trigger = ZapierTrigger.forge({ user_id: userId, target_url: targetUrl, type, params }) + return bookshelf.transaction(async (transacting) => { + const trigger = await ZapierTrigger.forge({ user_id: userId, target_url: targetUrl, type, params }).save({}, { transacting }) - if (groupIds && groupIds.length > 0) { - const memberships = await GroupMembership.query(q => q.where({ user_id: userId }).whereIn('group_id', groupIds)).fetchAll() - if (!memberships || memberships.length === 0) { - throw new GraphQLYogaError('You don\'t have access to any of these groups') + if (groupIds && groupIds.length > 0) { + const memberships = await GroupMembership.query(q => q.where({ user_id: userId }).whereIn('group_id', groupIds)).fetchAll({ transacting }) + if (!memberships || memberships.length === 0) { + throw new GraphQLYogaError('You don\'t have access to any of these groups') + } + await trigger.groups().attach(memberships.map(m => m.get('group_id')), { transacting }) } - trigger.groups().attach(memberships.map(m => m.group_id)) - } - return trigger.save() + return trigger + }) } export async function deleteZapierTrigger (userId, id) { diff --git a/api/models/ZapierTrigger.js b/api/models/ZapierTrigger.js index c51e6998d..ea87fba93 100644 --- a/api/models/ZapierTrigger.js +++ b/api/models/ZapierTrigger.js @@ -24,7 +24,7 @@ module.exports = bookshelf.Model.extend(Object.assign({ forTypeAndGroups (type, groupIdOrArray) { const groupIds = castArray(groupIdOrArray) return ZapierTrigger.query(q => { - q.join('zapier_triggers_groups', 'trigger_id', 'zapier_triggers.id') + q.join('zapier_triggers_groups', 'zapier_trigger_id', 'zapier_triggers.id') q.where({ type }).whereIn('group_id', groupIds) }) } diff --git a/api/models/ZapierTriggerGroup.js b/api/models/ZapierTriggerGroup.js index 953540736..d86fe7a31 100644 --- a/api/models/ZapierTriggerGroup.js +++ b/api/models/ZapierTriggerGroup.js @@ -1,7 +1,7 @@ module.exports = bookshelf.Model.extend({ tableName: 'zapier_triggers_groups', requireFetch: false, - hasTimestamps: true, + hasTimestamps: false, trigger: function () { return this.belongsTo(ZapierTrigger) diff --git a/migrations/20230221155959_zapier-triggers-multiple-groups.js b/migrations/20230221155959_zapier-triggers-multiple-groups.js index 58d3cf22d..92d64d7fc 100644 --- a/migrations/20230221155959_zapier-triggers-multiple-groups.js +++ b/migrations/20230221155959_zapier-triggers-multiple-groups.js @@ -2,15 +2,15 @@ exports.up = async function (knex) { await knex.schema.createTable('zapier_triggers_groups', table => { table.increments().primary() - table.bigInteger('trigger_id').references('id').inTable('zapier_triggers') + table.bigInteger('zapier_trigger_id').references('id').inTable('zapier_triggers') table.bigInteger('group_id').references('id').inTable('groups') - table.index(['trigger_id']) + table.index(['zapier_trigger_id']) }) - await knex.raw('alter table zapier_triggers_groups alter constraint zapier_triggers_groups_trigger_id_foreign deferrable initially deferred') + await knex.raw('alter table zapier_triggers_groups alter constraint zapier_triggers_groups_zapier_trigger_id_foreign deferrable initially deferred') await knex.raw('alter table zapier_triggers_groups alter constraint zapier_triggers_groups_group_id_foreign deferrable initially deferred') - await knex.raw('insert into zapier_triggers_groups (trigger_id, group_id) (select id, group_id from zapier_triggers)') + await knex.raw('insert into zapier_triggers_groups (zapier_trigger_id, group_id) (select id, group_id from zapier_triggers)') await knex.schema.table('zapier_triggers', table => { table.dropColumn('group_id') From ac3b8f4f311ebe65be22b83930ba3fa12bc27155 Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Tue, 21 Feb 2023 23:46:19 -0800 Subject: [PATCH 03/11] Return correct post data for new_post zapier trigger --- api/models/Post.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/api/models/Post.js b/api/models/Post.js index a4e6aa03f..43c86c5b1 100644 --- a/api/models/Post.js +++ b/api/models/Post.js @@ -5,6 +5,7 @@ import { init, getEmojiDataFromNative } from 'emoji-mart' import { difference, filter, isNull, omitBy, uniqBy, isEmpty, intersection, isUndefined, pick } from 'lodash/fp' import { flatten, sortBy } from 'lodash' import { TextHelpers } from 'hylo-shared' +import fetch from 'node-fetch' import { postRoom, pushToSockets } from '../services/Websockets' import { fulfill, unfulfill } from './post/fulfillPost' import EnsureLoad from './mixins/EnsureLoad' @@ -668,24 +669,33 @@ module.exports = bookshelf.Model.extend(Object.assign({ // Background task to fire zapier triggers on new_post zapierTriggers: async ({ postId }) => { - const post = await Post.find(postId, { withRelated: ['groups'] }) + const post = await Post.find(postId, { withRelated: ['groups', 'tags'] }) if (!post) return const groupIds = post.relations.groups.map(g => g.id) const zapierTriggers = await ZapierTrigger.forTypeAndGroups('new_post', groupIds).fetchAll() if (zapierTriggers && zapierTriggers.length > 0) { - const members = await User.query(q => q.whereIn('id', newUserIds.concat(reactivatedUserIds))).fetchAll() for (const trigger of zapierTriggers) { // Check if this trigger is only for certain post types and if so whether it matches this post type if (trigger.get('params')?.types?.length > 0 && !trigger.get('params').types.includes(post.get('type'))) { continue } + const response = await fetch(trigger.get('target_url'), { method: 'post', - body: JSON.stringify(members.map(m => ({ - id: m.id, - name: m.get('name'), - reactivated: reactivatedUserIds.includes(m.id) - }))), + body: JSON.stringify({ + id: post.id, + announcement: post.get('announcement'), + title: post.summary(), + details: post.details(), + createdAt: post.get('created_at'), + endTime: post.get('end_time'), + isPublic: post.get('is_public'), + location: post.get('location'), + startTime: post.get('start_time'), + type: post.get('type'), + groups: post.relations.groups.map(g => ({ id: g.id, name: g.get('name')})), + topics: post.relations.tags.map(t => ({ name: t.get('name')})), + }), headers: { 'Content-Type': 'application/json' } }) // TODO: what to do with the response? check if succeeded or not? From e61efaca9c6ef31cf479f8b5772837c0db34e484 Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Wed, 22 Feb 2023 15:02:17 -0800 Subject: [PATCH 04/11] Send post creator and post urls to Zapier trigger for new_post --- api/models/Post.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/api/models/Post.js b/api/models/Post.js index 43c86c5b1..015b3f8f8 100644 --- a/api/models/Post.js +++ b/api/models/Post.js @@ -669,7 +669,7 @@ module.exports = bookshelf.Model.extend(Object.assign({ // Background task to fire zapier triggers on new_post zapierTriggers: async ({ postId }) => { - const post = await Post.find(postId, { withRelated: ['groups', 'tags'] }) + const post = await Post.find(postId, { withRelated: ['groups', 'tags', 'user'] }) if (!post) return const groupIds = post.relations.groups.map(g => g.id) const zapierTriggers = await ZapierTrigger.forTypeAndGroups('new_post', groupIds).fetchAll() @@ -680,20 +680,23 @@ module.exports = bookshelf.Model.extend(Object.assign({ continue } + const creator = post.relations.user const response = await fetch(trigger.get('target_url'), { method: 'post', body: JSON.stringify({ id: post.id, announcement: post.get('announcement'), - title: post.summary(), - details: post.details(), createdAt: post.get('created_at'), + creator: { name: creator.get('name'), url: Frontend.Route.profile(creator) }, + details: post.details(), endTime: post.get('end_time'), isPublic: post.get('is_public'), location: post.get('location'), startTime: post.get('start_time'), + title: post.summary(), type: post.get('type'), - groups: post.relations.groups.map(g => ({ id: g.id, name: g.get('name')})), + url: Frontend.Route.post(post), + groups: post.relations.groups.map(g => ({ id: g.id, name: g.get('name'), postUrl: Frontend.Route.post(post, g) })), topics: post.relations.tags.map(t => ({ name: t.get('name')})), }), headers: { 'Content-Type': 'application/json' } From f4978119d87b4fc198d7cc50e487ed1a5261199e Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Thu, 23 Feb 2023 14:49:36 -0800 Subject: [PATCH 05/11] Send more info with zapier triggers Correctly delete zapier triggers --- api/graphql/mutations/zapier.js | 13 ++++++++----- api/models/Group.js | 17 ++++++++++++++++- api/models/Post.js | 3 ++- api/models/User.js | 23 ++++++++++++++++------- api/services/Frontend.js | 5 ++++- 5 files changed, 46 insertions(+), 15 deletions(-) diff --git a/api/graphql/mutations/zapier.js b/api/graphql/mutations/zapier.js index 8801d9ad2..1fedfa903 100644 --- a/api/graphql/mutations/zapier.js +++ b/api/graphql/mutations/zapier.js @@ -15,10 +15,13 @@ export async function createZapierTrigger (userId, groupIds, targetUrl, type, pa } export async function deleteZapierTrigger (userId, id) { - const trigger = await ZapierTrigger.query(q => q.where({ id, userId })).fetch() + return bookshelf.transaction(async (transacting) => { + const trigger = await ZapierTrigger.query(q => q.where({ id, userId })).fetch() - if (trigger) { - await trigger.destroy() - } - return true + if (trigger) { + await ZapierTriggerGroup.where({ zapier_trigger_id: id }).destroy({ transacting }) + await trigger.destroy({ transacting }) + } + return true + }) } diff --git a/api/models/Group.js b/api/models/Group.js index 017302f07..db4e45d4a 100644 --- a/api/models/Group.js +++ b/api/models/Group.js @@ -547,14 +547,29 @@ module.exports = bookshelf.Model.extend(merge({ const zapierTriggers = await ZapierTrigger.forTypeAndGroups('new_member', groupId).fetchAll() if (zapierTriggers && zapierTriggers.length > 0) { + const group = await Group.find(groupId) const members = await User.query(q => q.whereIn('id', newUserIds.concat(reactivatedUserIds))).fetchAll() for (const trigger of zapierTriggers) { const response = await fetch(trigger.get('target_url'), { method: 'post', body: JSON.stringify(members.map(m => ({ id: m.id, + avatarUrl: m.get('avatar_url'), + bio: m.get('bio'), + contactEmail: m.get('contact_email'), + contactPhone: m.get('contact_phone'), + facebookUrl: m.get('facebook_url'), + linkedinUrl: m.get('linkedin_url'), + location: m.get('location'), name: m.get('name'), - reactivated: reactivatedUserIds.includes(m.id) + profileUrl: Frontend.Route.profile(m, group), + tagline: m.get('tagline'), + twitterName: m.get('twitter_name'), + url: m.get('url'), + // Whether this user was previously in the group and is being reactivated + reactivated: reactivatedUserIds.includes(m.id), + // Which group were they added to, since the trigger can be for multiple groups + group: { id: group.id, name: group.get('name'), url: Frontend.Route.group(group) } }))), headers: { 'Content-Type': 'application/json' } }) diff --git a/api/models/Post.js b/api/models/Post.js index 015b3f8f8..8d6f98f03 100644 --- a/api/models/Post.js +++ b/api/models/Post.js @@ -671,6 +671,7 @@ module.exports = bookshelf.Model.extend(Object.assign({ zapierTriggers: async ({ postId }) => { const post = await Post.find(postId, { withRelated: ['groups', 'tags', 'user'] }) if (!post) return + const groupIds = post.relations.groups.map(g => g.id) const zapierTriggers = await ZapierTrigger.forTypeAndGroups('new_post', groupIds).fetchAll() if (zapierTriggers && zapierTriggers.length > 0) { @@ -696,7 +697,7 @@ module.exports = bookshelf.Model.extend(Object.assign({ title: post.summary(), type: post.get('type'), url: Frontend.Route.post(post), - groups: post.relations.groups.map(g => ({ id: g.id, name: g.get('name'), postUrl: Frontend.Route.post(post, g) })), + groups: post.relations.groups.map(g => ({ id: g.id, name: g.get('name'), url: Frontend.Route.group(g), postUrl: Frontend.Route.post(post, g) })), topics: post.relations.tags.map(t => ({ name: t.get('name')})), }), headers: { 'Content-Type': 'application/json' } diff --git a/api/models/User.js b/api/models/User.js index d2eaeafd6..8219f6130 100644 --- a/api/models/User.js +++ b/api/models/User.js @@ -779,13 +779,22 @@ module.exports = bookshelf.Model.extend(merge({ const zapierTriggers = await ZapierTrigger.forTypeAndGroups('member_leaves', groupId).fetchAll() if (zapierTriggers && zapierTriggers.length > 0) { const user = await User.find(userId) - for (const trigger of zapierTriggers) { - const response = await fetch(trigger.get('target_url'), { - method: 'post', - body: JSON.stringify({ id: user.id, name: user.get('name'), removedByModerator }), - headers: { 'Content-Type': 'application/json' } - }) - // TODO: what to do with the response? check if succeeded or not? + const group = await Group.find(groupId) + if (user && group) { + for (const trigger of zapierTriggers) { + const response = await fetch(trigger.get('target_url'), { + method: 'post', + body: JSON.stringify({ + id: user.id, + name: user.get('name'), + // Which group were they removed from, since the trigger can be for multiple groups + group: { id: group.id, name: group.get('name'), url: Frontend.Route.group(group) }, + removedByModerator + }), + headers: { 'Content-Type': 'application/json' } + }) + // TODO: what to do with the response? check if succeeded or not? + } } } }, diff --git a/api/services/Frontend.js b/api/services/Frontend.js index d48683f4b..741d16c7e 100644 --- a/api/services/Frontend.js +++ b/api/services/Frontend.js @@ -127,7 +127,10 @@ module.exports = { return url(`${contextUrl}/map/post/${getModelId(post)}`) }, - profile: function (user) { + profile: function (user, group) { + if (group) { + url(`/groups/${getSlug(group)}/members/${getModelId(user)}`) + } return url(`/members/${getModelId(user)}`) }, From b8b1e5c4a3380c08ac7326d71fdc61294b1f2c52 Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Thu, 23 Feb 2023 14:55:42 -0800 Subject: [PATCH 06/11] Send profileUrl with member updated zapier trigger --- api/models/User.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/models/User.js b/api/models/User.js index 8219f6130..dc42ff8ab 100644 --- a/api/models/User.js +++ b/api/models/User.js @@ -808,7 +808,7 @@ module.exports = bookshelf.Model.extend(merge({ for (const trigger of zapierTriggers) { const response = await fetch(trigger.get('target_url'), { method: 'post', - body: JSON.stringify(Object.assign({ id: user.id }, changes)), + body: JSON.stringify(Object.assign({ id: user.id, profileUrl: Frontend.Route.profile(user) }, changes)), headers: { 'Content-Type': 'application/json' } }) // TODO: what to do with the response? check if succeeded or not? From 3bab67dc089970fbb747728e915b3d29409cfd90 Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Thu, 23 Feb 2023 15:19:47 -0800 Subject: [PATCH 07/11] Fix return of profile URL within a group --- api/services/Frontend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/services/Frontend.js b/api/services/Frontend.js index 741d16c7e..2fbbbb13a 100644 --- a/api/services/Frontend.js +++ b/api/services/Frontend.js @@ -129,7 +129,7 @@ module.exports = { profile: function (user, group) { if (group) { - url(`/groups/${getSlug(group)}/members/${getModelId(user)}`) + return url(`/groups/${getSlug(group)}/members/${getModelId(user)}`) } return url(`/members/${getModelId(user)}`) }, From 95940d5f2b82b1057d80cffc2b7cdc0298d00731 Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Sun, 26 Feb 2023 21:04:50 -0800 Subject: [PATCH 08/11] Fix member_updates zapier trigger --- api/models/User.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/models/User.js b/api/models/User.js index dc42ff8ab..4f18581c7 100644 --- a/api/models/User.js +++ b/api/models/User.js @@ -802,13 +802,13 @@ module.exports = bookshelf.Model.extend(merge({ async afterUpdate({ userId, changes }) { const user = await User.find(userId) if (user) { - const memberships = await user.memberships().fetch() + const memberships = await user.memberships().fetch({ withRelated: 'group' }) memberships.models.forEach(async (membership) => { - const zapierTriggers = await ZapierTrigger.forTypeAndGroups('member_udpated', membership.get('group_id')).fetchAll() + const zapierTriggers = await ZapierTrigger.forTypeAndGroups('member_updated', membership.get('group_id')).fetchAll() for (const trigger of zapierTriggers) { const response = await fetch(trigger.get('target_url'), { method: 'post', - body: JSON.stringify(Object.assign({ id: user.id, profileUrl: Frontend.Route.profile(user) }, changes)), + body: JSON.stringify(Object.assign({ id: user.id, profileUrl: Frontend.Route.profile(user, membership.relations.group) }, changes)), headers: { 'Content-Type': 'application/json' } }) // TODO: what to do with the response? check if succeeded or not? From 1c7047e74f418552b2d8d7562b81c19a646133de Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Sun, 26 Feb 2023 21:30:53 -0800 Subject: [PATCH 09/11] Logging --- api/models/User.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/api/models/User.js b/api/models/User.js index 4f18581c7..eb8b0c357 100644 --- a/api/models/User.js +++ b/api/models/User.js @@ -777,11 +777,19 @@ module.exports = bookshelf.Model.extend(merge({ async afterLeaveGroup({ removedByModerator, groupId, userId }) { const zapierTriggers = await ZapierTrigger.forTypeAndGroups('member_leaves', groupId).fetchAll() + console.log("member leaves", zapierTriggers, groupId, userId) if (zapierTriggers && zapierTriggers.length > 0) { const user = await User.find(userId) const group = await Group.find(groupId) if (user && group) { for (const trigger of zapierTriggers) { + console.log("doing trigger", trigger.get('target_url'), JSON.stringify({ + id: user.id, + name: user.get('name'), + // Which group were they removed from, since the trigger can be for multiple groups + group: { id: group.id, name: group.get('name'), url: Frontend.Route.group(group) }, + removedByModerator + })) const response = await fetch(trigger.get('target_url'), { method: 'post', body: JSON.stringify({ From 354afed9fb9a3d98cd840221bb3c6867cdcda579 Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Sun, 26 Feb 2023 21:55:04 -0800 Subject: [PATCH 10/11] Remove console.log --- api/models/User.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/api/models/User.js b/api/models/User.js index eb8b0c357..4f18581c7 100644 --- a/api/models/User.js +++ b/api/models/User.js @@ -777,19 +777,11 @@ module.exports = bookshelf.Model.extend(merge({ async afterLeaveGroup({ removedByModerator, groupId, userId }) { const zapierTriggers = await ZapierTrigger.forTypeAndGroups('member_leaves', groupId).fetchAll() - console.log("member leaves", zapierTriggers, groupId, userId) if (zapierTriggers && zapierTriggers.length > 0) { const user = await User.find(userId) const group = await Group.find(groupId) if (user && group) { for (const trigger of zapierTriggers) { - console.log("doing trigger", trigger.get('target_url'), JSON.stringify({ - id: user.id, - name: user.get('name'), - // Which group were they removed from, since the trigger can be for multiple groups - group: { id: group.id, name: group.get('name'), url: Frontend.Route.group(group) }, - removedByModerator - })) const response = await fetch(trigger.get('target_url'), { method: 'post', body: JSON.stringify({ From de318958fc5ef23331ff36cf45cc871c78e657da Mon Sep 17 00:00:00 2001 From: Tibet Sprague Date: Sun, 26 Feb 2023 22:14:32 -0800 Subject: [PATCH 11/11] Fix tests --- api/models/FlaggedItem.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/models/FlaggedItem.js b/api/models/FlaggedItem.js index 381161dd5..d47f5bb25 100644 --- a/api/models/FlaggedItem.js +++ b/api/models/FlaggedItem.js @@ -43,7 +43,7 @@ module.exports = bookshelf.Model.extend({ const comment = await this.getObject() return Frontend.Route.comment({ comment, groupSlug: group ? group.get('slug') : null }) case FlaggedItem.Type.MEMBER: - return Frontend.Route.profile(this.get('object_id')) + return Frontend.Route.profile(this.get('object_id'), group) default: throw new GraphQLYogaError('Unsupported type for Flagged Item', this.get('object_type')) }