diff --git a/docker-compose.yml b/docker-compose.yml index afc87f66..9dda7d8a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -64,9 +64,9 @@ services: UPLOAD_FOLDER: /app/uploads UPLOAD_USER: UPLOAD_PASSWORD: - PUBLIC_OVERPASS_API_URL: https://overpass-api.de - PUBLIC_VALHALLA_URL: https://valhalla1.openstreetmap.de - PUBLIC_NOMINATIM_URL: https://nominatim.openstreetmap.org + OVERPASS_API_URL: https://overpass-api.de + VALHALLA_URL: https://valhalla1.openstreetmap.de + NOMINATIM_URL: https://nominatim.openstreetmap.org volumes: - ./data/uploads:/app/uploads # - ./data/about.md:/app/build/client/md/about.md diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index c6c78ce6..5b821175 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -52,9 +52,9 @@ services: UPLOAD_FOLDER: /app/uploads UPLOAD_USER: UPLOAD_PASSWORD: - PUBLIC_OVERPASS_API_URL: https://overpass-api.de - PUBLIC_VALHALLA_URL: https://valhalla1.openstreetmap.de - PUBLIC_NOMINATIM_URL: https://nominatim.openstreetmap.org + OVERPASS_API_URL: https://overpass-api.de + VALHALLA_URL: https://valhalla1.openstreetmap.de + NOMINATIM_URL: https://nominatim.openstreetmap.org volumes: - uploads:/app/uploads # - ./data/about.md:/app/build/client/md/about.md diff --git a/docker/docker-compose.prod.yml b/docker/docker-compose.prod.yml index f89161e6..fc398ca4 100644 --- a/docker/docker-compose.prod.yml +++ b/docker/docker-compose.prod.yml @@ -52,9 +52,9 @@ services: UPLOAD_FOLDER: /app/uploads UPLOAD_USER: UPLOAD_PASSWORD: - PUBLIC_OVERPASS_API_URL: https://overpass-api.de - PUBLIC_VALHALLA_URL: https://valhalla1.openstreetmap.de - PUBLIC_NOMINATIM_URL: https://nominatim.openstreetmap.org + OVERPASS_API_URL: https://overpass-api.de + VALHALLA_URL: https://valhalla1.openstreetmap.de + NOMINATIM_URL: https://nominatim.openstreetmap.org volumes: - uploads:/app/uploads # - ./data/about.md:/app/build/client/md/about.md diff --git a/docs/src/content/docs/develop/local-development.md b/docs/src/content/docs/develop/local-development.md index 247fa442..9e09fc5a 100644 --- a/docs/src/content/docs/develop/local-development.md +++ b/docs/src/content/docs/develop/local-development.md @@ -12,7 +12,7 @@ export ORIGIN=http://localhost:5173 export MEILI_URL=http://127.0.0.1:7700 export MEILI_MASTER_KEY=p2gYZAWODOrwTPr4AYoahCZ9CI8y9bUd0yQLGk-E3m8 export PUBLIC_POCKETBASE_URL=http://127.0.0.1:8090 -export PUBLIC_VALHALLA_URL=https://valhalla1.openstreetmap.de +export VALHALLA_URL=https://valhalla1.openstreetmap.de export POCKETBASE_ENCRYPTION_KEY=9ada3c93163812101e50e2bf49e880bc cd search && ./meilisearch --master-key $MEILI_MASTER_KEY & diff --git a/docs/src/content/docs/run/environment-configuration.md b/docs/src/content/docs/run/environment-configuration.md index cd14daab..ee0e10b0 100644 --- a/docs/src/content/docs/run/environment-configuration.md +++ b/docs/src/content/docs/run/environment-configuration.md @@ -42,10 +42,38 @@ Since we use an unmodified installation of meilisearch you can use all variables | BODY_SIZE_LIMIT | Maximum allowed upload size | Infinity | | PUBLIC_POCKETBASE_URL | IP or hostname (including the port) of your pocketbase instance | http://db:8090 | | PUBLIC_DISABLE_SIGNUP | Disables signup option for new users | false | -| PUBLIC_VALHALLA_URL | Public IP or hostname (including the port) of a valhalla instance | https://valhalla1.openstreetmap.de | -| PUBLIC_NOMINATIM_URL | Public IP or hostname (including the port) of a nominatim instance | https://nominatim.openstreetmap.org | | PUBLIC_PRIVATE_INSTANCE | Setting this to true will block visitors from viewing content without an account | false | | UPLOAD_FOLDER | Folder from which wanderer auto-uploads trails | /app/uploads | | UPLOAD_USER | Username for the account with which wanderer auto-uploads trails | | | UPLOAD_PASSWORD | Password for the account with which wanderer auto-uploads trails | | -| PUBLIC_OVERPASS_API_URL | Overpass API URL used for map points of interest | https://overpass-api.de | + +## Geocoding & Routing + +These variables configure server-side requests to Valhalla, Nominatim and Overpass. + +| Environment Variable | Description | Default | +| ------------------------ | --------------------------------------------------------------------------- | ----------------------------------- | +| VALHALLA_URL | Valhalla API URL used for auto-routing and elevation data | https://valhalla1.openstreetmap.de | +| NOMINATIM_URL | Nominatim API URL used for (reverse) geocoding | https://nominatim.openstreetmap.org | +| OVERPASS_API_URL | Overpass API URL used for map points of interest | https://overpass-api.de | + +When `*_URL` is unset, the backend falls back to legacy `PUBLIC_*_URL`. + +## Custom CA certificates + +If your API endpoints use certificates signed by a private CA, add the CA bundle and set `NODE_EXTRA_CA_CERTS` for the `web` service. + +| Environment Variable | Description | Default | +| -------------------- | ------------------------------------------------------------------------ | ------- | +| NODE_EXTRA_CA_CERTS | Path to an additional CA bundle used by Node.js TLS connections | | + +Example (`docker-compose.yml`): + +```yaml +services: + web: + environment: + NODE_EXTRA_CA_CERTS: /etc/ssl/private-ca/ca.pem + volumes: + - ./certs/ca.pem:/etc/ssl/private-ca/ca.pem:ro +``` diff --git a/docs/src/content/docs/run/installation/docker.mdx b/docs/src/content/docs/run/installation/docker.mdx index 8212030e..24894099 100644 --- a/docs/src/content/docs/run/installation/docker.mdx +++ b/docs/src/content/docs/run/installation/docker.mdx @@ -111,11 +111,14 @@ services: UPLOAD_FOLDER: /app/uploads UPLOAD_USER: UPLOAD_PASSWORD: - PUBLIC_OVERPASS_API_URL: https://overpass-api.de - PUBLIC_VALHALLA_URL: https://valhalla1.openstreetmap.de - PUBLIC_NOMINATIM_URL: https://nominatim.openstreetmap.org + OVERPASS_API_URL: https://overpass-api.de + VALHALLA_URL: https://valhalla1.openstreetmap.de + NOMINATIM_URL: https://nominatim.openstreetmap.org + # Optional: trust private CAs for server-side API calls + # NODE_EXTRA_CA_CERTS: /etc/ssl/private-ca/ca.pem volumes: - ./data/uploads:/app/uploads + # - ./certs/ca.pem:/etc/ssl/private-ca/ca.pem:ro # - ./data/about.md:/app/build/client/md/about.md ports: - "3000:3000" diff --git a/docs/src/content/docs/run/installation/from-source.mdx b/docs/src/content/docs/run/installation/from-source.mdx index f9249347..1b6819cf 100644 --- a/docs/src/content/docs/run/installation/from-source.mdx +++ b/docs/src/content/docs/run/installation/from-source.mdx @@ -66,7 +66,7 @@ export ORIGIN=http://localhost:3000 export MEILI_URL=http://127.0.0.1:7700 export MEILI_MASTER_KEY=YOU_SHOULD_DEFINITELY_CHANGE_ME export PUBLIC_POCKETBASE_URL=http://127.0.0.1:8090 -export PUBLIC_VALHALLA_URL=https://valhalla1.openstreetmap.de +export VALHALLA_URL=https://valhalla1.openstreetmap.de export POCKETBASE_ENCRYPTION_KEY=YOUR_ENCRYPTION_KEY_HERE # Optional configuration @@ -76,6 +76,7 @@ export POCKETBASE_ENCRYPTION_KEY=YOUR_ENCRYPTION_KEY_HERE # export UPLOAD_FOLDER=/app/uploads # export UPLOAD_USER= # export UPLOAD_PASSWORD= +# export NODE_EXTRA_CA_CERTS=/absolute/path/to/ca.pem cd search && ./meilisearch --master-key $MEILI_MASTER_KEY & cd db && ./pocketbase serve & diff --git a/web/src/lib/server/http.ts b/web/src/lib/server/http.ts new file mode 100644 index 00000000..7a683253 --- /dev/null +++ b/web/src/lib/server/http.ts @@ -0,0 +1,18 @@ +import { json } from "@sveltejs/kit"; + +function safeJson(text: string): any { + try { + return JSON.parse(text); + } catch { + return { message: text }; + } +} + +export async function proxyJsonResponse(response: Response) { + const text = await response.text(); + const payload = text.length ? safeJson(text) : {}; + if (!response.ok) { + return json(payload, { status: response.status }); + } + return json(payload); +} diff --git a/web/src/lib/server/nominatim.ts b/web/src/lib/server/nominatim.ts new file mode 100644 index 00000000..7bc217d1 --- /dev/null +++ b/web/src/lib/server/nominatim.ts @@ -0,0 +1,63 @@ +import { version } from "$app/environment"; +import { resolveBaseUrl } from "$lib/server/url"; +import type { RequestEvent } from "@sveltejs/kit"; + +const NOMINATIM_RATE_LIMIT_MS = 1000; +const NOMINATIM_MAX_RETRIES = 2; +let lastNominatimCall = 0; + +function getNominatimBaseUrl(): string { + return resolveBaseUrl("NOMINATIM_URL", "https://nominatim.openstreetmap.org"); +} + +function needsRateLimiting(baseUrl: string): boolean { + return baseUrl.includes("nominatim.openstreetmap.org"); +} + +const waitTimer = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + +async function nominatimRateLimiter(baseUrl: string) { + if (!needsRateLimiting(baseUrl)) { + return; + } + + const elapsedTimeMs = Date.now() - lastNominatimCall; + const waitTime = NOMINATIM_RATE_LIMIT_MS - elapsedTimeMs; + if (waitTime > 0) { + await waitTimer(waitTime); + } + + lastNominatimCall = Date.now(); +} + +export async function fetchNominatim(event: RequestEvent, path: string, params: URLSearchParams): Promise { + const baseUrl = getNominatimBaseUrl(); + const base = new URL(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`); + const cleanPath = path.replace(/^\/+/, ""); + const url = new URL(cleanPath, base); + const query = params.toString(); + if (query.length) { + url.search = query; + } + + let attempt = 0; + + while (true) { + await nominatimRateLimiter(baseUrl); + + try { + return await event.fetch(url.toString(), { + method: "GET", + headers: { + "User-Agent": `wanderer/${version}`, + }, + }); + } catch (error) { + if (attempt < NOMINATIM_MAX_RETRIES) { + attempt++; + continue; + } + throw new Error(`Nominatim fetch failed for ${url.toString()}`, { cause: error }); + } + } +} diff --git a/web/src/lib/server/overpass.ts b/web/src/lib/server/overpass.ts new file mode 100644 index 00000000..0f8a96a7 --- /dev/null +++ b/web/src/lib/server/overpass.ts @@ -0,0 +1,34 @@ +import { resolveBaseUrl } from "$lib/server/url"; +import type { RequestEvent } from "@sveltejs/kit"; + +const OVERPASS_MAX_RETRIES = 2; + +function getOverpassBaseUrl(): string { + return resolveBaseUrl("OVERPASS_API_URL", "https://overpass-api.de"); +} + +export async function fetchOverpass(event: RequestEvent, params: URLSearchParams): Promise { + const baseUrl = getOverpassBaseUrl(); + const base = new URL(baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`); + const url = new URL("api/interpreter", base); + const query = params.toString(); + if (query.length) { + url.search = query; + } + + let attempt = 0; + + while (true) { + try { + return await event.fetch(url.toString(), { + method: "GET", + }); + } catch (error) { + if (attempt < OVERPASS_MAX_RETRIES) { + attempt++; + continue; + } + throw error; + } + } +} diff --git a/web/src/lib/server/url.ts b/web/src/lib/server/url.ts new file mode 100644 index 00000000..7f195439 --- /dev/null +++ b/web/src/lib/server/url.ts @@ -0,0 +1,24 @@ +import { env as privateEnv } from "$env/dynamic/private"; +import { env as publicEnv } from "$env/dynamic/public"; + +export type ExternalServiceUrlKey = "VALHALLA_URL" | "NOMINATIM_URL" | "OVERPASS_API_URL"; + +export function normalizeBaseUrl(url: string): string { + const trimmedUrl = url.trim(); + if (!trimmedUrl) { + return ""; + } + if (!/^https?:\/\//i.test(trimmedUrl)) { + return `https://${trimmedUrl}`; + } + return trimmedUrl; +} + +export function resolveBaseUrl( + key: ExternalServiceUrlKey, + fallback: string = "", +): string { + const publicKey = `PUBLIC_${key}` as `PUBLIC_${string}`; + const rawUrl = privateEnv[key] ?? publicEnv[publicKey] ?? fallback; + return normalizeBaseUrl(rawUrl); +} diff --git a/web/src/lib/server/valhalla.ts b/web/src/lib/server/valhalla.ts new file mode 100644 index 00000000..98ff4294 --- /dev/null +++ b/web/src/lib/server/valhalla.ts @@ -0,0 +1,5 @@ +import { resolveBaseUrl } from "$lib/server/url"; + +export function getValhallaBaseUrl(): string { + return resolveBaseUrl("VALHALLA_URL"); +} diff --git a/web/src/lib/stores/search_store.ts b/web/src/lib/stores/search_store.ts index 78ce7857..a25a1971 100644 --- a/web/src/lib/stores/search_store.ts +++ b/web/src/lib/stores/search_store.ts @@ -1,10 +1,8 @@ -import { env } from "$env/dynamic/public"; import type { Actor } from "$lib/models/activitypub/actor"; import { defaultTrailSearchAttributes, type TrailSearchResult } from "$lib/models/trail"; import { APIError } from "$lib/util/api_util"; import type { Hits, MultiSearchParams, MultiSearchResponse, MultiSearchResult, SearchParams, SearchResponse } from "meilisearch"; import type { ListResult } from "pocketbase"; -import { version } from "$app/environment"; export type LocationSearchResult = { name: string; @@ -104,14 +102,17 @@ export async function searchTrails(q: string, options: SearchParams): Promise> { - const nominatimURL = env.PUBLIC_NOMINATIM_URL ?? "https://nominatim.openstreetmap.org" - const r = await fetch(`${nominatimURL}/search?q=${q}&format=geojson&addressdetails=1${limit ? '&limit=' + limit : ''}`, { - method: "GET", - headers: new Headers({ - "User-Agent": "wanderer/" + version - }) - }); +export async function searchLocations(q: string, limit?: number, f: (url: RequestInfo | URL, config?: RequestInit) => Promise = fetch): Promise> { + if (!q.trim()) { + return []; + } + + const params = new URLSearchParams({ + q, + format: "geojson", + addressdetails: "1", + }); + const r = await fetchGeocoding("search", params, f); if (!r.ok) { const response = await r.json(); throw new APIError(r.status, response.message, response.detail) @@ -127,14 +128,20 @@ export async function searchLocations(q: string, limit?: number): Promise Promise = fetch): Promise { + const query = params.toString(); + const url = query.length ? `/api/v1/geocoding/${path}?${query}` : `/api/v1/geocoding/${path}`; + return await f(url); +} + +export async function searchLocationReverse(lat: number, lon: number, f: (url: RequestInfo | URL, config?: RequestInit) => Promise = fetch) { + const params = new URLSearchParams({ + lat: String(lat), + lon: String(lon), + format: "geojson", + addressdetails: "1", + }); + const r = await fetchGeocoding("reverse", params, f); if (!r.ok) { const response = await r.json(); throw new APIError(r.status, response.message, response.detail) @@ -170,14 +177,17 @@ function getLocationDescription(address: Address) { export async function searchMulti(options: MultiSearchParams): Promise[]> { - const locationQuery = options.queries.find(q => q.indexUid === "locations"); - const locationQueryIndex = locationQuery ? options.queries.indexOf(locationQuery) : -1 - if (locationQueryIndex >= 0) { - options.queries.splice(locationQueryIndex, 1) - } + const locationQueryIndex = options.queries.findIndex(q => q.indexUid === "locations"); + const locationQuery = locationQueryIndex >= 0 ? options.queries[locationQueryIndex] : undefined; + const queries = locationQueryIndex >= 0 + ? options.queries.filter((_, index) => index !== locationQueryIndex) + : options.queries; const r = await fetch("/api/v1/search/multi", { method: "POST", - body: JSON.stringify(options), + body: JSON.stringify({ + ...options, + queries, + }), }); if (!r.ok) { diff --git a/web/src/lib/vendor/maplibre-layer-manager/overpass-layer.ts b/web/src/lib/vendor/maplibre-layer-manager/overpass-layer.ts index 040490a5..d7e7e805 100644 --- a/web/src/lib/vendor/maplibre-layer-manager/overpass-layer.ts +++ b/web/src/lib/vendor/maplibre-layer-manager/overpass-layer.ts @@ -12,7 +12,7 @@ import type { OverpassResponse } from "./types"; import { env } from '$env/dynamic/public' export class OverpassLayer implements BaseLayer { - private overpassApiURL: string = (env.PUBLIC_OVERPASS_API_URL && env.PUBLIC_OVERPASS_API_URL.length > 0 ? env.PUBLIC_OVERPASS_API_URL : "https://overpass-api.de") + "/api/interpreter"; + private overpassApiURL: string = "/api/v1/overpass/interpreter"; data: GeoJSON.FeatureCollection = ({ type: 'FeatureCollection', features: [] }); diff --git a/web/src/routes/api/v1/geocoding/reverse/+server.ts b/web/src/routes/api/v1/geocoding/reverse/+server.ts new file mode 100644 index 00000000..0eb931bb --- /dev/null +++ b/web/src/routes/api/v1/geocoding/reverse/+server.ts @@ -0,0 +1,36 @@ +import { json, type RequestEvent } from "@sveltejs/kit"; +import { proxyJsonResponse } from "$lib/server/http"; +import { fetchNominatim } from "$lib/server/nominatim"; + +export async function GET(event: RequestEvent) { + const lat = event.url.searchParams.get("lat"); + const lon = event.url.searchParams.get("lon"); + if (!lat || !lon) { + return json({ message: "Missing query parameter: lat or lon" }, { status: 400 }); + } + + if (Number.isNaN(Number(lat)) || Number.isNaN(Number(lon))) { + return json({ message: "Invalid query parameter: lat or lon" }, { status: 400 }); + } + + const params = new URLSearchParams({ + lat, + lon, + format: "geojson", + addressdetails: "1", + }); + + try { + const response = await fetchNominatim(event, "/reverse", params); + return await proxyJsonResponse(response); + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + const detail = { + name: err.name, + message: err.message, + cause: err.cause instanceof Error ? err.cause.message : err.cause, + }; + console.error("Nominatim reverse request failed", detail); + return json({ message: "Nominatim request failed", detail }, { status: 502 }); + } +} diff --git a/web/src/routes/api/v1/geocoding/search/+server.ts b/web/src/routes/api/v1/geocoding/search/+server.ts new file mode 100644 index 00000000..4fb16886 --- /dev/null +++ b/web/src/routes/api/v1/geocoding/search/+server.ts @@ -0,0 +1,38 @@ +import { json, type RequestEvent } from "@sveltejs/kit"; +import { proxyJsonResponse } from "$lib/server/http"; +import { fetchNominatim } from "$lib/server/nominatim"; + +export async function GET(event: RequestEvent) { + const q = event.url.searchParams.get("q"); + if (!q) { + return json({ message: "Missing query parameter: q" }, { status: 400 }); + } + + const limit = event.url.searchParams.get("limit"); + if (limit !== null && Number.isNaN(Number(limit))) { + return json({ message: "Invalid query parameter: limit" }, { status: 400 }); + } + + const params = new URLSearchParams({ + q, + format: "geojson", + addressdetails: "1", + }); + if (limit) { + params.set("limit", limit); + } + + try { + const response = await fetchNominatim(event, "/search", params); + return await proxyJsonResponse(response); + } catch (error) { + const err = error instanceof Error ? error : new Error(String(error)); + const detail = { + name: err.name, + message: err.message, + cause: err.cause instanceof Error ? err.cause.message : err.cause, + }; + console.error("Nominatim search request failed", detail); + return json({ message: "Nominatim request failed", detail }, { status: 502 }); + } +} diff --git a/web/src/routes/api/v1/overpass/interpreter/+server.ts b/web/src/routes/api/v1/overpass/interpreter/+server.ts new file mode 100644 index 00000000..f10c5eef --- /dev/null +++ b/web/src/routes/api/v1/overpass/interpreter/+server.ts @@ -0,0 +1,21 @@ +import { json, type RequestEvent } from "@sveltejs/kit"; +import { proxyJsonResponse } from "$lib/server/http"; +import { fetchOverpass } from "$lib/server/overpass"; + +export async function GET(event: RequestEvent) { + const data = event.url.searchParams.get("data"); + if (!data) { + return json({ message: "Missing query parameter: data" }, { status: 400 }); + } + + const params = new URLSearchParams({ + data, + }); + + try { + const response = await fetchOverpass(event, params); + return await proxyJsonResponse(response); + } catch (error) { + return json({ message: "Overpass request failed" }, { status: 502 }); + } +} diff --git a/web/src/routes/api/v1/trail/upload/+server.ts b/web/src/routes/api/v1/trail/upload/+server.ts index 16bdff88..b0986cd0 100644 --- a/web/src/routes/api/v1/trail/upload/+server.ts +++ b/web/src/routes/api/v1/trail/upload/+server.ts @@ -75,8 +75,12 @@ export async function PUT(event: RequestEvent) { } if (trail.lat && trail.lon) { - const location = await searchLocationReverse(trail.lat, trail.lon) - trail.location ??= location; + try { + const location = await searchLocationReverse(trail.lat, trail.lon, event.fetch) + trail.location ??= location; + } catch (e: any) { + console.warn("Reverse geocoding failed during upload", e); + } } trail.public = event.locals.settings.privacy?.trails == "public" diff --git a/web/src/routes/api/v1/valhalla/height/+server.ts b/web/src/routes/api/v1/valhalla/height/+server.ts index c17d5bee..4f6b7625 100644 --- a/web/src/routes/api/v1/valhalla/height/+server.ts +++ b/web/src/routes/api/v1/valhalla/height/+server.ts @@ -1,5 +1,6 @@ -import { env } from '$env/dynamic/public'; -import { error, json, type NumericRange, type RequestEvent } from "@sveltejs/kit"; +import { getValhallaBaseUrl } from '$lib/server/valhalla'; +import { proxyJsonResponse } from '$lib/server/http'; +import { json, type RequestEvent } from "@sveltejs/kit"; /** @@ -29,18 +30,15 @@ import { error, json, type NumericRange, type RequestEvent } from "@sveltejs/kit * description: Internal Server Error */ export async function POST(event: RequestEvent) { + const baseUrl = getValhallaBaseUrl(); const data = await event.request.json() - if (!env.PUBLIC_VALHALLA_URL) { - return error(400, "PUBLIC_VALHALLA_URL not set") + if (!baseUrl) { + return json({ message: "VALHALLA_URL not set" }, { status: 400 }) } try { - const r = await event.fetch(env.PUBLIC_VALHALLA_URL + '/height', { method: "POST", body: JSON.stringify(data) }); - const response = await r.json(); - if (!r.ok) { - throw error(r.status as NumericRange<400,500>, response); - } - return json(response); + const response = await event.fetch(baseUrl + '/height', { method: "POST", body: JSON.stringify(data) }); + return await proxyJsonResponse(response); } catch (e: any) { - throw error(e.status || 500, e) + return json({ message: "Valhalla request failed" }, { status: 502 }) } -} \ No newline at end of file +} diff --git a/web/src/routes/api/v1/valhalla/route/+server.ts b/web/src/routes/api/v1/valhalla/route/+server.ts index 543d7097..26bba480 100644 --- a/web/src/routes/api/v1/valhalla/route/+server.ts +++ b/web/src/routes/api/v1/valhalla/route/+server.ts @@ -1,6 +1,10 @@ -import { env } from '$env/dynamic/public'; -import { error, json, type NumericRange, type RequestEvent } from "@sveltejs/kit"; +import { getValhallaBaseUrl } from '$lib/server/valhalla'; +import { proxyJsonResponse } from '$lib/server/http'; +import { json, type RequestEvent } from "@sveltejs/kit"; +type RouteRequestBody = Record & { + include_elevation_profile?: boolean; +}; /** * @swagger @@ -29,19 +33,19 @@ import { error, json, type NumericRange, type RequestEvent } from "@sveltejs/kit * description: Internal Server Error */ export async function POST(event: RequestEvent) { - const data = await event.request.json() - if (!env.PUBLIC_VALHALLA_URL) { - return json({ message: "PUBLIC_VALHALLA_URL not set" }, { status: 400 }) + const baseUrl = getValhallaBaseUrl(); + const data: RouteRequestBody = await event.request.json(); + if (!baseUrl) { + return json({ message: "VALHALLA_URL not set" }, { status: 400 }) } - try { - const r = await event.fetch(env.PUBLIC_VALHALLA_URL + '/route', { method: "POST", body: JSON.stringify(data) }); - const response = await r.json(); - if (!r.ok) { - return json({ message: response }, { status: r.status }) - } - return json(response); + try { + const response = await event.fetch(baseUrl + '/route', { + method: "POST", + body: JSON.stringify(data) + }); + return await proxyJsonResponse(response); } catch (e: any) { - return json({ message: e }, { status: 500 }) + return json({ message: "Valhalla request failed" }, { status: 502 }) } -} \ No newline at end of file +} diff --git a/web/src/routes/trail/edit/[id]/+page.svelte b/web/src/routes/trail/edit/[id]/+page.svelte index e7009a6b..fc5977f2 100644 --- a/web/src/routes/trail/edit/[id]/+page.svelte +++ b/web/src/routes/trail/edit/[id]/+page.svelte @@ -1,5 +1,4 @@