Loading IVOA.js +42 −26 Original line number Diff line number Diff line Loading @@ -47,7 +47,7 @@ const IVOA_capabilitiesEndpoint = staticEndpoint("Stat/capabilities.xml"); * Functional signature: Request -> IO -> IO String * * @param {Request} req - Incoming HTTP request * @returns {function(Symbol): Promise<string>} - XML content in VOTable format or error message * @returns {function(Symbol): Promise<string>} - CPS IO action that returns VOTable XML content or an error message. */ const IVOA_tablesEndpoint = (req) => async (IO) => { const query = await readFile("Query/tables.sql")(IO); Loading @@ -63,22 +63,27 @@ const IVOA_tablesEndpoint = (req) => async (IO) => { }; /** * Handles synchronous TAP queries. * Executes a SQL query and returns results in VOTable format. * Executes an SQL query and returns a VOTable representation. * * Functional signature: Request -> IO -> IO String * * @param {Request} req - Incoming HTTP request * @returns {function(Symbol): Promise<string>} - XML content in VOTable format or error message * @returns {function(Symbol): Promise<string>} - A CPS IO action that returns VOTable XML content or an error message. */ const IVOA_syncEndpoint = (req) => async (IO) => { const query = req.query?.QUERY || req.body?.QUERY || req.body?.query || null; // Extract TAP parameters from the request let query, requestType, clientId; const requestType = req.method === "GET" ? req.query?.request : req.body?.request || "doQuery"; const clientId = (req.method === "GET" ? req.query?.clientId : req.body?.clientId) || uuidv4(); if (req.method === "GET") { query = req.query?.QUERY; requestType = req.query?.request || "doQuery"; clientId = req.query?.clientId || uuidv4(); } else { const postData = req.body || {}; query = postData.QUERY; requestType = postData.request || "doQuery"; clientId = postData.clientId || uuidv4(); } if (!query) { return makeSyncError("Missing QUERY parameter"); Loading @@ -90,16 +95,20 @@ const IVOA_syncEndpoint = (req) => async (IO) => { ); } // Execute the SQL query const conn = await connPostgreSQL()(IO); const result = await fetchQueryResult(conn, query, [])(IO); await quitPostgreSQL(conn)(IO); if (result.tag === "nothing") { if (!result || result.tag === "nothing" || !result.value) { return makeSyncError("No results"); } const { fields, rows } = result.value; // Extract fields and rows from the query result const fields = result.value.fields ?? []; const rows = Array.isArray(result.value.rows) ? result.value.rows : []; // Initialize VOTable structure and define table fields const redis = await connRedis()(IO); const votable = xmlbuilder Loading @@ -122,32 +131,39 @@ const IVOA_syncEndpoint = (req) => async (IO) => { const data = table.ele("DATA").ele("TABLEDATA"); // Iterate over result rows, write each to the VOTable, and store in Redis with access_url for (const row of rows) { const tr = data.ele("TR"); const rowUUID = uuidv4(); const accessUrl = `http://localhost:3000/datalink?id=${rowUUID}&clientId=${clientId}`; const accessUrl = `${URLs.serverHost}/datalink?id=${rowUUID}&clientId=${clientId}`; const serializedRow = Object.fromEntries( Object.entries(row).map(([k, v]) => [ k, typeof v === "object" && v !== null ? JSON.stringify(v) : String(v), ]), ); serializedRow.access_url = accessUrl; await setRedis(redis, { key: `client:${clientId}:query:${rowUUID}`, value: JSON.stringify({ ...row, access_url: accessUrl }), value: JSON.stringify(serializedRow), })(IO); const tr = data.ele("TR"); try { fields.forEach((f) => { tr.ele("TD", row[f.name] ?? ""); const val = serializedRow[f.name] ?? ""; tr.ele("TD", val); }); tr.ele("TD", accessUrl); } catch (e) { console.error("ERROR inside row mapping:", e); throw new InternalError("BUG: row is not iterable or not valid object"); } } // Close Redis and return the VOTable XML as string await quitRedis(redis)(IO); return votable.end({ pretty: true }); }; export { IVOA_syncEndpoint }; /** * Handles the DataLink service. * Loading IVOA.py +12 −5 Original line number Diff line number Diff line Loading @@ -85,7 +85,7 @@ def ivoa_tables_endpoint(req) -> Callable[[object], Awaitable[str]]: def ivoa_sync_endpoint(req) -> Callable[[object], Awaitable[str]]: """ Handles synchronous TAP queries. Executes an SQL query and returns a VOTable representation. Functional signature: Request -> IO -> IO String Loading @@ -97,7 +97,10 @@ def ivoa_sync_endpoint(req) -> Callable[[object], Awaitable[str]]: """ async def inner(io): # Extracts TAP parameters from the request method = req.method if method == "GET": query = req.query.get("QUERY") request_type = req.query.get("request", "doQuery") Loading @@ -116,22 +119,26 @@ def ivoa_sync_endpoint(req) -> Callable[[object], Awaitable[str]]: 'The parameter "request" must be "doQuery" or omitted' ) # Execute the SQL query conn = await conn_postgresql()(io) result = await fetch_query_result(conn, query, [])(io) await quit_postgresql(conn)(io) if result["tag"] == "nothing": return make_sync_error("No results.") return make_sync_error("No results") # Extract fields and rows from the query result fields = result["value"]["fields"] rows = result["value"]["rows"] # Initialize VOTable structure and define table fields redis = await conn_redis()(io) votable = ET.Element( "VOTABLE", {"version": "1.3", "xmlns": "http://www.ivoa.net/xml/VOTable/v1.3"}, ) resource = ET.SubElement(votable, "RESOURCE", {"type": "results"}) table = ET.SubElement(resource, "TABLE") Loading @@ -147,6 +154,7 @@ def ivoa_sync_endpoint(req) -> Callable[[object], Awaitable[str]]: ) data = ET.SubElement(ET.SubElement(table, "DATA"), "TABLEDATA") # Iterate over result rows, write each to the VOTable, and store in Redis with access_url for row in rows: tr = ET.SubElement(data, "TR") row_uuid = str(uuid.uuid4()) Loading @@ -155,9 +163,7 @@ def ivoa_sync_endpoint(req) -> Callable[[object], Awaitable[str]]: ) row_serialized = { k: ( str(v) if not isinstance(v, (dict, list)) else v ) # evita tipi non stringabili k: (str(v) if not isinstance(v, (dict, list)) else v) for k, v in row.items() } row_serialized["access_url"] = access_url Loading @@ -174,6 +180,7 @@ def ivoa_sync_endpoint(req) -> Callable[[object], Awaitable[str]]: ET.SubElement(tr, "TD").text = val ET.SubElement(tr, "TD").text = access_url # Close Redis and return the VOTable XML as string await quit_redis(redis)(io) xml_str = ET.tostring(votable, encoding="utf-8") return minidom.parseString(xml_str).toprettyxml(indent=" ") Loading Lib/REST.js +0 −1 Original line number Diff line number Diff line Loading @@ -80,7 +80,6 @@ export function startRESTServer(port, routes) { } server = app.listen(port); await putStr("REST server started")(IO); return { tag: "RESTActive", server }; }; } Loading Lib/REST.py +0 −1 Original line number Diff line number Diff line Loading @@ -80,7 +80,6 @@ def start_rest_server( site = web.TCPSite(runner, "0.0.0.0", port) await site.start() server_runner = runner await put_str("REST server started")(io) return {"tag": "RESTActive", "server": runner} return inner Loading Loading
IVOA.js +42 −26 Original line number Diff line number Diff line Loading @@ -47,7 +47,7 @@ const IVOA_capabilitiesEndpoint = staticEndpoint("Stat/capabilities.xml"); * Functional signature: Request -> IO -> IO String * * @param {Request} req - Incoming HTTP request * @returns {function(Symbol): Promise<string>} - XML content in VOTable format or error message * @returns {function(Symbol): Promise<string>} - CPS IO action that returns VOTable XML content or an error message. */ const IVOA_tablesEndpoint = (req) => async (IO) => { const query = await readFile("Query/tables.sql")(IO); Loading @@ -63,22 +63,27 @@ const IVOA_tablesEndpoint = (req) => async (IO) => { }; /** * Handles synchronous TAP queries. * Executes a SQL query and returns results in VOTable format. * Executes an SQL query and returns a VOTable representation. * * Functional signature: Request -> IO -> IO String * * @param {Request} req - Incoming HTTP request * @returns {function(Symbol): Promise<string>} - XML content in VOTable format or error message * @returns {function(Symbol): Promise<string>} - A CPS IO action that returns VOTable XML content or an error message. */ const IVOA_syncEndpoint = (req) => async (IO) => { const query = req.query?.QUERY || req.body?.QUERY || req.body?.query || null; // Extract TAP parameters from the request let query, requestType, clientId; const requestType = req.method === "GET" ? req.query?.request : req.body?.request || "doQuery"; const clientId = (req.method === "GET" ? req.query?.clientId : req.body?.clientId) || uuidv4(); if (req.method === "GET") { query = req.query?.QUERY; requestType = req.query?.request || "doQuery"; clientId = req.query?.clientId || uuidv4(); } else { const postData = req.body || {}; query = postData.QUERY; requestType = postData.request || "doQuery"; clientId = postData.clientId || uuidv4(); } if (!query) { return makeSyncError("Missing QUERY parameter"); Loading @@ -90,16 +95,20 @@ const IVOA_syncEndpoint = (req) => async (IO) => { ); } // Execute the SQL query const conn = await connPostgreSQL()(IO); const result = await fetchQueryResult(conn, query, [])(IO); await quitPostgreSQL(conn)(IO); if (result.tag === "nothing") { if (!result || result.tag === "nothing" || !result.value) { return makeSyncError("No results"); } const { fields, rows } = result.value; // Extract fields and rows from the query result const fields = result.value.fields ?? []; const rows = Array.isArray(result.value.rows) ? result.value.rows : []; // Initialize VOTable structure and define table fields const redis = await connRedis()(IO); const votable = xmlbuilder Loading @@ -122,32 +131,39 @@ const IVOA_syncEndpoint = (req) => async (IO) => { const data = table.ele("DATA").ele("TABLEDATA"); // Iterate over result rows, write each to the VOTable, and store in Redis with access_url for (const row of rows) { const tr = data.ele("TR"); const rowUUID = uuidv4(); const accessUrl = `http://localhost:3000/datalink?id=${rowUUID}&clientId=${clientId}`; const accessUrl = `${URLs.serverHost}/datalink?id=${rowUUID}&clientId=${clientId}`; const serializedRow = Object.fromEntries( Object.entries(row).map(([k, v]) => [ k, typeof v === "object" && v !== null ? JSON.stringify(v) : String(v), ]), ); serializedRow.access_url = accessUrl; await setRedis(redis, { key: `client:${clientId}:query:${rowUUID}`, value: JSON.stringify({ ...row, access_url: accessUrl }), value: JSON.stringify(serializedRow), })(IO); const tr = data.ele("TR"); try { fields.forEach((f) => { tr.ele("TD", row[f.name] ?? ""); const val = serializedRow[f.name] ?? ""; tr.ele("TD", val); }); tr.ele("TD", accessUrl); } catch (e) { console.error("ERROR inside row mapping:", e); throw new InternalError("BUG: row is not iterable or not valid object"); } } // Close Redis and return the VOTable XML as string await quitRedis(redis)(IO); return votable.end({ pretty: true }); }; export { IVOA_syncEndpoint }; /** * Handles the DataLink service. * Loading
IVOA.py +12 −5 Original line number Diff line number Diff line Loading @@ -85,7 +85,7 @@ def ivoa_tables_endpoint(req) -> Callable[[object], Awaitable[str]]: def ivoa_sync_endpoint(req) -> Callable[[object], Awaitable[str]]: """ Handles synchronous TAP queries. Executes an SQL query and returns a VOTable representation. Functional signature: Request -> IO -> IO String Loading @@ -97,7 +97,10 @@ def ivoa_sync_endpoint(req) -> Callable[[object], Awaitable[str]]: """ async def inner(io): # Extracts TAP parameters from the request method = req.method if method == "GET": query = req.query.get("QUERY") request_type = req.query.get("request", "doQuery") Loading @@ -116,22 +119,26 @@ def ivoa_sync_endpoint(req) -> Callable[[object], Awaitable[str]]: 'The parameter "request" must be "doQuery" or omitted' ) # Execute the SQL query conn = await conn_postgresql()(io) result = await fetch_query_result(conn, query, [])(io) await quit_postgresql(conn)(io) if result["tag"] == "nothing": return make_sync_error("No results.") return make_sync_error("No results") # Extract fields and rows from the query result fields = result["value"]["fields"] rows = result["value"]["rows"] # Initialize VOTable structure and define table fields redis = await conn_redis()(io) votable = ET.Element( "VOTABLE", {"version": "1.3", "xmlns": "http://www.ivoa.net/xml/VOTable/v1.3"}, ) resource = ET.SubElement(votable, "RESOURCE", {"type": "results"}) table = ET.SubElement(resource, "TABLE") Loading @@ -147,6 +154,7 @@ def ivoa_sync_endpoint(req) -> Callable[[object], Awaitable[str]]: ) data = ET.SubElement(ET.SubElement(table, "DATA"), "TABLEDATA") # Iterate over result rows, write each to the VOTable, and store in Redis with access_url for row in rows: tr = ET.SubElement(data, "TR") row_uuid = str(uuid.uuid4()) Loading @@ -155,9 +163,7 @@ def ivoa_sync_endpoint(req) -> Callable[[object], Awaitable[str]]: ) row_serialized = { k: ( str(v) if not isinstance(v, (dict, list)) else v ) # evita tipi non stringabili k: (str(v) if not isinstance(v, (dict, list)) else v) for k, v in row.items() } row_serialized["access_url"] = access_url Loading @@ -174,6 +180,7 @@ def ivoa_sync_endpoint(req) -> Callable[[object], Awaitable[str]]: ET.SubElement(tr, "TD").text = val ET.SubElement(tr, "TD").text = access_url # Close Redis and return the VOTable XML as string await quit_redis(redis)(io) xml_str = ET.tostring(votable, encoding="utf-8") return minidom.parseString(xml_str).toprettyxml(indent=" ") Loading
Lib/REST.js +0 −1 Original line number Diff line number Diff line Loading @@ -80,7 +80,6 @@ export function startRESTServer(port, routes) { } server = app.listen(port); await putStr("REST server started")(IO); return { tag: "RESTActive", server }; }; } Loading
Lib/REST.py +0 −1 Original line number Diff line number Diff line Loading @@ -80,7 +80,6 @@ def start_rest_server( site = web.TCPSite(runner, "0.0.0.0", port) await site.start() server_runner = runner await put_str("REST server started")(io) return {"tag": "RESTActive", "server": runner} return inner Loading