import { createServerFn } from "@tanstack/react-start"; import { z } from "zod"; import { normalizePost, getByPath, type NormalizedPost, type SiteConfig } from "./sites"; const SiteSchema: z.ZodType = z.object({ id: z.string(), name: z.string(), baseUrl: z.string().url(), searchPath: z.string(), params: z.object({ tags: z.string(), page: z.string(), limit: z.string() }), postsPath: z.string(), fields: z.object({ id: z.string(), previewUrl: z.string(), fileUrl: z.string(), ext: z.string(), tags: z.string(), source: z.string().optional(), rating: z.string().optional(), score: z.string().optional(), sampleUrl: z.string().optional(), fileSize: z.string().optional(), width: z.string().optional(), height: z.string().optional(), md5: z.string().optional(), uploader: z.string().optional(), createdAt: z.string().optional(), }), headers: z.record(z.string(), z.string()).optional(), pageStartsAt: z.number().optional(), }); const InputSchema = z.object({ site: SiteSchema, tags: z.string().default(""), page: z.number().int().min(1).default(1), limit: z.number().int().min(1).max(200).default(40), login: z.string().optional(), apiKey: z.string().optional(), }); export type SearchResult = { posts: NormalizedPost[]; error?: string; }; export const searchPosts = createServerFn({ method: "POST" }) .inputValidator((input: unknown) => InputSchema.parse(input)) .handler(async ({ data }): Promise => { const { site, tags, page, limit, login, apiKey } = data; const pageParam = (site.pageStartsAt ?? 1) === 0 ? page - 1 : page; const url = new URL(site.searchPath, site.baseUrl); url.searchParams.set(site.params.tags, tags); url.searchParams.set(site.params.page, String(pageParam)); url.searchParams.set(site.params.limit, String(limit)); if (login) { url.searchParams.set("login", login); } if (apiKey) { url.searchParams.set("api_key", apiKey); } try { const res = await fetch(url.toString(), { headers: { Accept: "application/json", "User-Agent": "LovableGrabber/1.0", ...(site.headers ?? {}), }, }); if (!res.ok) { return { posts: [], error: `${site.name} returned ${res.status}` }; } const json = await res.json(); const arr = getByPath(json, site.postsPath); if (!Array.isArray(arr)) { return { posts: [], error: `Response at "${site.postsPath}" is not an array` }; } const posts = arr .map((raw) => normalizePost(raw, site.fields)) .filter((p): p is NormalizedPost => p !== null); return { posts }; } catch (err) { return { posts: [], error: err instanceof Error ? err.message : "Request failed" }; } });