import { z } from "zod/v4"; import { env } from "@homarr/common/env"; import { fetchWithTrustedCertificatesAsync } from "@homarr/core/infrastructure/http"; import { withTimeoutAsync } from "@homarr/core/infrastructure/http/timeout"; import { createTRPCRouter, publicProcedure } from "../trpc"; const citySchema = z.object({ id: z.number(), name: z.string(), country: z.string().optional(), country_code: z.string().optional(), latitude: z.number(), longitude: z.number(), population: z.number().optional(), }); export const locationSearchCityInput = z.object({ query: z.string(), }); export const locationSearchCityOutput = z .object({ results: z.array(citySchema), }) .or( z .object({ generationtime_ms: z.number(), }) .refine((data) => Object.keys(data).length === 1, { message: "Invalid response" }) .transform(() => ({ results: [] })), // We fallback to empty array if no results ); export const locationRouter = createTRPCRouter({ searchCity: publicProcedure .input(locationSearchCityInput) .output(locationSearchCityOutput) .query(async ({ input }) => { if (env.NO_EXTERNAL_CONNECTION) return { results: [] }; const url = new URL("https://geocoding-api.open-meteo.com/v1/search"); url.searchParams.set("name", input.query); const res = await withTimeoutAsync(async (signal) => { return await fetchWithTrustedCertificatesAsync(url.toString(), { signal }); }); return (await res.json()) as z.infer; }), });