Fix server-fn deprecation warnings and add CSRF middleware (#357)

* Replace deprecated createServerFn().inputValidator() with .validator()

* Add CSRF middleware for server functions

* Fix prettier formatting after validator rename

* Pass Zod schemas directly to .validator()

Drops the (data: unknown) => schema.parse(data) lambdas in favor of
standard-schema support. This also makes server-fn callers type-checked
against the schema input, which surfaced a dead hideSpam property in
SearchTabStrip (the schema never included it and the server hard-codes
hideSpam for web requests).

* Name the inline project-scoped schema in projects.ts
This commit is contained in:
Ben Senescu 2026-07-05 20:18:12 -04:00 committed by GitHub
parent 2645750671
commit abc80e1e2b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 74 additions and 80 deletions

View File

@ -179,7 +179,6 @@ function getSearchTabQueryConfig(
projectId,
target: input.target,
scope: input.scope,
hideSpam: false,
},
}),
};

View File

@ -32,7 +32,7 @@ const ahrefsResponseSchema = z.object({
export const getAhrefsDomainRatings = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => domainRatingsInputSchema.parse(data))
.validator(domainRatingsInputSchema)
.handler(async ({ data }) => {
const result: Record<string, number | null> = {};

View File

@ -26,7 +26,7 @@ async function assertPaidPlan(organizationId: string) {
export const lookupBrand = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => brandLookupInputSchema.parse(data))
.validator(brandLookupInputSchema)
.handler(async ({ data, context }) => {
await assertPaidPlan(context.organizationId);
return getBrandLookup({ ...data, projectId: context.projectId }, context);
@ -34,7 +34,7 @@ export const lookupBrand = createServerFn({ method: "POST" })
export const explorePrompt = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => promptExplorerInputSchema.parse(data))
.validator(promptExplorerInputSchema)
.handler(async ({ data, context }) => {
await assertPaidPlan(context.organizationId);
return runExplorePrompt({ ...data, projectId: context.projectId }, context);

View File

@ -17,7 +17,7 @@ type AiSearchAccessStatus = {
export const getAiSearchAccessSetupStatus = createServerFn({ method: "GET" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => aiSearchProjectSchema.parse(data))
.validator(aiSearchProjectSchema)
.handler(async (): Promise<AiSearchAccessStatus> => {
if (await isHostedServerAuthMode()) {
return { enabled: true, errorMessage: null };

View File

@ -21,7 +21,7 @@ import {
export const startAudit = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => startAuditSchema.parse(data))
.validator(startAuditSchema)
.handler(async ({ data, context }) => {
// The crawler runs on our Workers compute and isn't credit-metered, so
// plan-tier limits are the abuse bound in hosted mode: free accounts get
@ -68,35 +68,35 @@ export const startAudit = createServerFn({ method: "POST" })
export const getAuditStatus = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => getAuditStatusSchema.parse(data))
.validator(getAuditStatusSchema)
.handler(async ({ data, context }) => {
return AuditService.getStatus(data.auditId, context.projectId);
});
export const getAuditResults = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => getAuditResultsSchema.parse(data))
.validator(getAuditResultsSchema)
.handler(async ({ data, context }) => {
return AuditService.getResults(data.auditId, context.projectId);
});
export const getAuditHistory = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => getAuditHistorySchema.parse(data))
.validator(getAuditHistorySchema)
.handler(async ({ context }) => {
return AuditService.getHistory(context.projectId);
});
export const getCrawlProgress = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => getCrawlProgressSchema.parse(data))
.validator(getCrawlProgressSchema)
.handler(async ({ data, context }) => {
return AuditService.getCrawlProgress(data.auditId, context.projectId);
});
export const deleteAudit = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => deleteAuditSchema.parse(data))
.validator(deleteAuditSchema)
.handler(async ({ data, context }) => {
await AuditService.remove(data.auditId, context.projectId);
return { success: true };

View File

@ -16,7 +16,7 @@ export const getBacklinksOverview = createServerFn({
method: "POST",
})
.middleware(requireProjectContext)
.inputValidator((data: unknown) => backlinksOverviewInputSchema.parse(data))
.validator(backlinksOverviewInputSchema)
.handler(async ({ data, context }) => {
const profile = await BacklinksService.profileOverview(
{
@ -32,7 +32,7 @@ export const getBacklinksRows = createServerFn({
method: "POST",
})
.middleware(requireProjectContext)
.inputValidator((data: unknown) => backlinksRowsPageRequestSchema.parse(data))
.validator(backlinksRowsPageRequestSchema)
.handler(({ data, context }) =>
BacklinksService.profileBacklinksPage(data, context, WEB_SPAM_OPTIONS),
);
@ -41,9 +41,7 @@ export const getBacklinksReferringDomains = createServerFn({
method: "POST",
})
.middleware(requireProjectContext)
.inputValidator((data: unknown) =>
referringDomainsPageRequestSchema.parse(data),
)
.validator(referringDomainsPageRequestSchema)
.handler(({ data, context }) =>
BacklinksService.profileReferringDomainsPage(
data,
@ -56,7 +54,7 @@ export const getBacklinksTopPages = createServerFn({
method: "POST",
})
.middleware(requireProjectContext)
.inputValidator((data: unknown) => topPagesPageRequestSchema.parse(data))
.validator(topPagesPageRequestSchema)
.handler(({ data, context }) =>
BacklinksService.profileTopPagesPage(data, context),
);

View File

@ -17,7 +17,7 @@ type BacklinksAccessStatus = {
export const getBacklinksAccessSetupStatus = createServerFn({ method: "GET" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => backlinksProjectSchema.parse(data))
.validator(backlinksProjectSchema)
.handler(async (): Promise<BacklinksAccessStatus> => {
if (await isHostedServerAuthMode()) {
return { enabled: true, errorMessage: null };

View File

@ -50,7 +50,7 @@ export type BillingUsageEvent = {
export const getBillingUsageEvents = createServerFn({ method: "POST" })
.middleware(requireAuthenticatedContext)
.inputValidator((data: unknown) => billingUsageRangeSchema.parse(data))
.validator(billingUsageRangeSchema)
.handler(async ({ data, context }) => {
if (!(await isHostedServerAuthMode())) {
return [];

View File

@ -18,7 +18,7 @@ async function getDomainE2eFixtures() {
export const getDomainOverview = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => domainOverviewSchema.parse(data))
.validator(domainOverviewSchema)
.handler(async ({ data, context }) => {
if (shouldUseDomainE2eFixtures()) {
const fixtures = await getDomainE2eFixtures();
@ -36,7 +36,7 @@ export const getDomainOverview = createServerFn({ method: "POST" })
export const getDomainKeywordSuggestions = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => domainKeywordSuggestionsSchema.parse(data))
.validator(domainKeywordSuggestionsSchema)
.handler(async ({ data, context }) =>
DomainService.getSuggestedKeywords(
{
@ -50,9 +50,7 @@ export const getDomainKeywordSuggestions = createServerFn({ method: "POST" })
export const getDomainKeywordsPage = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) =>
domainKeywordsPageRequestSchema.parse(data),
)
.validator(domainKeywordsPageRequestSchema)
.handler(async ({ data, context }) => {
if (shouldUseDomainE2eFixtures()) {
const fixtures = await getDomainE2eFixtures();
@ -70,7 +68,7 @@ export const getDomainKeywordsPage = createServerFn({ method: "POST" })
export const getDomainPagesPage = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => domainPagesPageRequestSchema.parse(data))
.validator(domainPagesPageRequestSchema)
.handler(async ({ data, context }) => {
if (shouldUseDomainE2eFixtures()) {
const fixtures = await getDomainE2eFixtures();

View File

@ -32,7 +32,7 @@ export const getGscGrantStatus = createServerFn({ method: "GET" })
export const getGscConnection = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => projectScopedSchema.parse(data))
.validator(projectScopedSchema)
.handler(async ({ context }) => {
const [connection, currentUserHasGrant, hosted, gscConfigured] =
await Promise.all([
@ -53,7 +53,7 @@ export const getGscConnection = createServerFn({ method: "POST" })
export const listGscSites = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => projectScopedSchema.parse(data))
.validator(projectScopedSchema)
.handler(async ({ context }) => {
const [siteList, connection] = await Promise.all([
GscService.listSitesForUserWithGrantStatus(context.userId),
@ -72,7 +72,7 @@ export const listGscSites = createServerFn({ method: "POST" })
export const setGscSite = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => setSiteSchema.parse(data))
.validator(setSiteSchema)
.handler(async ({ data, context }) => {
const connection = await GscService.setSite({
projectId: context.projectId,
@ -94,7 +94,7 @@ export const setGscSite = createServerFn({ method: "POST" })
export const disconnectGsc = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => projectScopedSchema.parse(data))
.validator(projectScopedSchema)
.handler(async ({ context }) => {
await GscService.disconnect({
projectId: context.projectId,
@ -113,7 +113,7 @@ export const disconnectGsc = createServerFn({ method: "POST" })
export const startSelfHostedGscLink = createServerFn({ method: "POST" })
.middleware(requireAuthenticatedContext)
.inputValidator((data: unknown) => startSelfHostedLinkSchema.parse(data))
.validator(startSelfHostedLinkSchema)
.handler(async ({ data, context }) => {
const publicOrigin = getPublicOrigin(getRequest());
const url = await createSelfHostedGscAuthorizationUrl({

View File

@ -24,7 +24,7 @@ async function getKeywordE2eFixtures() {
export const researchKeywords = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => researchKeywordsSchema.parse(data))
.validator(researchKeywordsSchema)
.handler(async ({ data, context }) => {
if (shouldUseKeywordE2eFixtures()) {
const fixtures = await getKeywordE2eFixtures();
@ -42,7 +42,7 @@ export const researchKeywords = createServerFn({ method: "POST" })
export const saveKeywords = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => saveKeywordsSchema.parse(data))
.validator(saveKeywordsSchema)
.handler(async ({ data, context }) => {
return KeywordResearchService.saveKeywords({
...data,
@ -52,7 +52,7 @@ export const saveKeywords = createServerFn({ method: "POST" })
export const getSavedKeywords = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => getSavedKeywordsSchema.parse(data))
.validator(getSavedKeywordsSchema)
.handler(async ({ data, context }) => {
return KeywordResearchService.getSavedKeywords({
...data,
@ -62,7 +62,7 @@ export const getSavedKeywords = createServerFn({ method: "POST" })
export const exportSavedKeywords = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => exportSavedKeywordsSchema.parse(data))
.validator(exportSavedKeywordsSchema)
.handler(async ({ data, context }) => {
return KeywordResearchService.exportSavedKeywords({
...data,
@ -72,7 +72,7 @@ export const exportSavedKeywords = createServerFn({ method: "POST" })
export const updateSavedKeywordTags = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => updateSavedKeywordTagsSchema.parse(data))
.validator(updateSavedKeywordTagsSchema)
.handler(async ({ data, context }) => {
return KeywordResearchService.updateSavedKeywordTags({
...data,
@ -82,7 +82,7 @@ export const updateSavedKeywordTags = createServerFn({ method: "POST" })
export const updateSavedKeywordTag = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => updateSavedKeywordTagSchema.parse(data))
.validator(updateSavedKeywordTagSchema)
.handler(async ({ data, context }) => {
return KeywordResearchService.updateSavedKeywordTag({
...data,
@ -92,7 +92,7 @@ export const updateSavedKeywordTag = createServerFn({ method: "POST" })
export const deleteSavedKeywordTag = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => deleteSavedKeywordTagSchema.parse(data))
.validator(deleteSavedKeywordTagSchema)
.handler(async ({ data, context }) => {
return KeywordResearchService.deleteSavedKeywordTag({
...data,
@ -104,16 +104,14 @@ export const removeSavedKeywords = createServerFn({
method: "POST",
})
.middleware(requireProjectContext)
.inputValidator((data: unknown) => removeSavedKeywordsSchema.parse(data))
.validator(removeSavedKeywordsSchema)
.handler(async ({ data, context }) => {
return KeywordResearchService.removeSavedKeywords(context.projectId, data);
});
export const refreshSavedKeywordMetrics = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) =>
refreshSavedKeywordMetricsSchema.parse(data),
)
.validator(refreshSavedKeywordMetricsSchema)
.handler(async ({ context }) => {
return KeywordResearchService.refreshSavedKeywordMetrics(
{ projectId: context.projectId },
@ -123,7 +121,7 @@ export const refreshSavedKeywordMetrics = createServerFn({ method: "POST" })
export const getSerpAnalysis = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => serpAnalysisSchema.parse(data))
.validator(serpAnalysisSchema)
.handler(async ({ data, context }) =>
KeywordResearchService.getSerpAnalysis(
{

View File

@ -45,7 +45,7 @@ async function getAuditLighthouseData(input: {
export const getAuditLighthouseIssues = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => lighthouseAuditIssueSchema.parse(data))
.validator(lighthouseAuditIssueSchema)
.handler(async ({ data, context }) => {
const lighthouse = await getAuditLighthouseData({
projectId: context.projectId,
@ -68,7 +68,7 @@ export const getAuditLighthouseIssues = createServerFn({ method: "POST" })
export const exportAuditLighthouseIssues = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => lighthouseAuditExportSchema.parse(data))
.validator(lighthouseAuditExportSchema)
.handler(async ({ data, context }) => {
const lighthouse = await getAuditLighthouseData({
projectId: context.projectId,

View File

@ -66,7 +66,7 @@ export const getOnboardingAnswers = createServerFn({ method: "GET" })
export const saveOnboardingAnswers = createServerFn({ method: "POST" })
.middleware(requireAuthenticatedContext)
.inputValidator((data: unknown) => onboardingAnswersSchema.parse(data))
.validator(onboardingAnswersSchema)
.handler(async ({ data, context }) => {
const now = new Date().toISOString();
const completedAt = data.completed ? now : undefined;

View File

@ -39,7 +39,7 @@ const saveSiteSchema = z.object({
// Persists the site + default location for the onboarding project.
export const saveOnboardingSite = createServerFn({ method: "POST" })
.middleware(requireAuthenticatedContext)
.inputValidator((data: unknown) => saveSiteSchema.parse(data))
.validator(saveSiteSchema)
.handler(async ({ data, context }) => {
const project = await ProjectRepository.getProjectForOrganization(
data.projectId,

View File

@ -12,6 +12,8 @@ import {
} from "@/types/schemas/projects";
import { z } from "zod";
const projectScopedSchema = z.object({ projectId: z.string().min(1) });
export const getProjects = createServerFn({ method: "POST" })
.middleware(requireAuthenticatedContext)
.handler(async ({ context }) =>
@ -20,21 +22,21 @@ export const getProjects = createServerFn({ method: "POST" })
export const createProject = createServerFn({ method: "POST" })
.middleware(requireAuthenticatedContext)
.inputValidator((data: unknown) => createProjectSchema.parse(data))
.validator(createProjectSchema)
.handler(async ({ data, context }) =>
ProjectService.createProject(context.organizationId, data),
);
export const updateProject = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => updateProjectSchema.parse(data))
.validator(updateProjectSchema)
.handler(async ({ data, context }) =>
ProjectService.updateProject(context.organizationId, data),
);
export const archiveProject = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => archiveProjectSchema.parse(data))
.validator(archiveProjectSchema)
.handler(async ({ data, context }) =>
ProjectService.archiveProject(context.organizationId, data),
);
@ -47,16 +49,14 @@ export const getArchivedProjects = createServerFn({ method: "POST" })
export const restoreProject = createServerFn({ method: "POST" })
.middleware(requireAuthenticatedContext)
.inputValidator((data: unknown) => restoreProjectSchema.parse(data))
.validator(restoreProjectSchema)
.handler(async ({ data, context }) =>
ProjectService.restoreProject(context.organizationId, data),
);
export const getProjectAccess = createServerFn({ method: "POST" })
.middleware(requireAuthenticatedContext)
.inputValidator((data: unknown) =>
z.object({ projectId: z.string().min(1) }).parse(data),
)
.validator(projectScopedSchema)
.handler(async ({ data, context }) => {
return ProjectService.getProjectForOrganization(
context.organizationId,

View File

@ -59,21 +59,21 @@ async function requireConfig(configId: string, projectId: string) {
export const getRankTrackingConfigs = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => getConfigsSchema.parse(data))
.validator(getConfigsSchema)
.handler(async ({ context }) => {
return RankTrackingRepository.getConfigsForProject(context.projectId);
});
export const getRankTrackingConfigSummaries = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => getConfigsSchema.parse(data))
.validator(getConfigsSchema)
.handler(async ({ context }) => {
return RankTrackingRepository.getConfigSummaries(context.projectId);
});
export const createRankTrackingConfig = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => createConfigSchema.parse(data))
.validator(createConfigSchema)
.handler(async ({ data, context }) => {
const result = await RankTrackingService.createConfig({
projectId: context.projectId,
@ -104,7 +104,7 @@ export const createRankTrackingConfig = createServerFn({ method: "POST" })
export const updateRankTrackingConfig = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => updateConfigSchema.parse(data))
.validator(updateConfigSchema)
.handler(async ({ data, context }) => {
await RankTrackingService.updateConfig(data.configId, context.projectId, {
domain: data.domain,
@ -120,7 +120,7 @@ export const updateRankTrackingConfig = createServerFn({ method: "POST" })
export const triggerRankCheck = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => triggerCheckSchema.parse(data))
.validator(triggerCheckSchema)
.handler(async ({ data, context }) => {
const isHosted = await isHostedServerAuthMode();
if (isHosted && !(await customerHasPaidPlan(context.organizationId))) {
@ -157,7 +157,7 @@ export const triggerRankCheck = createServerFn({ method: "POST" })
export const getLatestRankResults = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => getLatestResultsSchema.parse(data))
.validator(getLatestResultsSchema)
.handler(async ({ data, context }) => {
return getLatestResults(
data.configId,
@ -168,21 +168,21 @@ export const getLatestRankResults = createServerFn({ method: "POST" })
export const getLatestRankRun = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => getLatestRunSchema.parse(data))
.validator(getLatestRunSchema)
.handler(async ({ data, context }) => {
return RankTrackingService.getLatestRun(data.configId, context.projectId);
});
export const estimateRankCheckCost = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => estimateCostSchema.parse(data))
.validator(estimateCostSchema)
.handler(async ({ data, context }) => {
return RankTrackingService.estimateCost(data.configId, context.projectId);
});
export const addTrackingKeywords = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => addKeywordsSchema.parse(data))
.validator(addKeywordsSchema)
.handler(async ({ data, context }) => {
const result = await RankTrackingService.addKeywords(
data.configId,
@ -252,7 +252,7 @@ export const addTrackingKeywords = createServerFn({ method: "POST" })
export const removeTrackingKeywords = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => removeKeywordsSchema.parse(data))
.validator(removeKeywordsSchema)
.handler(async ({ data, context }) => {
await RankTrackingService.removeKeywords(
data.configId,
@ -264,7 +264,7 @@ export const removeTrackingKeywords = createServerFn({ method: "POST" })
export const refreshTrackingKeywordMetrics = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => refreshMetricsSchema.parse(data))
.validator(refreshMetricsSchema)
.handler(async ({ data, context }) => {
const result = await RankTrackingService.refreshKeywordMetrics(
data.configId,
@ -290,7 +290,7 @@ export const refreshTrackingKeywordMetrics = createServerFn({ method: "POST" })
export const getRankKeywordHistory = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => getKeywordHistorySchema.parse(data))
.validator(getKeywordHistorySchema)
.handler(async ({ data, context }): Promise<RankKeywordHistoryPoint[]> => {
await requireConfig(data.configId, context.projectId);
return RankTrackingRepository.getKeywordHistory(
@ -302,7 +302,7 @@ export const getRankKeywordHistory = createServerFn({ method: "POST" })
export const getRankConfigTrend = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => getConfigTrendSchema.parse(data))
.validator(getConfigTrendSchema)
.handler(async ({ data, context }): Promise<RankConfigTrendPoint[]> => {
await requireConfig(data.configId, context.projectId);
const rows = await RankTrackingRepository.getConfigTrend(
@ -330,7 +330,7 @@ export const getRankConfigTrend = createServerFn({ method: "POST" })
export const getRankPositionMatrix = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => getPositionMatrixSchema.parse(data))
.validator(getPositionMatrixSchema)
.handler(async ({ data, context }): Promise<RankPositionMatrixCell[]> => {
await requireConfig(data.configId, context.projectId);
return RankTrackingRepository.getPositionMatrix(

View File

@ -11,7 +11,7 @@ const conversionInputSchema = z.object({
export const captureRedditConversionEvent = createServerFn({ method: "POST" })
.middleware(requireAuthenticatedContext)
.inputValidator((data: unknown) => conversionInputSchema.parse(data))
.validator(conversionInputSchema)
.handler(async ({ data, context }) => {
const status = await captureRedditConversion({
attribution: data.attribution,

View File

@ -15,7 +15,7 @@ const projectScopedSchema = z.object({ projectId: z.string().min(1) });
// Lists the SAM chat sessions for a project (newest first) for the side-panel.
export const listSamSessions = createServerFn({ method: "GET" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => projectScopedSchema.parse(data))
.validator(projectScopedSchema)
.handler(async ({ context }) => {
return SamSessionRepository.listSessionsForProject(
context.projectId,
@ -27,7 +27,7 @@ export const listSamSessions = createServerFn({ method: "GET" })
// connection keyed by that id.
export const createSamSession = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => projectScopedSchema.parse(data))
.validator(projectScopedSchema)
.handler(async ({ context }) => {
const session = await SamSessionRepository.createSession({
projectId: context.projectId,
@ -46,7 +46,7 @@ const archiveSchema = z.object({ sessionId: z.string().min(1) });
// unarchive can restore it. There is no unarchive UI yet.
export const archiveSamSession = createServerFn({ method: "POST" })
.middleware(requireAuthenticatedContext)
.inputValidator((data: unknown) => archiveSchema.parse(data))
.validator(archiveSchema)
.handler(async ({ data, context }) => {
// Authorize against the session's project (the canonical project-access
// path), not the caller's org directly.

View File

@ -21,7 +21,7 @@ type SamAccessStatus = {
// deployments always have the key provisioned, so only self-hosted is checked.
export const getSamAccessSetupStatus = createServerFn({ method: "GET" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => projectScopedSchema.parse(data))
.validator(projectScopedSchema)
.handler(async (): Promise<SamAccessStatus> => {
if (await isHostedServerAuthMode()) {
return { enabled: true, errorMessage: null };

View File

@ -64,7 +64,7 @@ function isExpectedConnectionFailure(error: unknown): boolean {
*/
export const getSearchPerformanceReport = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) => searchPerformanceInputSchema.parse(data))
.validator(searchPerformanceInputSchema)
.handler(async ({ data, context }) => {
const { startDate, endDate } = resolveDateRange({
dateRange: data.dateRange,
@ -137,9 +137,7 @@ export const getSearchPerformanceReport = createServerFn({ method: "POST" })
*/
export const getSearchPerformanceTable = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) =>
searchPerformanceTableInputSchema.parse(data),
)
.validator(searchPerformanceTableInputSchema)
.handler(async ({ data, context }) => {
const { startDate, endDate } = resolveDateRange({
dateRange: data.dateRange,
@ -185,9 +183,7 @@ export const getSearchPerformanceTable = createServerFn({ method: "POST" })
*/
export const exportSearchPerformanceTable = createServerFn({ method: "POST" })
.middleware(requireProjectContext)
.inputValidator((data: unknown) =>
searchPerformanceTableExportInputSchema.parse(data),
)
.validator(searchPerformanceTableExportInputSchema)
.handler(async ({ data, context }) => {
const { startDate, endDate } = resolveDateRange({
dateRange: data.dateRange,

View File

@ -1,6 +1,11 @@
import { createStart } from "@tanstack/react-start";
import { createCsrfMiddleware, createStart } from "@tanstack/react-start";
import { globalServerFunctionMiddleware } from "@/serverFunctions/middleware";
const csrfMiddleware = createCsrfMiddleware({
filter: (ctx) => ctx.handlerType === "serverFn",
});
export const startInstance = createStart(() => ({
requestMiddleware: [csrfMiddleware],
functionMiddleware: globalServerFunctionMiddleware,
}));