<commit_before>const axios = require('axios');
const crypto = require('crypto');
const queryString = require('query-string');

let response;

/**
 *
 * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
 * @param {Object} event - API Gateway Lambda Proxy Input Format
 *
 * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html
 * @param {Object} context
 *
 * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
 * @returns {Object} object - API Gateway Lambda Proxy Output Format
 *
 */
exports.lambdaHandler = async (event, context) => {
    try {
      const body = validateJSON(event);

      if (body.jwt) {
        const {jwt, json, learnersApiUrl} = body;
        const parsedJson = JSON.parse(json);
        // create a copy of our request to display back to the user
        const infoBody = {
          jwt,
          json: parsedJson
        };
        let message = "Called with:\n\n";
        message += JSON.stringify(infoBody, null, 2) + "\n\n";
        message += "Parsed JWT:\n\n";
        message += Buffer.from(body.jwt.split('.')[1], 'base64').toString() + "\n\n";

        const pageSize = 5;   // for demo purposes, but really something like 1000 would be reasonable
        const learnersArray = await fetchLearnerData(jwt, parsedJson.query, learnersApiUrl, pageSize);

        message += `Learners returned, using page size ${pageSize}:\n`;
        message += `  Total pages: ${learnersArray.length}\n`;
        if (learnersArray.length > 0) {
          message += `  Total learners: ${learnersArray.reduce((acc, arr) => acc + arr.length, 0)}\n\n`;
          message += JSON.stringify(learnersArray, null, 2);
        }

        response = {
          statusCode: 200,
          body: message
        }
      } else {
        const json = body.json;
        const renderCSV = (key) => {
          if (json[key] && json[key].length > 0) {
            message = Object.keys(json[key][0]).join(",") + "\n";
            message += json[key].map(item => Object.values(item).join(",")).join("\n");
          } else {
            message = `${key.charAt(0).toUpperCase() + key.slice(1)} report requested, but no ${key} were in the query.\n\n`
            message += "Full query:\n\n"
            message += json;
          }
        }

        let message;

        if (json.type == "learners") {
          renderCSV("learners");
        } else if (json.type == "users") {
          renderCSV("users");
        } else {
          throw new Error("Demo report must be called from learner or user report page");
        }


        response = {
          statusCode: 200,
          body: message
        }
      }
    } catch (err) {
      console.log(err);
      response = {
        statusCode: 500,
        body: `${err.toString()}\n\n${err.stack}`
      }
    }

    return response
};

function validateJSON(event) {
  if (!event.body) {
    throw new Error("Missing post body in request")
  }

  const body = queryString.parse(event.body);

  if (body.jwt) {
    // pass back the wuole request without any verification
    return body;
  }

  let json = body.json;
  if (!json) {
    throw new Error("Missing json body parameter");
  }

  const signature = body.signature;
  if (!signature) {
    throw new Error("Missing signature body parameter");
  }

  const hmac = crypto.createHmac('sha256', process.env.JWT_HMAC_SECRET);
  hmac.update(json);
  const signatureBuffer = new Buffer.from(signature);
  const digestBuffer = new Buffer.from(hmac.digest('hex'));

  if ((signatureBuffer.length !== digestBuffer.length) || !crypto.timingSafeEqual(signatureBuffer, digestBuffer)) {
    console.log("digestBuffer", digestBuffer.toString())
    throw new Error(`Invalid signature for json parameter.
        Either the report's JwtHmacSecret is incorrectly set, or the request JSON is not signed corrently.`);
  }

  try {
    json = JSON.parse(json);<commit_msg>Fix extracting the learnerApiUrl from portal request<commit_after>const axios = require('axios');
const crypto = require('crypto');
const queryString = require('query-string');

let response;

/**
 *
 * Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
 * @param {Object} event - API Gateway Lambda Proxy Input Format
 *
 * Context doc: https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-context.html
 * @param {Object} context
 *
 * Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
 * @returns {Object} object - API Gateway Lambda Proxy Output Format
 *
 */
exports.lambdaHandler = async (event, context) => {
    try {
      const body = validateJSON(event);

      if (body.jwt) {
        const {jwt, json} = body;
        const parsedJson = JSON.parse(json);
        const learnersApiUrl = parsedJson.learnersApiUrl;

        // create a copy of our request to display back to the user
        const infoBody = {
          jwt,
          json: parsedJson
        };
        let message = "Called with:\n\n";
        message += JSON.stringify(infoBody, null, 2) + "\n\n";
        message += "Parsed JWT:\n\n";
        message += Buffer.from(body.jwt.split('.')[1], 'base64').toString() + "\n\n";

        const pageSize = 5;   // for demo purposes, but really something like 1000 would be reasonable
        const learnersArray = await fetchLearnerData(jwt, parsedJson.query, learnersApiUrl, pageSize);

        message += `Learners returned, using page size ${pageSize}:\n`;
        message += `  Total pages: ${learnersArray.length}\n`;
        if (learnersArray.length > 0) {
          message += `  Total learners: ${learnersArray.reduce((acc, arr) => acc + arr.length, 0)}\n\n`;
          message += JSON.stringify(learnersArray, null, 2);
        }

        response = {
          statusCode: 200,
          body: message
        }
      } else {
        const json = body.json;
        const renderCSV = (key) => {
          if (json[key] && json[key].length > 0) {
            message = Object.keys(json[key][0]).join(",") + "\n";
            message += json[key].map(item => Object.values(item).join(",")).join("\n");
          } else {
            message = `${key.charAt(0).toUpperCase() + key.slice(1)} report requested, but no ${key} were in the query.\n\n`
            message += "Full query:\n\n"
            message += json;
          }
        }

        let message;

        if (json.type == "learners") {
          renderCSV("learners");
        } else if (json.type == "users") {
          renderCSV("users");
        } else {
          throw new Error("Demo report must be called from learner or user report page");
        }


        response = {
          statusCode: 200,
          body: message
        }
      }
    } catch (err) {
      console.log(err);
      response = {
        statusCode: 500,
        body: `${err.toString()}\n\n${err.stack}`
      }
    }

    return response
};

function validateJSON(event) {
  if (!event.body) {
    throw new Error("Missing post body in request")
  }

  const body = queryString.parse(event.body);

  if (body.jwt) {
    // pass back the whole request without any verification
    return body;
  }

  let json = body.json;
  if (!json) {
    throw new Error("Missing json body parameter");
  }

  const signature = body.signature;
  if (!signature) {
    throw new Error("Missing signature body parameter");
  }

  const hmac = crypto.createHmac('sha256', process.env.JWT_HMAC_SECRET);
  hmac.update(json);
  const signatureBuffer = new Buffer.from(signature);
  const digestBuffer = new Buffer.from(hmac.digest('hex'));

  if ((signatureBuffer.length !== digestBuffer.length) || !crypto.timingSafeEqual(signatureBuffer, digestBuffer)) {
    console.log("digestBuffer", digestBuffer.toString())
    throw new Error(`Invalid signature for json parameter.
        Either the report's JwtHmacSecret is incorrectly set, or the request JSON is not signed corrently.`);
  }

  try {
    json = JSON.parse(json);
{'fraction_non_alphanumeric': 0.13894277400581959, 'fraction_numerical': 0.006789524733268671, 'mean_word_length': 2.5342759211653814, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 15, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1353652', 'n_tokens_mistral': 2525, 'n_tokens_neox': 2428, 'n_words': 631}