Commit 1606558d authored by Massimo Costantini's avatar Massimo Costantini
Browse files

Updated comments

parent 738c4ea8
Loading
Loading
Loading
Loading
+42 −26
Original line number Diff line number Diff line
@@ -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);
@@ -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");
@@ -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
@@ -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.
 *
+12 −5
Original line number Diff line number Diff line
@@ -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

@@ -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")
@@ -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")

@@ -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())
@@ -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
@@ -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="  ")
+0 −1
Original line number Diff line number Diff line
@@ -80,7 +80,6 @@ export function startRESTServer(port, routes) {
    }

    server = app.listen(port);
    await putStr("REST server started")(IO);
    return { tag: "RESTActive", server };
  };
}
+0 −1
Original line number Diff line number Diff line
@@ -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