Skip to content

Commit 774caa1

Browse files
committed
use zod schema for route
1 parent 4b0b362 commit 774caa1

1 file changed

Lines changed: 21 additions & 24 deletions

File tree

  • apps/sim/app/api/workspaces/invitations/batch

apps/sim/app/api/workspaces/invitations/batch/route.ts

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
3+
import { z } from 'zod'
34
import { getSession } from '@/lib/auth'
45
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
56
import { 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

2738
function 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

Comments
 (0)