37 lines
1.4 KiB
TypeScript
37 lines
1.4 KiB
TypeScript
import { createServerFn } from "@tanstack/react-start";
|
|
import { z } from "zod";
|
|
import { requireAuthenticatedContext } from "@/serverFunctions/middleware";
|
|
import { fetchSerpLocationsForCountry } from "@/server/lib/dataforseo/serp-locations";
|
|
|
|
/** ISO 3166-1 alpha-2, e.g. "us" — DataForSEO rejects country names. */
|
|
const countryCodeField = z.string().regex(/^[a-z]{2}$/i);
|
|
|
|
const searchSerpLocationsSchema = z.object({
|
|
query: z.string().min(1).max(100),
|
|
countryCode: countryCodeField,
|
|
});
|
|
|
|
export const searchSerpLocations = createServerFn({ method: "POST" })
|
|
.middleware(requireAuthenticatedContext)
|
|
.validator(searchSerpLocationsSchema)
|
|
.handler(async ({ data }) => {
|
|
const all = await fetchSerpLocationsForCountry(data.countryCode);
|
|
const needle = data.query.trim().toLowerCase();
|
|
return all
|
|
.filter((loc) => loc.displayLabel.toLowerCase().includes(needle))
|
|
.slice(0, 10);
|
|
});
|
|
|
|
/**
|
|
* Warm the per-country location cache so the first real search is fast.
|
|
* Fired when the user switches to Local targeting; the first search per
|
|
* country otherwise pays the full ~9.5MB DataForSEO fetch (~3s).
|
|
*/
|
|
export const prewarmSerpLocations = createServerFn({ method: "POST" })
|
|
.middleware(requireAuthenticatedContext)
|
|
.validator(z.object({ countryCode: countryCodeField }))
|
|
.handler(async ({ data }) => {
|
|
await fetchSerpLocationsForCountry(data.countryCode);
|
|
return { warmed: true };
|
|
});
|