feat(activation): wire autoFlagPreExistingSlackAccess into self-serve paths

Replaces the per-file inviteToSlack helpers with a single auto-flag
call. Self-serve activation paths now check for pre-existing workspace
membership (silent on miss) instead of attempting an admin-only invite.

- helcim/subscription.post.js: removed local inviteToSlack; both
  free- and paid-tier activation branches now call the helper, then
  notifyNewMember with the canonical 'manual_invitation_required' arg.
- members/create.post.js: same shape — helper + canonical notify arg.
- invite/accept.post.js (free-tier branch): added the helper call after
  member creation. Free-tier had no prior Slack call (audit confirmed);
  paid-tier remains untouched and activates via the Helcim webhook.

Admin-created and CSV-imported members intentionally do NOT call the
helper — admins flip the flag manually after sending the invite.

Test stub for autoFlagPreExistingSlackAccess added to server setup.
This commit is contained in:
Jennie Robinson Faber 2026-04-29 12:21:12 +01:00
parent b1d8cb1966
commit 55029e7eb7
5 changed files with 262 additions and 152 deletions

View file

@ -7,80 +7,6 @@ import { memberCreateSchema } from '../../utils/schemas.js'
import { sendWelcomeEmail } from '../../utils/resend.js'
import { assignMemberNumber } from '../../utils/memberNumber.js'
// Function to invite member to Slack
async function inviteToSlack(member) {
try {
const slackService = getSlackService()
if (!slackService) {
console.warn('Slack service not configured, skipping invitation')
return
}
console.warn(`Processing Slack invitation for member`)
const inviteResult = await slackService.inviteUserToSlack(
member.email,
member.name
)
if (inviteResult.success) {
// Update member record based on the actual result
if (inviteResult.status === 'existing_user_added_to_channel' ||
inviteResult.status === 'user_already_in_channel' ||
inviteResult.status === 'new_user_invited_to_workspace') {
await Member.findByIdAndUpdate(
member._id,
{ $set: { slackInviteStatus: 'sent', slackUserId: inviteResult.userId, slackInvited: true } },
{ runValidators: false }
)
} else {
// Manual invitation required
await Member.findByIdAndUpdate(
member._id,
{ $set: { slackInviteStatus: 'pending', slackInvited: false } },
{ runValidators: false }
)
}
// Send notification to vetting channel
await slackService.notifyNewMember(
member.name,
member.email,
member.circle,
member.contributionAmount,
inviteResult.status
)
console.warn(`Slack invitation processed: ${inviteResult.status}`)
} else {
// Update member record to reflect failed invitation
await Member.findByIdAndUpdate(
member._id,
{ $set: { slackInviteStatus: 'failed' } },
{ runValidators: false }
)
console.error(`Failed to process Slack invitation: ${inviteResult.error}`)
// Don't throw error - member creation should still succeed
}
} catch (error) {
console.error('Error during Slack invitation process:', error)
// Update member record to reflect failed invitation
try {
await Member.findByIdAndUpdate(
member._id,
{ $set: { slackInviteStatus: 'failed' } },
{ runValidators: false }
)
} catch (saveError) {
console.error('Failed to update member Slack status:', saveError)
}
// Don't throw error - member creation should still succeed
}
}
export default defineEventHandler(async (event) => {
// Ensure database is connected
await connectDB()
@ -107,8 +33,22 @@ export default defineEventHandler(async (event) => {
circle: member.circle
}, { timestamp: member.createdAt })
// Send Slack invitation for new members
await inviteToSlack(member)
// Auto-flag pre-existing Slack workspace members; admin manually invites the rest.
await autoFlagPreExistingSlackAccess(member)
try {
const slackService = getSlackService()
if (slackService) {
await slackService.notifyNewMember(
member.name,
member.email,
member.circle,
member.contributionAmount,
'manual_invitation_required'
)
}
} catch (err) {
console.error('[slack] notifyNewMember failed:', err)
}
// Send welcome email (non-blocking)
try {