import { SupabaseClient, createClient } from "https://esm.sh/@supabase/supabase-js@2"; interface StraetoVariables { [key: string]: number | string; } interface StraetoQuery { operationName: string; variables: StraetoVariables; extensions: StraetoQueryExtensions; } interface StraetoQueryExtensions { persistedQuery: StraetoPersistedQuery; } interface StraetoPersistedQuery { version: number; sha256Hash: string; } interface StraetoBusStop { id: number; name: string; lat: number; lon: number; type: number; rotation: number; code: string | null; isTerminal: boolean; routes: Array; alerts: Array; __typename: string; } interface DbBusStop { id: number; stop_name: string; coords: string; // TODO better geometry type representation type: number; rotation: number; code: string | null; is_terminal: boolean; routes: Array; } function toDbBusStop(straetoStop: StraetoBusStop): DbBusStop { return { id: straetoStop.id, code: straetoStop.code, stop_name: straetoStop.name, coords: `POINT(${straetoStop.lat} ${straetoStop.lon})`, type: straetoStop.type, rotation: straetoStop.rotation, is_terminal: straetoStop.isTerminal, routes: straetoStop.routes, }; } function toQuerystring(obj: StraetoQuery) { return Object.keys(obj).map(function (variableName) { const variableValue = obj[variableName as keyof typeof obj]; if (typeof variableValue == "object") { return encodeURIComponent(variableName) + "=" + encodeURIComponent(JSON.stringify(variableValue)); } else { return encodeURIComponent(variableName) + "=" + encodeURIComponent(variableValue); } }).join("&"); } const graphqlParams: StraetoQuery = { operationName: "Stops", variables: {}, extensions: { persistedQuery: { version: 1, sha256Hash: "6303de055cc42db47a4e1f1cc941b8c86d11c147903bed231e6d4bddcf0e1312", }, }, }; async function getStops(): Promise> { const apiUrl = `https://straeto.is/graphql?${toQuerystring(graphqlParams)}`; const opts = { method: "GET", headers: { "Content-Type": "application/json", }, }; const resp = await fetch(apiUrl, opts); const body = await resp.json(); return body.data.GtfsStops.results; } async function verifyUserIsAdmin(userClient: SupabaseClient): Promise { const user = await userClient.auth.getUser(); const metadata = user.data.user?.app_metadata; if (!metadata || (metadata && !metadata.claims_admin)) { return false; } return true; } Deno.serve(async (req) => { const authHeader = req.headers.get("Authorization")!; const userClient = createClient( Deno.env.get("SUPABASE_URL") ?? "", Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "", { global: { headers: { Authorization: authHeader } } }, ); const secureClient = createClient( Deno.env.get("SUPABASE_URL") ?? "", Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "", ); if (!(await verifyUserIsAdmin(userClient))) { return new Response( JSON.stringify({ status: "denied", error: "must be an admin" }), { status: 403, headers: { "Content-Type": "application/json" } }, ); } const busStops = await getStops(); console.log("inserting", busStops.length, "rows"); const toInsert = busStops.map(toDbBusStop); const insert = await secureClient.from("bus_stops") .upsert(toInsert, { ignoreDuplicates: true }); console.log(insert.statusText, insert.error?.code, insert.error?.message); return new Response( JSON.stringify({ "status": insert.statusText, "inserted": toInsert.length, "error": insert.error, }), { headers: { "Content-Type": "application/json" } }, ); }); /* To invoke locally: 1. Run `supabase start` (see: https://supabase.com/docs/reference/cli/supabase-start) 2. Make an HTTP request: curl -i --location --request POST 'http://127.0.0.1:54321/functions/v1/download-bus-stops' \ --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0' \ --header 'Content-Type: application/json' */