Skip to content

Commit 88d9fb0

Browse files
committed
more fixes
1 parent 4490e07 commit 88d9fb0

39 files changed

Lines changed: 809 additions & 397 deletions

File tree

apps/sim/app/(auth)/oauth/consent/page.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import { ArrowLeftRight } from 'lucide-react'
55
import Image from 'next/image'
66
import { useRouter, useSearchParams } from 'next/navigation'
77
import { Button, Loader } from '@/components/emcn'
8+
import { requestJson } from '@/lib/api/client/request'
9+
import { oauthAuthorizeParamsContract } from '@/lib/api/contracts/oauth-connections'
810
import { signOut, useSession } from '@/lib/auth/auth-client'
911
import { AUTH_SUBMIT_BTN } from '@/app/(auth)/components/auth-button-classes'
1012

@@ -102,16 +104,15 @@ export default function OAuthConsentPage() {
102104
const handleSwitchAccount = useCallback(async () => {
103105
if (!consentCode) return
104106

105-
// boundary-raw-fetch: route handler not yet contract-backed (uses safeParse, not parseRequest)
106-
const res = await fetch(`/api/auth/oauth2/authorize-params?consent_code=${consentCode}`, {
107-
credentials: 'include',
108-
})
109-
if (!res.ok) {
107+
const params = await requestJson(oauthAuthorizeParamsContract, {
108+
query: { consent_code: consentCode },
109+
}).catch(() => null)
110+
111+
if (!params) {
110112
setError('Unable to switch accounts. Please re-initiate the connection.')
111113
return
112114
}
113115

114-
const params = (await res.json()) as Record<string, string | null>
115116
const authorizeUrl = new URL('/api/auth/oauth2/authorize', window.location.origin)
116117
for (const [key, value] of Object.entries(params)) {
117118
if (value) authorizeUrl.searchParams.set(key, value)

apps/sim/app/(landing)/components/auth-modal/auth-modal.tsx

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {
1414
ModalTrigger,
1515
} from '@/components/emcn'
1616
import { GithubIcon, GoogleIcon } from '@/components/icons'
17+
import { requestJson } from '@/lib/api/client/request'
18+
import { type AuthProviderStatusResponse, getAuthProvidersContract } from '@/lib/api/contracts/auth'
1719
import { client } from '@/lib/auth/auth-client'
1820
import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env'
1921
import { captureClientEvent } from '@/lib/posthog/client'
@@ -30,13 +32,9 @@ interface AuthModalProps {
3032
source: PostHogEventMap['auth_modal_opened']['source']
3133
}
3234

33-
interface ProviderStatus {
34-
githubAvailable: boolean
35-
googleAvailable: boolean
36-
registrationDisabled: boolean
37-
}
35+
type ProviderStatus = AuthProviderStatusResponse
3836

39-
let fetchPromise: Promise<ProviderStatus> | null = null
37+
let fetchPromise: Promise<AuthProviderStatusResponse> | null = null
4038

4139
const FALLBACK_STATUS: ProviderStatus = {
4240
githubAvailable: false,
@@ -49,13 +47,8 @@ const SOCIAL_BTN =
4947

5048
function fetchProviderStatus(): Promise<ProviderStatus> {
5149
if (fetchPromise) return fetchPromise
52-
// boundary-raw-fetch: /api/auth/providers route has no parseRequest contract (zero-input GET, no boundary contract exists)
53-
fetchPromise = fetch('/api/auth/providers')
54-
.then((r) => {
55-
if (!r.ok) throw new Error(`HTTP ${r.status}`)
56-
return r.json()
57-
})
58-
.then(({ githubAvailable, googleAvailable, registrationDisabled }: ProviderStatus) => ({
50+
fetchPromise = requestJson(getAuthProvidersContract, {})
51+
.then(({ githubAvailable, googleAvailable, registrationDisabled }) => ({
5952
githubAvailable,
6053
googleAvailable,
6154
registrationDisabled,

apps/sim/app/(landing)/components/contact/contact-form.tsx

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import { useMutation } from '@tanstack/react-query'
77
import Link from 'next/link'
88
import { Combobox, Input, Textarea } from '@/components/emcn'
99
import { Check } from '@/components/emcn/icons'
10+
import { requestJson } from '@/lib/api/client/request'
1011
import {
1112
CONTACT_TOPIC_OPTIONS,
1213
type ContactRequestPayload,
1314
contactRequestSchema,
15+
type SubmitContactBody,
16+
submitContactContract,
1417
} from '@/lib/api/contracts/contact'
1518
import { flattenFieldErrors } from '@/lib/api/contracts/primitives'
1619
import { getEnv } from '@/lib/core/config/env'
@@ -53,30 +56,8 @@ const LANDING_SUBMIT =
5356
const LANDING_LABEL =
5457
'font-[500] font-season text-[13px] text-[var(--landing-text)] tracking-[0.02em]'
5558

56-
interface SubmitContactRequestInput extends ContactRequestPayload {
57-
website: string
58-
captchaToken?: string
59-
captchaUnavailable?: boolean
60-
}
61-
62-
async function submitContactRequest(payload: SubmitContactRequestInput) {
63-
// boundary-raw-fetch: payload includes turnstile captcha + honeypot fields not in submitContactContract body
64-
const response = await fetch('/api/contact', {
65-
method: 'POST',
66-
headers: { 'Content-Type': 'application/json' },
67-
body: JSON.stringify(payload),
68-
})
69-
70-
const result = (await response.json().catch(() => null)) as {
71-
error?: string
72-
message?: string
73-
} | null
74-
75-
if (!response.ok) {
76-
throw new Error(result?.error || 'Failed to send message')
77-
}
78-
79-
return result
59+
async function submitContactRequest(payload: SubmitContactBody) {
60+
return requestJson(submitContactContract, { body: payload })
8061
}
8162

8263
export function ContactForm() {

apps/sim/app/_shell/providers/session-provider.tsx

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import type React from 'react'
44
import { createContext, useCallback, useEffect, useMemo, useState } from 'react'
55
import { createLogger } from '@sim/logger'
66
import { useQueryClient } from '@tanstack/react-query'
7+
import { requestJson } from '@/lib/api/client/request'
8+
import { listCreatorOrganizationsContract } from '@/lib/api/contracts/creator-profile'
79
import { client } from '@/lib/auth/auth-client'
810
import { extractSessionDataFromAuthClientResult } from '@/lib/auth/session-response'
911

@@ -92,15 +94,9 @@ export function SessionProvider({ children }: { children: React.ReactNode }) {
9294
}
9395

9496
try {
95-
// boundary-raw-fetch: GET route handler not yet contract-backed (no GET contract for /api/organizations)
96-
const response = await fetch('/api/organizations')
97-
if (!response.ok) {
98-
return
99-
}
97+
const orgData = await requestJson(listCreatorOrganizationsContract, {}).catch(() => null)
98+
if (!orgData) return
10099

101-
const orgData = (await response.json()) as {
102-
organizations?: Array<{ id: string }>
103-
}
104100
const organizationId = orgData.organizations?.[0]?.id
105101

106102
if (!organizationId || isCancelled) {

apps/sim/app/api/auth/oauth2/authorize-params/route.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { verification } from '@sim/db/schema'
33
import { and, eq, gt } from 'drizzle-orm'
44
import type { NextRequest } from 'next/server'
55
import { NextResponse } from 'next/server'
6-
import { oauthAuthorizeParamsQuerySchema } from '@/lib/api/contracts/oauth-connections'
7-
import { getValidationErrorMessage } from '@/lib/api/server'
6+
import { oauthAuthorizeParamsContract } from '@/lib/api/contracts/oauth-connections'
7+
import { parseRequest } from '@/lib/api/server'
88
import { getSession } from '@/lib/auth'
99
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1010

@@ -19,16 +19,9 @@ export const GET = withRouteHandler(async (request: NextRequest) => {
1919
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
2020
}
2121

22-
const parsedQuery = oauthAuthorizeParamsQuerySchema.safeParse({
23-
consent_code: request.nextUrl.searchParams.get('consent_code') || undefined,
24-
})
25-
if (!parsedQuery.success) {
26-
return NextResponse.json(
27-
{ error: getValidationErrorMessage(parsedQuery.error) },
28-
{ status: 400 }
29-
)
30-
}
31-
const consentCode = parsedQuery.data.consent_code
22+
const parsed = await parseRequest(oauthAuthorizeParamsContract, request, {})
23+
if (!parsed.success) return parsed.response
24+
const consentCode = parsed.data.query.consent_code
3225

3326
const [record] = await db
3427
.select({ value: verification.value })

apps/sim/app/api/auth/providers/route.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
1+
import type { NextRequest } from 'next/server'
12
import { NextResponse } from 'next/server'
3+
import { getAuthProvidersContract } from '@/lib/api/contracts/auth'
4+
import { parseRequest } from '@/lib/api/server'
25
import { isRegistrationDisabled } from '@/lib/core/config/feature-flags'
36
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
47
import { getOAuthProviderStatus } from '@/app/(auth)/components/oauth-provider-checker'
58

69
export const dynamic = 'force-dynamic'
710

8-
export const GET = withRouteHandler(async () => {
11+
export const GET = withRouteHandler(async (request: NextRequest) => {
12+
const parsed = await parseRequest(getAuthProvidersContract, request, {})
13+
if (!parsed.success) return parsed.response
14+
915
const { githubAvailable, googleAvailable } = await getOAuthProviderStatus()
1016
return NextResponse.json({
1117
githubAvailable,

apps/sim/app/api/contact/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { renderHelpConfirmationEmail } from '@/components/emails'
44
import {
5-
contactRequestSchema,
65
getContactTopicLabel,
76
mapContactTopicToHelpType,
7+
submitContactBodySchema,
88
} from '@/lib/api/contracts/contact'
99
import { getValidationErrorMessage } from '@/lib/api/server'
1010
import { env } from '@/lib/core/config/env'
@@ -120,7 +120,7 @@ export const POST = withRouteHandler(async (req: NextRequest) => {
120120
}
121121
}
122122

123-
const validationResult = contactRequestSchema.safeParse(body)
123+
const validationResult = submitContactBodySchema.safeParse(body)
124124

125125
if (!validationResult.success) {
126126
logger.warn(`[${requestId}] Invalid contact request data`, {

apps/sim/app/api/credential-sets/invite/[token]/route.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ import { createLogger } from '@sim/logger'
1010
import { generateId } from '@sim/utils/id'
1111
import { and, eq } from 'drizzle-orm'
1212
import { type NextRequest, NextResponse } from 'next/server'
13-
import { credentialSetInviteTokenParamsSchema } from '@/lib/api/contracts/credential-sets'
13+
import {
14+
acceptCredentialSetInvitationContract,
15+
getCredentialSetInvitationContract,
16+
} from '@/lib/api/contracts/credential-sets'
17+
import { parseRequest } from '@/lib/api/server'
1418
import { getSession } from '@/lib/auth'
1519
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
1620
import { normalizeEmail } from '@/lib/invitations/core'
@@ -19,8 +23,10 @@ import { syncAllWebhooksForCredentialSet } from '@/lib/webhooks/utils.server'
1923
const logger = createLogger('CredentialSetInviteToken')
2024

2125
export const GET = withRouteHandler(
22-
async (req: NextRequest, { params }: { params: Promise<{ token: string }> }) => {
23-
const { token } = credentialSetInviteTokenParamsSchema.parse(await params)
26+
async (req: NextRequest, context: { params: Promise<{ token: string }> }) => {
27+
const parsed = await parseRequest(getCredentialSetInvitationContract, req, context)
28+
if (!parsed.success) return parsed.response
29+
const { token } = parsed.data.params
2430

2531
const [invitation] = await db
2632
.select({
@@ -69,8 +75,10 @@ export const GET = withRouteHandler(
6975
)
7076

7177
export const POST = withRouteHandler(
72-
async (req: NextRequest, { params }: { params: Promise<{ token: string }> }) => {
73-
const { token } = credentialSetInviteTokenParamsSchema.parse(await params)
78+
async (req: NextRequest, context: { params: Promise<{ token: string }> }) => {
79+
const parsed = await parseRequest(acceptCredentialSetInvitationContract, req, context)
80+
if (!parsed.success) return parsed.response
81+
const { token } = parsed.data.params
7482

7583
const session = await getSession()
7684
if (!session?.user?.id) {

apps/sim/app/api/invitations/[id]/accept/route.ts

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,32 @@
11
import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit'
22
import { createLogger } from '@sim/logger'
33
import { type NextRequest, NextResponse } from 'next/server'
4-
import {
5-
invitationActionBodySchema,
6-
invitationActionParamsSchema,
7-
} from '@/lib/api/contracts/invitations'
8-
import { getValidationErrorMessage } from '@/lib/api/server'
4+
import { acceptInvitationContract } from '@/lib/api/contracts/invitations'
5+
import { parseRequest } from '@/lib/api/server'
96
import { getSession } from '@/lib/auth'
107
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
118
import { acceptInvitation } from '@/lib/invitations/core'
129

1310
const logger = createLogger('InvitationAcceptAPI')
1411

1512
export const POST = withRouteHandler(
16-
async (request: NextRequest, { params }: { params: Promise<{ id: string }> }) => {
17-
const parsedParams = invitationActionParamsSchema.safeParse(await params)
18-
if (!parsedParams.success) {
19-
return NextResponse.json(
20-
{ error: getValidationErrorMessage(parsedParams.error) },
21-
{ status: 400 }
22-
)
23-
}
24-
const { id } = parsedParams.data
13+
async (request: NextRequest, context: { params: Promise<{ id: string }> }) => {
2514
const session = await getSession()
2615

2716
if (!session?.user?.id || !session.user.email) {
2817
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
2918
}
3019

31-
const body = await request.json().catch(() => ({}))
32-
const parsed = invitationActionBodySchema.safeParse(body)
33-
if (!parsed.success) {
34-
return NextResponse.json(
35-
{ error: getValidationErrorMessage(parsed.error, 'Invalid request body') },
36-
{ status: 400 }
37-
)
38-
}
20+
const parsed = await parseRequest(acceptInvitationContract, request, context)
21+
if (!parsed.success) return parsed.response
22+
23+
const { id } = parsed.data.params
3924

4025
const result = await acceptInvitation({
4126
userId: session.user.id,
4227
userEmail: session.user.email,
4328
invitationId: id,
44-
token: parsed.data.token ?? null,
29+
token: parsed.data.body.token ?? null,
4530
})
4631

4732
if (!result.success) {

apps/sim/app/api/invitations/[id]/reject/route.ts

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,32 @@
11
import { AuditAction, AuditResourceType, recordAudit } from '@sim/audit'
22
import { createLogger } from '@sim/logger'
33
import { type NextRequest, NextResponse } from 'next/server'
4-
import {
5-
invitationActionBodySchema,
6-
invitationActionParamsSchema,
7-
} from '@/lib/api/contracts/invitations'
8-
import { getValidationErrorMessage } from '@/lib/api/server'
4+
import { rejectInvitationContract } from '@/lib/api/contracts/invitations'
5+
import { parseRequest } from '@/lib/api/server'
96
import { getSession } from '@/lib/auth'
107
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
118
import { rejectInvitation } from '@/lib/invitations/core'
129

1310
const logger = createLogger('InvitationRejectAPI')
1411

1512
export const POST = withRouteHandler(
16-
async (request: NextRequest, { params }: { params: Promise<{ id: string }> }) => {
17-
const parsedParams = invitationActionParamsSchema.safeParse(await params)
18-
if (!parsedParams.success) {
19-
return NextResponse.json(
20-
{ error: getValidationErrorMessage(parsedParams.error) },
21-
{ status: 400 }
22-
)
23-
}
24-
const { id } = parsedParams.data
13+
async (request: NextRequest, context: { params: Promise<{ id: string }> }) => {
2514
const session = await getSession()
2615

2716
if (!session?.user?.id || !session.user.email) {
2817
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
2918
}
3019

31-
const body = await request.json().catch(() => ({}))
32-
const parsed = invitationActionBodySchema.safeParse(body)
33-
if (!parsed.success) {
34-
return NextResponse.json(
35-
{ error: getValidationErrorMessage(parsed.error, 'Invalid request body') },
36-
{ status: 400 }
37-
)
38-
}
20+
const parsed = await parseRequest(rejectInvitationContract, request, context)
21+
if (!parsed.success) return parsed.response
22+
23+
const { id } = parsed.data.params
3924

4025
const result = await rejectInvitation({
4126
userId: session.user.id,
4227
userEmail: session.user.email,
4328
invitationId: id,
44-
token: parsed.data.token ?? null,
29+
token: parsed.data.body.token ?? null,
4530
})
4631

4732
if (!result.success) {

0 commit comments

Comments
 (0)