Ctrl K
ComunidadGuías…

server/api/ChartRoute.js

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
const { teamRoles, viewerRoles } = require("../../shared/roles.json");
const rateLimit = require("express-rate-limit");
const jwt = require("jsonwebtoken");

const chartVersions = require("../controllers/ChartVersionController");
const { mutationContext } = require("../modules/chartVersions");
const ChartController = require("../controllers/ChartController");
const { getPreview, placePreview } = require("../controllers/ChartPreviewController");
const ProjectController = require("../controllers/ProjectController");
const TeamController = require("../controllers/TeamController");
const SharePolicyController = require("../controllers/SharePolicyController");
const verifyToken = require("../modules/verifyToken");
const getUserFromToken = require("../modules/getUserFromToken");
const accessControl = require("../modules/accessControl");
const spreadsheetExport = require("../modules/spreadsheetExport");
const alertController = require("../controllers/AlertController");
const getEmbeddedChartData = require("../modules/getEmbeddedChartData");
const db = require("../models/models");
const { startRun } = require("../modules/updateAudit");
const { getDataRecovery } = require("../modules/dataRecovery");
const {
  isOutboundPolicyError,
  serializeOutboundPolicyError,
} = require("../modules/outboundTargetPolicy");
const checkChartShareAccess = require("../modules/chartShareAccess");
const { verifyProjectPassword } = require("../modules/projectPassword");
const { validateShareTokenPolicy, verifyShareToken } = require("../modules/shareToken");
const settings = require("../settings");

const apiLimiter = (max = 10) => {
  return rateLimit({
    windowMs: 60 * 1000, // 1 minute
    max,
  });
};

