// (C) 2007-2019 GoodData Corporation import { Application, Request, Response } from "express"; import { uniqueId } from "lodash"; import * as HttpStatusCodes from "http-status-codes"; import { IExportConfig, IExportResultRequest } from "../../model/ExportReport"; import { IMockProject } from "../../model/MockProject"; import { IConfig } from "../../model/Config"; import { IError } from "../../model/Error"; type IDGenerator = (prefix?: string) => string; interface IExecutionInfo { createdAt: number; exportConfig?: IExportConfig; pollCounter: number; } export interface IExportResponse { uri: string; } const executionCache = new Map(); export function cleanOldCache(lifetime: number = 240000) { const now = Date.now(); const oldEntries: string[] = []; executionCache.forEach((entry: IExecutionInfo, key: string) => { if (entry.createdAt < now - lifetime) { oldEntries.push(key); } }); oldEntries.forEach(entry => executionCache.delete(entry)); } export function hasCachedResult(key: string): boolean { return executionCache.has(key); } function getPathToExportedFile(format: string): string { const path = require("path"); return path.join(__dirname, `../../static/dummy-export.${format}`); } function getExportResponse(executionId: string, projectId: string): IExportResponse { return { uri: `/gdc/exporter/result/${projectId}/${executionId}`, }; } function getExportContent(format: string): string { const fs = require("fs"); try { return fs.readFileSync(getPathToExportedFile(format), "utf8"); } catch (err) { return "INTERNAL_SERVER_ERROR"; } } function getExportError(message: string): IError { return { error: { trace: "", parameters: [""], requestId: "", component: "", errorClass: "GDC::Exception::", message, }, }; } function createExportResponse( projectId: string, body: IExportResultRequest, res: Response, executionId: string, ): Response { const { resultExport: { executionResult, exportConfig }, } = body; if (!executionResult) { return res.status(HttpStatusCodes.BAD_REQUEST).json(getExportError("BAD_REQUEST")); } cleanOldCache(); executionCache.set(executionId, { createdAt: Date.now(), exportConfig: exportConfig || {}, pollCounter: 0, }); return res.status(HttpStatusCodes.CREATED).send(getExportResponse(executionId, projectId)); } function maybeWaitAndCreateResult( projectId: string, req: Request, res: Response, globalPollCount: number, ): Response { const executionId = req.params.id; const { exportConfig, pollCounter } = executionCache.get(executionId); if (pollCounter < globalPollCount) { executionCache.set(executionId, { createdAt: Date.now(), exportConfig, pollCounter: pollCounter + 1, }); return res.status(HttpStatusCodes.ACCEPTED).send(getExportResponse(executionId, projectId)); } const format = exportConfig.format || "csv"; const title = exportConfig.title || "Untitled"; const contentDisposition = `attachment; filename="${title}.${format}"; filename*=UTF-8''${title}.${format}`; const contentType = format === "csv" ? "text/csv;charset=utf8" : "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; return res .status(HttpStatusCodes.OK) .set("Content-Disposition", contentDisposition) .set("Content-Type", contentType) .send(getExportContent(format)); } function downloadFile(req: Request, res: Response): void { const executionId = req.params.id; const { exportConfig } = executionCache.get(executionId); const format = exportConfig.format || "csv"; const title = exportConfig.title || "Untitled"; return res.download(getPathToExportedFile(format), `${title}.${format}`); } export const exportReport = { register(app: Application, project: IMockProject, config: IConfig, idGenerator: IDGenerator = uniqueId) { const projectId = project.project.meta.identifier; app.post(`/gdc/internal/projects/${projectId}/exportResult`, (req, res) => createExportResponse(projectId, req.body, res, idGenerator()), ); app.head(`/gdc/exporter/result/${projectId}/:id`, (req, res) => maybeWaitAndCreateResult(projectId, req, res, config.pollCount), ); app.get(`/gdc/exporter/result/${projectId}/:id`, (req, res) => downloadFile(req, res)); return app; }, };