Source: services/asyncRequestApi.js

import axios from 'axios'
import config from '../../config/index.js'
import ResultData from '../models/requestData.js'
import logger from '../utils/logger.js'
import { types } from '../utils/logging.js'

const requestsEndpoint = `${config.asyncRequestApi.url}/${config.asyncRequestApi.requestsEndpoint}`

/**
 * Creates a `check_file` request for a file already uploaded to S3.
 *
 * @param {object} formData
 * @param {string} formData.uploadedFilename - name the file was stored under
 * @param {string} formData.originalFilename - name the user's file had
 * @param {string} formData.dataset
 * @param {string} formData.collection
 * @param {string} [formData.geomType] - only set for datasets that require it (e.g. tree)
 * @param {string} formData.organisationName - organisation code, not the display name
 * @returns {Promise<string>} id of the created request, stored in the wizard as `request_id`
 */
export const postFileRequest = async (formData) => {
  const { uploadedFilename, originalFilename, dataset, collection, geomType, organisationName } = formData

  return await postRequest({
    dataset,
    collection,
    organisationName,
    geom_type: geomType,
    uploaded_filename: uploadedFilename,
    original_filename: originalFilename,
    type: 'check_file'
  })
}

/**
 * Creates a `check_url` request for a user supplied endpoint URL.
 *
 * Only requests of this type can go on to be provided — the submit wizard checks
 * `type === 'check_url'` before it will accept a request.
 *
 * @param {object} formData
 * @param {string} formData.url - the endpoint URL to check
 * @param {string} formData.dataset
 * @param {string} formData.collection
 * @param {string} [formData.geomType] - only set for datasets that require it (e.g. tree)
 * @param {string} formData.organisationName - organisation code, not the display name
 * @returns {Promise<string>} id of the created request, stored in the wizard as `request_id`
 */
export const postUrlRequest = async (formData) => {
  const { url, dataset, collection, geomType, organisationName } = formData
  logger.debug('postUrlRequest', { url, dataset, collection, geomType, organisationName })
  return await postRequest({
    dataset,
    collection,
    geom_type: geomType,
    organisationName,
    url,
    type: 'check_url'
  })
}

/**
 * Creates a request from an already-built params object, without the field mapping
 * `postUrlRequest` and `postFileRequest` do. Used when resubmitting a check with user
 * supplied column mappings, where the params are copied from the original request.
 *
 * @param {object} params - passed to the API as-is; the caller owns the shape
 * @returns {Promise<string>} id of the created request
 */
export const postCheckRequest = async (params) => {
  return await postRequest(params)
}

/**
 * POSTs a requeset to the 'publish' API.
 *
 * @param {*} formData
 * @returns {Promise<string>} uuid - unique id of the uploaded file
 */
const postRequest = async (formData) => {
  try {
    const response = await axios.post(requestsEndpoint, { params: formData })
    return response.data.id // assuming the response contains the id
  } catch (error) {
    // see: https://axios-http.com/docs/handling_errors
    const errorDetails = {
      requestData: formData,
      responseStatus: error.response?.status,
      responseData: error.response?.data,
      errorCode: error.code,
      errorMessage: error.message,
      errorCause: error?.cause,
      url: error?.config?.url
    }
    const errorMessage = `Post request failed with status ${errorDetails.responseStatus} and message: ${errorDetails.errorMessage}`
    logger.warn('postRequest()', { type: types.App, errorDetails })
    const newError = new Error(errorMessage, { cause: error })
    newError.code = error.code
    newError.response = error.response
    throw newError
  }
}

/**
 * Fetches a request and wraps it in a {@link ResultData} model.
 *
 * Note the error handling: a **404 is rethrown unchanged** so callers can tell "no such
 * request" apart from a genuine failure and redirect instead of erroring (the submit
 * wizard's guards rely on this). Every other failure is wrapped in a generic `Error`
 * with the original attached as `cause`.
 *
 * Uses a hardcoded 15s timeout — `config.asyncRequestApi.requestTimeout` is declared in
 * config but not read here.
 *
 * @param {string} resultId - id returned by one of the `post*Request` functions
 * @param {*} [opts] - currently unused
 * @returns {Promise<ResultData>}
 * @throws the original axios error when the request does not exist (404), otherwise a
 *   wrapped `Error`
 */
export const getRequestData = async (resultId, opts = undefined) => {
  const url = new URL(`${config.asyncRequestApi.url}/${config.asyncRequestApi.requestsEndpoint}/${resultId}`)
  try {
    const response = await axios.get(url, { timeout: 15000 })
    return new ResultData(response.data)
  } catch (error) {
    if (error?.response?.status === 404) {
      throw error
    }
    const status = error.response?.status ?? error.code ?? 'Unknown'
    throw new Error(`HTTP error! status: ${status}: ${error.message}`, { cause: error })
  }
}