module.exports = (app) => {
  const chartController = new ChartController();
  const projectController = new ProjectController();
  const teamController = new TeamController();

  app.route("/chart-previews/:chart_id")
    .all(apiLimiter(30), verifyToken, (_req, res, next) => {
      res.set("Cache-Control", "private, no-store");
      next();
    })
    .get(async (req, res) => {
      try {
        return res.json(await getPreview(req.params.chart_id, req.user.id));
      } catch (error) {
        return res.status(error.statusCode || 500).json({ message: error.statusCode ? error.message : "The preview could not load. Try again." });
      }
    })
    .post(async (req, res) => {
      try {
        return res.json(await placePreview(req.params.chart_id, req.body.targetProjectId, req.user.id));
      } catch (error) {
        return res.status(error.statusCode || 500).json({ message: error.statusCode ? error.message : "The chart could not be placed. Try again." });
      }
    });

  const sendPolicyError = (res, error) => {
    if (!isOutboundPolicyError(error)) return false;
    return res.status(400).send(serializeOutboundPolicyError(error));
  };

  const getPublicPasswordInput = (req) => {
    return req.body?.password || req.query?.password || req.query?.pass || req.headers.pass;
  };

  const verifyProjectSharePolicyAccess = async (req, project, sharePolicy) => {
    if (!sharePolicy) {
      return true;
    }

    if (sharePolicy.visibility === "disabled") {
      return false;
    }

    if (sharePolicy.visibility === "public") {
      return true;
    }

    const shareToken = req.query?.token || req.body?.token;
    if (!shareToken) {
      return false;
    }

    try {
      const decodedToken = verifyShareToken(shareToken);
      validateShareTokenPolicy(decodedToken, sharePolicy, "Project", project.id);
      return true;
    } catch (_error) {
      return false;
    }
  };

  const hasProjectReadAccess = async (project, user) => {
    if (!project || !user?.id) {
      return false;
    }

    const teamRole = await teamController.getTeamRole(project.team_id, user.id);
    if (!teamRole?.role) {
      return false;
    }

    const permission = accessControl.can(teamRole.role).readOwn("chart");
    if (!permission.granted) {
      return false;
    }

    user.teamRole = teamRole;

    if (teamRoles.includes(teamRole.role)) {
      return true;
    }

    if (teamRole?.projects?.length > 0) {
      const hasProjectAccess = teamRole.projects.some((projectId) => `${projectId}` === `${project.id}`);
      if (hasProjectAccess || project.ghost) {
        user.projects = teamRole.projects;
        return true;
      }
    }

    return false;
  };

  const checkPublicAccess = async (req, requiredAccess) => {
    const chart = await chartController.findById(req.params.chart_id);
    if (!chart) {
      return Promise.reject(404);
    }

    const projectId = req.params.project_id || chart.project_id;
    const project = await projectController.findById(projectId);
    if (!project || `${chart.project_id}` !== `${project.id}`) {
      return Promise.reject(401);
    }

    if (!chart.onReport || chart.draft) {
      return Promise.reject(401);
    }

    const hasAuthenticatedProjectAccess = await hasProjectReadAccess(project, req.user);

    if (!project.public && !hasAuthenticatedProjectAccess) {
      return Promise.reject(401);
    }

    const passwordInput = getPublicPasswordInput(req);
    const isPasswordCorrect = await verifyProjectPassword(passwordInput, project.password);
    if (project.passwordProtected && !isPasswordCorrect && !hasAuthenticatedProjectAccess) {
      return Promise.reject(401);
    }

    const sharePolicy = await db.SharePolicy.findOne({
      where: {
        entity_type: "Project",
        entity_id: project.id,
      },
    });

    const hasSharePolicyAccess = await verifyProjectSharePolicyAccess(req, project, sharePolicy);
    if (!hasSharePolicyAccess && !hasAuthenticatedProjectAccess) {
      return Promise.reject(401);
    }

    if (requiredAccess === "export") {
      const team = await teamController.findById(project.team_id);
      if (!team?.allowReportExport) {
        return Promise.reject(401);
      }
    }

    const hydratedChart = await chartController.hydratePreparedChart(chart, {
      refresh: true,
      timezone: project.timezone,
    });
    return { chart: hydratedChart, project, hasAuthenticatedProjectAccess };
  };

  const resolveRuntimeVariables = async (req, project, providedVariables = {}) => {
    const queryParams = req.body?.queryParams || {};
    const urlVariables = projectController._extractVariablesFromQuery(queryParams);
    const requestedVariables = { ...urlVariables, ...(providedVariables || {}) };

    if (await hasProjectReadAccess(project, req.user)) {
      return requestedVariables;
    }

    const sharePolicy = await db.SharePolicy.findOne({
      where: {
        entity_type: "Project",
        entity_id: project.id,
      },
    });

    if (sharePolicy) {
      return projectController._mergeVariablesWithPolicy(requestedVariables, sharePolicy);
    }

    return project.public ? requestedVariables : (providedVariables || {});
  };

  const checkPermissions = (actionType = "readOwn", entity = "chart") => {
    return async (req, res, next) => {
      const projectId = req.params.project_id || req.body?.project_id;
      const chartId = req.params.chart_id || req.body?.chart_id;

      const project = await projectController.findById(projectId);
      if (!project) {
        return res.status(404).json({ message: "Project not found" });
      }

      const teamRole = await teamController.getTeamRole(project.team_id, req.user.id);

      req.user.teamRole = teamRole;

      if (!teamRole?.role) {
        return res.status(403).json({ message: "Access denied" });
      }

      // check if the chart is part of the right project
      if (chartId && projectId) {
        const chart = await chartController.findById(req.params.chart_id);
        if (!chart) {
          return res.status(404).json({ message: "Chart not found" });
        }
        if (chart.draft && viewerRoles.includes(teamRole.role)) {
          return res.status(404).json({ message: "Chart not found" });
        }
        if (chart.project_id.toString() !== projectId.toString()) {
          return res.status(403).json({ message: "Access denied" });
        }
      }

      // check if the alert is part of a chart in the right project
      if (chartId && req.params.alert_id) {
        const alert = await alertController.findById(req.params.alert_id);
        if (alert.chart_id.toString() !== chartId.toString()) {
          return res.status(403).json({ message: "Access denied" });
        }
      }

      // check if the cdc is part of a chart in the right project
      if (chartId && req.params.cdc_id) {
        const cdc = await db.ChartDatasetConfig.findByPk(req.params.cdc_id);
        if (cdc.chart_id.toString() !== chartId.toString()) {
          return res.status(403).json({ message: "Access denied" });
        }
      }

      if (teamRoles.includes(teamRole.role)) {
        const permission = accessControl.can(teamRole.role)[actionType](entity);
        if (!permission.granted) {
          return res.status(403).json({ message: "Access denied" });
        }

        return next();
      }

      if (teamRole?.projects?.length > 0) {
        if (projectId) {
          const filteredProjects = teamRole.projects.filter((o) => `${o}` === `${projectId}`);
          if (filteredProjects.length === 0 && !project.ghost) {
            return res.status(403).json({ message: "Access denied" });
          }
        }

        const permission = accessControl.can(teamRole.role)[actionType](entity);
        if (!permission.granted) {
          return res.status(403).json({ message: "Access denied" });
        }

        req.user.projects = teamRole.projects;

        return next();
      }

      return res.status(403).json({ message: "Access denied" });
    };
  };

  const isPublicProjectFilterAllowed = async (project, chart, passwordInput) => {
    if (!project?.public) {
      return false;
    }

    const isPasswordCorrect = await verifyProjectPassword(passwordInput, project.password);
    if (project?.passwordProtected && !isPasswordCorrect) {
      return false;
    }

    if (!chart?.onReport) {
      return false;
    }

    const sharePolicy = await db.SharePolicy.findOne({
      where: {
        entity_type: "Project",
        entity_id: project.id,
      },
    });

    if (!sharePolicy) {
      return true;
    }

    if (sharePolicy.visibility === "disabled") {
      return false;
    }

    return sharePolicy.visibility === "public";
  };

  const checkFilterAccess = async (req, res, next) => {
    try {
      const project = await projectController.findById(req.params.project_id);
      if (!project) {
        return res.status(404).json({ message: "Project not found" });
      }

      const chart = await chartController.findById(req.params.chart_id);
      if (!chart) {
        return res.status(404).json({ message: "Chart not found" });
      }

      if (`${chart.project_id}` !== `${project.id}`) {
        return res.status(403).json({ message: "Access denied" });
      }

      if (req.user?.id) {
        const teamRole = await teamController.getTeamRole(project.team_id, req.user.id);
        if (teamRole?.role) {
          req.user.teamRole = teamRole;

          const permission = accessControl.can(teamRole.role).readOwn("chart");
          if (permission.granted) {
            if (teamRoles.includes(teamRole.role)) {
              return next();
            }

            if (teamRole?.projects?.length > 0) {
              const hasProjectAccess = teamRole.projects.some((projectId) => `${projectId}` === `${project.id}`);
              if (hasProjectAccess || project.ghost) {
                req.user.projects = teamRole.projects;
                return next();
              }
            }
          }
        }
      }

      const snapshotAccessToken = req.query.accessToken || req.body?.accessToken;
      if (snapshotAccessToken) {
        try {
          const decodedAccessToken = jwt.verify(snapshotAccessToken, settings.encryptionKey);
          if (`${decodedAccessToken?.project_id}` === `${project.id}`) {
            return next();
          }
        } catch (error) {
          // Continue to other auth mechanisms.
        }
      }

      const shareToken = req.query.token || req.body?.token;
      if (!shareToken) {
        const passwordInput = getPublicPasswordInput(req);
        const allowPublicAccess = await isPublicProjectFilterAllowed(project, chart, passwordInput);
        if (allowPublicAccess) {
          return next();
        }
        return res.status(401).json({ message: "Not authorized" });
      }

      let decodedToken;
      try {
        decodedToken = verifyShareToken(shareToken);
      } catch (error) {
        return res.status(401).json({ message: "Not authorized" });
      }

      if (!decodedToken?.sub?.sharePolicyId || !decodedToken?.sub?.type || !decodedToken?.sub?.id) {
        return res.status(401).json({ message: "Not authorized" });
      }

      const sharePolicy = await db.SharePolicy.findByPk(decodedToken.sub.sharePolicyId);
      if (!sharePolicy) {
        return res.status(401).json({ message: "Not authorized" });
      }

      if (sharePolicy.visibility === "disabled") {
        return res.status(403).json({ message: "Share policy is disabled" });
      }

      if (decodedToken.sub.type === "Chart") {
        try {
          validateShareTokenPolicy(decodedToken, sharePolicy, "Chart", chart.id);
          await checkChartShareAccess(chart, project, getPublicPasswordInput(req));
        } catch (error) {
          return res.status(401).json({ message: "Not authorized" });
        }

        return next();
      }

      if (decodedToken.sub.type === "Project") {
        try {
          validateShareTokenPolicy(decodedToken, sharePolicy, "Project", project.id);
        } catch (error) {
          return res.status(401).json({ message: "Not authorized" });
        }

        if (project.passwordProtected) {
          const passwordInput = getPublicPasswordInput(req);
          const isPasswordCorrect = await verifyProjectPassword(passwordInput, project.password);
          if (!isPasswordCorrect) {
            return res.status(403).json({ message: "Enter the correct password" });
          }
        }

        if (!project.public || !chart.onReport) {
          return res.status(401).json({ message: "Not authorized" });
        }

        return next();
      }

      return res.status(401).json({ message: "Not authorized" });
    } catch (error) {
      return res.status(400).send(error);
    }
  };

  /*
  ** [MASTER] Route to get all the charts
  */
  app.get("/chart", verifyToken, (req, res) => {
    if (!req.user.admin) {
      return res.status(401).send({ error: "Not authorized" });
    }

    // check query parameters
    const conditions = {};
    if (req.query.exclude) {
      const exclusions = req.query.exclude.split(",");
      conditions.attributes = { exclude: exclusions };
    } else if (req.query.include) {
      const inclusions = req.query.include.split(",");
      conditions.attributes = inclusions;
    }

    return chartController.findAll(conditions)
      .then((charts) => {
        return res.status(200).send(charts);
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to get all the charts for a project
  */
  app.get("/project/:project_id/chart", verifyToken, checkPermissions("readAny"), (req, res) => {
    return chartController.findByProject(req.params.project_id)
      .then((charts) => {
        let filteredCharts = charts;
        if (viewerRoles.includes(req.user?.teamRole?.role)) {
          filteredCharts = charts.filter((c) => !c.draft);
        }
        return res.status(200).send(filteredCharts);
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to get a chart by id
  */
  app.get("/project/:project_id/chart/:chart_id", verifyToken, checkPermissions("readAny"), (req, res) => {
    return chartController.findById(req.params.chart_id, null, {
      refreshPreparedData: true,
    })
      .then((chart) => {
        return res.status(200).send(chart);
      })
      .catch((error) => {
        if (error === "401" || error.message === "401") {
          return res.status(401).send({ error: "Not authorized" });
        }
        if (error === "413" && error.message === "413") {
          return res.status(413).send(error);
        }
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to create a new chart
  */
  app.post("/project/:project_id/chart", verifyToken, checkPermissions("createOwn"), (req, res) => {
    // assign the project id to the new chart
    req.body.project_id = req.params.project_id;
    return chartController.create(req.body, req.user)
      .then((chart) => {
        return res.status(200).send(chart);
      })
      .catch((error) => {
        if (error.message && error.message.indexOf("406") > -1) {
          return res.status(406).send(error);
        }
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to quickly create a chart with all its chart dataset configs in one go
  */
  app.post("/project/:project_id/chart/quick-create", verifyToken, checkPermissions("createOwn"), (req, res) => {
    // Ensure project_id matches the route parameter
    req.body.project_id = req.params.project_id;
    return chartController.createWithChartDatasetConfigs(req.body, req.user)
      .then((chart) => {
        return res.status(200).send(chart);
      })
      .catch((error) => {
        if (error.message && error.message.indexOf("406") > -1) {
          return res.status(406).send(error);
        }
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  const historyPath = "/project/:project_id/chart/:chart_id/versions";
  const historyHandler = (action) => async (req, res) => {
    res.set("Cache-Control", "private, no-store");
    try {
      const context = { userId: req.user.id, projectId: req.params.project_id };
      const args = [req.params.chart_id, req.params.version, context];
      if (action === "list") args[1] = req.query.before;
      if (action === "restore") args[2] = mutationContext(req);
      return res.json(await chartVersions[action](...args));
    } catch (error) {
      return res.status(error.statusCode || 500).json({
        message: error.statusCode ? error.message : "Chart history could not load. Try again.",
      });
    }
  };
  app.get(historyPath, verifyToken, checkPermissions("updateOwn"), historyHandler("list"));
  app.get(`${historyPath}/:version`, verifyToken, checkPermissions("updateOwn"), historyHandler("detail"));
  app.post(`${historyPath}/:version/preview`, verifyToken, checkPermissions("updateOwn"), historyHandler("preview"));
  app.post(`${historyPath}/:version/restore`, verifyToken, checkPermissions("updateOwn"), historyHandler("restore"));

  /*
  ** Route to update a chart
  */
  app.put("/project/:project_id/chart/:chart_id", verifyToken, checkPermissions("updateOwn"), async (req, res) => {
    try {
      const chart = await chartController.update(req.params.chart_id, req.body, req.user, req.query.justUpdates, mutationContext(req));
      return res.status(200).send(chart);
    } catch (error) {
      return res.status(error.statusCode || 400).send({ message: error.message });
    }

  });
  // --------------------------------------------------------

  /*
  ** Route to restore a missing visualization binding
  */
  app.post(
    "/project/:project_id/chart/:chart_id/visualization/repair",
    verifyToken,
    checkPermissions("updateOwn"),
    (req, res) => {
      return Promise.resolve().then(() => chartController.repairVisualization(
        req.params.chart_id, req.body.bindingId, mutationContext(req)
      ))
        .then((chart) => {
          return res.status(200).send(chart);
        })
        .catch((error) => {
          return res.status(error.statusCode || 400).send({ message: error.message });
        });
    }
  );
  // --------------------------------------------------------

  /*
  ** Route to update the order of the chart
  */
  app.put("/project/:project_id/chart/:chart_id/order", verifyToken, checkPermissions("updateOwn"), (req, res) => {
    return chartController.changeDashboardOrder(req.params.chart_id, req.body.otherId)
      .then((updates) => {
        return res.status(200).send(updates);
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to remove a chart
  */
  app.delete("/project/:project_id/chart/:chart_id", verifyToken, checkPermissions("deleteOwn"), (req, res) => {
    return chartController.remove(req.params.chart_id)
      .then((response) => {
        return res.status(200).send({ removed: response });
      })
      .catch((error) => {
        if (error.message === "401") {
          return res.status(401).send({ error: "Not authorized" });
        }
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to test a query before saving
  */
  app.post("/project/:project_id/chart/test", verifyToken, checkPermissions("updateOwn"), (req, res) => {
    return chartController.testQuery(req.body, req.params.project_id)
      .then((data) => {
        return res.status(200).send(data);
      })
      .catch((error) => {
        if (error.message === "401") {
          return res.status(401).send({ error: "Not authorized" });
        }
        if (error.message.indexOf("413") > -1) {
          return res.status(413).send(error);
        }
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to test a query before saving
  */
  app.post("/project/:project_id/chart/preview", verifyToken, checkPermissions("updateOwn"), (req, res) => {
    let chart = req.body;
    if (chart.chart) chart = chart.chart; // eslint-disable-line
    return chartController.previewChart(
      chart,
      req.params.project_id,
      req.user,
      req.query.no_source,
      req.query.skip_parsing,
      req.body.variables,
    )
      .then((chart) => {
        return res.status(200).send(chart);
      })
      .catch((error) => {
        if (error.message === "401") {
          return res.status(401).send({ error: "Not authorized" });
        }
        if (error.message && error.message.indexOf("413") > -1) {
          return res.status(413).send(error);
        }

        return res.status(400).send({ error });
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to run the query for a chart
  */
  app.post("/project/:project_id/chart/:chart_id/query", verifyToken, checkPermissions("readOwn"), async (req, res) => {
    try {
      const project = await projectController.findById(req.params.project_id);
      const traceContext = await startRun({
        triggerType: "chart_manual",
        entityType: "chart",
        status: "running",
        teamId: project.team_id,
        projectId: Number(req.params.project_id),
        chartId: Number(req.params.chart_id),
        summary: {
          noSource: req.query.no_source === "true",
          getCache: Boolean(req.query.getCache),
        },
      });

      const chart = await chartController.updateChartData(
        req.params.chart_id,
        req.user,
        {
          noSource: req.query.no_source === "true",
          skipParsing: req.query.skip_parsing === "true",
          getCache: req.query.getCache,
          filters: req.body.filters,
          variables: req.body.variables,
          traceContext,
        },
      );

      return res.status(200).send(chart);
    } catch (error) {
      console.error((error && error.message) || error); // eslint-disable-line
      const policyResponse = sendPolicyError(res, error);
      if (policyResponse) return policyResponse;
      if (`${error}` === "401" || error.message === "401") {
        return res.status(401).send({ error: "Not authorized" });
      }
      if (`${error}` === "413" && error.message === "413") {
        return res.status(413).send(`${error}`);
      }
      const recovery = getDataRecovery(error);
      return res.status(400).json({ message: recovery?.message || "The chart could not be updated. Try again.", ...(recovery ? { recovery } : {}) });
    }
  });
  // --------------------------------------------------------

  /*
  ** Route to filter the charts from the dashboard
  */
  app.post("/project/:project_id/chart/:chart_id/filter", apiLimiter(50), getUserFromToken, checkFilterAccess, async (req, res) => {
    if (!req.body?.filters) return res.status(400).send("No filters selected");
    let noSource = req.query.no_source === "true";
    let skipParsing = req.query.skip_parsing === "true";
    let getCache = true;

    if (req.query.refresh === "true") {
      noSource = false;
      skipParsing = false;
      getCache = false;
    }

    try {
      const project = await projectController.findById(req.params.project_id);
      const variables = await resolveRuntimeVariables(req, project, req.body?.variables);
      const team = await teamController.findById(project.team_id);
      const hasAuthenticatedProjectAccess = Boolean(req.user?.teamRole?.role);
      const hasInternalAccessToken = Boolean(req.query.accessToken || req.body?.accessToken);

      if (req.query.refresh === "true" && !hasAuthenticatedProjectAccess && !hasInternalAccessToken && !team.allowReportRefresh) {
        return res.status(401).send({ error: "Not authorized" });
      }

      // filters are being passed, so the chart is not updated in the database
      const chart = await chartController.updateChartData(
        req.params.chart_id,
        req.user,
        {
          noSource,
          skipParsing,
          filters: req.body?.filters,
          getCache,
          cacheOnly: req.query.cacheOnly === "true",
          variables,
          runtimeOnly: true,
        },
      );

      if (typeof chart?.setDataValue === "function") {
        chart.setDataValue("showBranding", team.showBranding);
      } else if (chart) {
        chart.showBranding = team.showBranding;
      }
      return res.status(200).send(chart);
    } catch (error) {
      if (error === "401" || error.message === "401") {
        return res.status(401).send({ error: "Not authorized" });
      }
      if (error === "413" && error.message === "413") {
        return res.status(413).send(error);
      }
      if (error?.code === "RUNTIME_CHART_CACHE_MISS") {
        return res.status(200).send({
          id: Number(req.params.chart_id),
          cacheMiss: true,
        });
      }
      return res.status(400).send(error);
    }
  });
  // --------------------------------------------------------

  /*
  ** [DEPRECATED] Route to get a chart for embedding (must be public for success)
  */
  app.get("/chart/:share_string/embedded", apiLimiter(50), (req, res) => {
    // backwards-compatible code for public charts with ID-enabled embed
    /* Deprecated */
    if (req.params.share_string.length < 16) {
      return chartController.findById(req.params.share_string)
        .then(async (chart) => {
          if (!chart.public || chart.draft) {
            return new Promise((resolve, reject) => reject(new Error("401")));
          }

          // get the team's branding status
          const project = await projectController.findById(chart.project_id);
          await checkChartShareAccess(chart, project, getPublicPasswordInput(req));
          const team = await teamController.findById(project.team_id);

          return res.status(200).send(getEmbeddedChartData(chart, team));
        })
        .catch((error) => {
          if (error?.code === "PASSWORD_REQUIRED") {
            return res.status(403).send({ code: error.code, error: error.message });
          }
          if (error.message === "401") {
            return res.status(401).send({ error: "Not authorized" });
          }
          if (error.message && error.message.indexOf("413") > -1) {
            return res.status(413).send(error);
          }
          return res.status(400).send(error);
        });
    }

    // New! taking advantage of the share strings
    return chartController.findByShareString(req.params.share_string, req.query, getPublicPasswordInput(req))
      .then(async (chart) => {
        return res.status(200).send(chart);
      })
      .catch((error) => {
        if (error?.code === "PASSWORD_REQUIRED") {
          return res.status(403).send({ code: error.code, error: error.message });
        }
        if (error?.message === "401") {
          return res.status(401).send({ error: "Not authorized" });
        }
        if (error?.message?.indexOf("413") > -1) {
          return res.status(413).send(error);
        }
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to share a chart with a share policy
  */
  app.get("/chart/share/:share_string", apiLimiter(50), (req, res) => {
    return chartController.findBySharePolicy(req.params.share_string, req.query, getPublicPasswordInput(req))
      .then((chart) => {
        return res.status(200).send(chart);
      })
      .catch((error) => {
        if (error?.code === "PASSWORD_REQUIRED") {
          return res.status(403).send({ code: error.code, error: error.message });
        }
        if (
          error?.message === "401"
          || error?.message === "Invalid share token"
          || error?.message === "Share policy has expired"
          || error?.name === "JsonWebTokenError"
          || error?.name === "TokenExpiredError"
        ) {
          return res.status(401).send({ error: "Not authorized" });
        }
        if (error?.message === "Share policy is disabled") {
          return res.status(403).send({ error: "Not authorized" });
        }
        if (error?.message?.indexOf("413") > -1) {
          return res.status(413).send(error);
        }
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to generate a share token
  */
  app.post("/project/:project_id/chart/:chart_id/share/token", verifyToken, checkPermissions("updateOwn"), (req, res) => {
    return chartController.generateShareToken(req.params.chart_id, req.body)
      .then(({ token, url, sharePolicy }) => {
        return res.status(200).send({ token, url, sharePolicy });
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to get latest chart data without an authentication token
  */
  app.get("/chart/:chart_id", apiLimiter(50), (req, res) => {
    return checkPublicAccess(req, "view")
      .then(async ({ chart, project }) => {
        const team = await teamController.findById(project.team_id);
        return res.status(200).send({
          id: chart.id,
          name: chart.name,
          type: chart.type,
          subType: chart.subType,
          preparedDataUpdatedAt: chart.preparedDataUpdatedAt,
          render: chart.render,
          ChartDatasetConfigs: chart.ChartDatasetConfigs,
          mode: chart.mode,
          chartSize: chart.chartSize,
          project_id: chart.project_id,
          showBranding: team.showBranding,
          showGrowth: chart.showGrowth,
          timeInterval: chart.timeInterval,
          isLogarithmic: chart.isLogarithmic,
        });
      })
      .catch((err) => {
        if (err === 401 || err?.message === "401") {
          return res.status(401).send({ error: "Not authorized" });
        }
        if (err === 404 || err?.message === "404") {
          return res.status(404).send({ error: "Chart not found" });
        }
        return res.status(400).send(err);
      });
  });

  /*
  ** Route to run the query for a chart on a project that enables this
  */
  app.post("/chart/:chart_id/query", apiLimiter(50), getUserFromToken, (req, res) => {
    return checkPublicAccess(req, "view")
      .then(async ({ project }) => {
        const team = await teamController.findById(project.team_id);

        if (!team.allowReportRefresh) {
          throw new Error(401);
        }

        const variables = await resolveRuntimeVariables(req, project, req.body?.variables);
        const traceContext = await startRun({
          triggerType: "chart_manual",
          entityType: "chart",
          status: "running",
          teamId: project.team_id,
          projectId: Number(project.id),
          chartId: Number(req.params.chart_id),
          summary: {
            publicReportRefresh: true,
            noSource: req.query.no_source === "true",
            getCache: Boolean(req.query.getCache),
          },
        });

        return chartController.updateChartData(
          req.params.chart_id,
          null,
          {
            noSource: req.query.no_source === "true",
            skipParsing: req.query.skip_parsing === "true",
            getCache: req.query.getCache,
            filters: req.body?.filters,
            variables,
            traceContext,
          },
        );
      })
      .then((chart) => {
        return res.status(200).send(chart);
      })
      .catch((err) => {
        if (err === 401 || err?.message === "401") {
          return res.status(401).send({ error: "Not authorized" });
        }
        if (err === 404 || err?.message === "404") {
          return res.status(404).send({ error: "Chart not found" });
        }
        return res.status(400).send(err);
      });
  });

  /*
  ** Route used to export data in spreadsheet format
  */
  app.post("/project/:project_id/chart/export", verifyToken, checkPermissions("readAny"), (req, res) => {
    if (!req.user?.teamRole?.canExport && !teamRoles.includes(req.user?.teamRole?.role)) {
      return new Promise((resolve, reject) => reject(new Error(401)));
    }

    if (!Array.isArray(req.body?.chartIds) || req.body.chartIds.length === 0) {
      return res.status(400).send({
        message: "chartIds is required",
        error: "chartIds is required",
      });
    }

    return chartController.exportChartData(
      req.user.id,
      req.body.chartIds,
      req.body.filters,
      req.params.project_id,
      req.body.mode || "source"
    )
      .then((data) => {
        return spreadsheetExport(data);
      })
      .then((fileBuffer) => {
        return res.status(200).send(fileBuffer);
      })
      .catch((err) => {
        return res.status(400).send({
          message: (err && err.message) || err,
          error: (err && err.toString()) || err,
        });
      });
  });
  // --------------------------------------------------------

  /*
  ** Route used to export data from a PUBLIC dashboard
  */
  app.post("/project/:project_id/chart/export/public/:chart_id", apiLimiter(20), (req, res) => {
    return checkPublicAccess(req, "export")
      .then(() => {
        return chartController.exportChartData(
          null,
          [req.params.chart_id],
          req.body.filters,
          req.params.project_id,
          req.body.mode || "source"
        );
      })
      .then((data) => {
        return spreadsheetExport(data);
      })
      .then((fileBuffer) => {
        return res.status(200).send(fileBuffer);
      })
      .catch((err) => {
        if (err === 401 || err?.message === "401") {
          return res.status(401).send({
            message: "Not authorized",
            error: "Not authorized",
          });
        }
        if (err === 404 || err?.message === "404") {
          return res.status(404).send({
            message: "Chart not found",
            error: "Chart not found",
          });
        }
        return res.status(400).send({
          message: (err && err.message) || err,
          error: (err && err.toString()) || err,
        });
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to create a new share link
  */
  app.post("/project/:project_id/chart/:chart_id/share", verifyToken, checkPermissions("updateOwn"), (req, res) => {
    return chartController.createShare(req.params.chart_id)
      .then(() => {
        return chartController.findById(req.params.chart_id);
      })
      .then((chart) => {
        return res.status(200).send(chart);
      })
      .catch((error) => {
        if (error === "401" || error.message === "401") {
          return res.status(401).send({ error: "Not authorized" });
        }
        if (error === "413" && error.message === "413") {
          return res.status(413).send(error);
        }
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to delete a share link
  */
  app.delete("/project/:project_id/chart/:chart_id/share/:share_id", verifyToken, checkPermissions("updateOwn"), (req, res) => {
    return chartController.removeShare(req.params.share_id)
      .catch((error) => {
        if (error === "401" || error.message === "401") {
          return res.status(401).send({ error: "Not authorized" });
        }
        if (error === "413" && error.message === "413") {
          return res.status(413).send(error);
        }
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to create a new share policy
  */
  app.post("/project/:project_id/chart/:chart_id/share/policy", verifyToken, checkPermissions("updateOwn"), async (req, res) => {
    try {
      const policy = await chartController.createSharePolicy(req.params.chart_id);
      return res.status(200).send(policy);
    } catch (error) {
      return res.status(400).send(error);
    }
  });
  // --------------------------------------------------------

  /*
  ** Route to get all share policies for a chart
  */
  app.get("/project/:project_id/chart/:chart_id/share/policy", verifyToken, checkPermissions("readAny"), async (req, res) => {
    try {
      const policies = await SharePolicyController.findByEntityId("Chart", req.params.chart_id);
      return res.status(200).send(policies);
    } catch (error) {
      return res.status(400).send(error);
    }
  });
  // --------------------------------------------------------

  /*
  ** Route to update a share policy
  */
  app.put("/project/:project_id/chart/:chart_id/share/policy/:policy_id", verifyToken, checkPermissions("updateOwn"), async (req, res) => {
    try {
      const updatedPolicy = await SharePolicyController.updateSharePolicy(
        req.params.policy_id,
        req.body,
        "Chart",
        req.params.chart_id
      );
      return res.status(200).send(updatedPolicy);
    } catch (error) {
      if (error?.message === "404") {
        return res.status(404).send({ error: "Share policy not found" });
      }
      return res.status(400).send(error);
    }
  });
  // --------------------------------------------------------

  /*
  ** Route to delete a share policy
  */
  app.delete("/project/:project_id/chart/:chart_id/share/policy/:policy_id", verifyToken, checkPermissions("updateOwn"), async (req, res) => {
    try {
      await SharePolicyController.deleteSharePolicy(req.params.policy_id, "Chart", req.params.chart_id);
      return res.status(200).send({ deleted: true });
    } catch (error) {
      if (error?.message === "404") {
        return res.status(404).send({ error: "Share policy not found" });
      }
      return res.status(400).send(error);
    }
  });
  // --------------------------------------------------------

  /*
  ** Route to get chart alerts
  */
  app.get("/project/:project_id/chart/:chart_id/alert", verifyToken, checkPermissions("updateOwn"), (req, res) => {
    return alertController.getByChartId(req.params.chart_id)
      .then((alerts) => {
        return res.status(200).send(alerts);
      })
      .catch((error) => {
        return res.status(400).send(error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to create a new chart alert
  */
  app.post("/project/:project_id/chart/:chart_id/alert", verifyToken, checkPermissions("updateOwn"), (req, res) => {
    // check to see if the recipients are in the same team
    return teamController.getTeamMembers(req.user?.teamRole?.team_id)
      .then((teamMembers) => {
        const { recipients } = req.body;
        const teamMemberEmails = teamMembers.map((member) => member.email);
        const invalidRecipients = recipients
          .filter((recipient) => !teamMemberEmails.includes(recipient));
        if (invalidRecipients.length > 0) {
          return new Promise((resolve, reject) => reject(new Error("Invalid recipients")));
        }

        return alertController.create(req.body);
      })
      .then((alert) => {
        return res.status(200).send(alert);
      })
      .catch((error) => {
        return res.status(400).send((error && error.message) || error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to update a chart alert
  */
  app.put("/project/:project_id/chart/:chart_id/alert/:alert_id", verifyToken, checkPermissions("updateOwn"), (req, res) => {
    // check to see if the recipients are in the same team
    return teamController.getTeamMembers(req.user?.teamRole?.team_id)
      .then((teamMembers) => {
        const { recipients } = req.body;
        const teamMemberEmails = teamMembers.map((member) => member.email);
        const invalidRecipients = recipients
          .filter((recipient) => !teamMemberEmails.includes(recipient));
        if (invalidRecipients.length > 0) {
          return new Promise((resolve, reject) => reject(new Error("Invalid recipients")));
        }

        return alertController.update(req.params.alert_id, req.body);
      })
      .then((alert) => {
        return res.status(200).send(alert);
      })
      .catch((error) => {
        return res.status(400).send((error && error.message) || error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to delete a chart alert
  */
  app.delete("/project/:project_id/chart/:chart_id/alert/:alert_id", verifyToken, checkPermissions("updateOwn"), (req, res) => {
    return alertController.remove(req.params.alert_id)
      .then(() => {
        return res.status(200).send({ message: "Alert deleted" });
      })
      .catch((error) => {
        return res.status(400).send((error && error.message) || error);
      });
  });
  // --------------------------------------------------------

  /*
  ** Route to create ChartDatasetConfig
  */
  app.post("/project/:project_id/chart/:chart_id/chart-dataset-config",
    verifyToken,
    checkPermissions("updateOwn"),
    async (req, res) => {
      try {
        const cdc = await chartController.createChartDatasetConfig(req.params.chart_id, req.body, mutationContext(req));

        return res.status(200).send(cdc);
      } catch (error) {
        return res.status(error.statusCode || 400).send({ message: error.message || "The chart could not be saved. Try again." });
      }
    });
  // --------------------------------------------------------

  /*
  ** Route to update ChartDatasetConfigs
  */
  app.put("/project/:project_id/chart/:chart_id/chart-dataset-config/:cdc_id",
    verifyToken,
    checkPermissions("updateOwn"),
    async (req, res) => {
      try {
        const cdc = await chartController.updateChartDatasetConfig(req.params.cdc_id, req.body, mutationContext(req));

        return res.status(200).send(cdc);
      } catch (error) {
        return res.status(error.statusCode || 400).send({ message: error.message || "The chart could not be saved. Try again." });
      }
    });
  // --------------------------------------------------------

  /*
  ** Route to delete ChartDatasetConfigs
  */
  app.delete("/project/:project_id/chart/:chart_id/chart-dataset-config/:cdc_id",
    verifyToken,
    checkPermissions("updateOwn"),
    async (req, res) => {
      try {
        const saved = await chartController.deleteChartDatasetConfig(req.params.cdc_id, mutationContext(req));

        return res.status(200).send({ removed: true, configurationVersion: saved.configurationVersion });
      } catch (error) {
        return res.status(error.statusCode || 400).send({ message: error.message || "The chart could not be saved. Try again." });
      }
    });
  // --------------------------------------------------------

  return (req, res, next) => {
    next();
  };
};