42 lines
1.7 KiB
TypeScript
42 lines
1.7 KiB
TypeScript
import { createServerFn } from "@tanstack/react-start";
|
|
import { getBrandLookup } from "@/server/features/ai-search/services/brandLookup";
|
|
import { explorePrompt as runExplorePrompt } from "@/server/features/ai-search/services/promptExplorer";
|
|
import { customerHasPaidPlan } from "@/server/billing/subscription";
|
|
import { AppError } from "@/server/lib/errors";
|
|
import { isHostedServerAuthMode } from "@/server/lib/runtime-env";
|
|
import { requireProjectContext } from "@/serverFunctions/middleware";
|
|
import {
|
|
brandLookupInputSchema,
|
|
promptExplorerInputSchema,
|
|
} from "@/types/schemas/ai-search";
|
|
|
|
/**
|
|
* AI Visibility endpoints are gated behind the paid plan in hosted mode
|
|
* because each call fans out to several paid DataForSEO requests. Self-hosted
|
|
* deployments pay DataForSEO directly and aren't gated.
|
|
*/
|
|
async function assertPaidPlan(organizationId: string) {
|
|
if (!(await isHostedServerAuthMode())) return;
|
|
if (await customerHasPaidPlan(organizationId)) return;
|
|
throw new AppError(
|
|
"PAYMENT_REQUIRED",
|
|
"Upgrade to the paid plan to use AI Visibility",
|
|
);
|
|
}
|
|
|
|
export const lookupBrand = createServerFn({ method: "POST" })
|
|
.middleware(requireProjectContext)
|
|
.inputValidator((data: unknown) => brandLookupInputSchema.parse(data))
|
|
.handler(async ({ data, context }) => {
|
|
await assertPaidPlan(context.organizationId);
|
|
return getBrandLookup({ ...data, projectId: context.projectId }, context);
|
|
});
|
|
|
|
export const explorePrompt = createServerFn({ method: "POST" })
|
|
.middleware(requireProjectContext)
|
|
.inputValidator((data: unknown) => promptExplorerInputSchema.parse(data))
|
|
.handler(async ({ data, context }) => {
|
|
await assertPaidPlan(context.organizationId);
|
|
return runExplorePrompt({ ...data, projectId: context.projectId }, context);
|
|
});
|