11import { createLogger } from '@sim/logger'
22import { type NextRequest , NextResponse } from 'next/server'
3+ import { z } from 'zod'
34import { getSession } from '@/lib/auth'
45import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
56import { normalizeEmail } from '@/lib/invitations/core'
@@ -20,9 +21,19 @@ interface BatchInvitationFailure {
2021 error : string
2122}
2223
23- function isRecord ( value : unknown ) : value is Record < string , unknown > {
24- return typeof value === 'object' && value !== null
25- }
24+ const batchInvitationSchema = z . object ( {
25+ workspaceId : z . string ( ) . min ( 1 , 'Workspace ID is required' ) ,
26+ invitations : z
27+ . array (
28+ z . object ( {
29+ email : z . string ( ) . trim ( ) . min ( 1 , 'Invitation email is required' ) ,
30+ permission : z . string ( ) . optional ( ) ,
31+ } )
32+ )
33+ . min ( 1 , 'At least one invitation is required' ) ,
34+ } )
35+
36+ type BatchInvitationRequest = z . infer < typeof batchInvitationSchema >
2637
2738function batchErrorResponse ( error : unknown ) {
2839 if ( error instanceof WorkspaceInvitationError ) {
@@ -51,14 +62,14 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
5162 }
5263
5364 try {
54- const body = ( await req . json ( ) ) as { workspaceId ?: unknown ; invitations ?: unknown }
55- if ( typeof body . workspaceId !== 'string' || body . workspaceId . length === 0 ) {
56- return NextResponse . json ( { error : 'Workspace ID is required' } , { status : 400 } )
57- }
58-
59- if ( ! Array . isArray ( body . invitations ) || body . invitations . length === 0 ) {
60- return NextResponse . json ( { error : 'At least one invitation is required' } , { status : 400 } )
65+ const parsedBody = batchInvitationSchema . safeParse ( await req . json ( ) . catch ( ( ) => null ) )
66+ if ( ! parsedBody . success ) {
67+ return NextResponse . json (
68+ { error : parsedBody . error . errors [ 0 ] ?. message ?? 'Invalid invitation batch payload' } ,
69+ { status : 400 }
70+ )
6171 }
72+ const body : BatchInvitationRequest = parsedBody . data
6273
6374 const context = await prepareWorkspaceInvitationContext ( {
6475 workspaceId : body . workspaceId ,
@@ -73,20 +84,6 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
7384 const seenEmails = new Set < string > ( )
7485
7586 for ( const item of body . invitations ) {
76- if ( ! isRecord ( item ) || typeof item . email !== 'string' ) {
77- return NextResponse . json (
78- { error : 'Each invitation must include an email' } ,
79- { status : 400 }
80- )
81- }
82-
83- if ( item . permission !== undefined && typeof item . permission !== 'string' ) {
84- return NextResponse . json (
85- { error : 'Invitation permission must be a string when provided' } ,
86- { status : 400 }
87- )
88- }
89-
9087 const normalizedEmail = normalizeEmail ( item . email )
9188 if ( seenEmails . has ( normalizedEmail ) ) {
9289 failed . push ( {
0 commit comments