feat(admin): PATCH /api/admin/members/:id/slack-status

Endpoint that flips a member's slackInvited flag manually after the
admin has actually sent the Slack invitation through Slack's UI. No
Slack API call is made from this app.

- Body validated via Zod literal-true schema (no undo path for the
  pilot — admins correct mistakes in the database if needed).
- Idempotent: re-marking an already-invited member is a no-op,
  preserving the original slackInvitedAt and not duplicating the
  activity log entry.
- Activity log: slack_invited_manually, actor = admin from
  requireAdmin, subject = the target member.
This commit is contained in:
Jennie Robinson Faber 2026-04-29 12:23:07 +01:00
parent 55029e7eb7
commit 0981596ea2
3 changed files with 227 additions and 0 deletions

View file

@ -0,0 +1,35 @@
import Member from '../../../../models/member.js'
import { connectDB } from '../../../../utils/mongoose.js'
import { validateBody } from '../../../../utils/validateBody.js'
import { adminSlackStatusSchema } from '../../../../utils/schemas.js'
export default defineEventHandler(async (event) => {
const admin = await requireAdmin(event)
await validateBody(event, adminSlackStatusSchema)
await connectDB()
const memberId = getRouterParam(event, 'id')
const existing = await Member.findById(memberId)
if (!existing) {
throw createError({
statusCode: 404,
statusMessage: 'Member not found.'
})
}
// Idempotent: if already invited, no-op (preserve original slackInvitedAt, no log).
if (existing.slackInvited === true) {
return { success: true, member: existing }
}
const member = await Member.findByIdAndUpdate(
memberId,
{ slackInvited: true, slackInvitedAt: new Date() },
{ new: true, runValidators: false }
)
logActivity(memberId, 'slack_invited_manually', {}, { performedBy: admin._id })
return { success: true, member }
})

View file

@ -314,6 +314,10 @@ export const adminRoleUpdateSchema = z.object({
role: z.enum(['admin', 'member'])
})
export const adminSlackStatusSchema = z.object({
slackInvited: z.literal(true)
}).strict()
export const bulkMemberImportSchema = z.object({
members: z.array(z.object({
name: z.string().min(1).max(200),