98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
import type { App_RoutingForms_Form } from "@prisma/client";
|
|
import type { NextApiRequest, NextApiResponse } from "next";
|
|
|
|
import prisma from "@calcom/prisma";
|
|
|
|
import { getSerializableForm } from "../../lib/getSerializableForm";
|
|
import type { Response, SerializableForm } from "../../types/types";
|
|
|
|
function escapeCsvText(str: string) {
|
|
return str.replace(/,/, "%2C");
|
|
}
|
|
async function* getResponses(
|
|
formId: string,
|
|
headerFields: NonNullable<SerializableForm<App_RoutingForms_Form>["fields"]>
|
|
) {
|
|
let responses;
|
|
let skip = 0;
|
|
// Keep it small enough to be in Vercel limits of Serverless Function in terms of memory.
|
|
// To avoid limit in terms of execution time there is an RFC https://linear.app/calcom/issue/CAL-204/rfc-routing-form-improved-csv-exports
|
|
const take = 100;
|
|
while (
|
|
(responses = await prisma.app_RoutingForms_FormResponse.findMany({
|
|
where: {
|
|
formId,
|
|
},
|
|
take: take,
|
|
skip: skip,
|
|
})) &&
|
|
responses.length
|
|
) {
|
|
const csv: string[] = [];
|
|
responses.forEach((response) => {
|
|
const fieldResponses = response.response as Response;
|
|
const csvCells: string[] = [];
|
|
headerFields.forEach((headerField) => {
|
|
const fieldResponse = fieldResponses[headerField.id];
|
|
const value = fieldResponse?.value || "";
|
|
let serializedValue = "";
|
|
if (value instanceof Array) {
|
|
serializedValue = value.map((val) => escapeCsvText(val)).join(" | ");
|
|
} else {
|
|
serializedValue = escapeCsvText(value);
|
|
}
|
|
csvCells.push(serializedValue);
|
|
});
|
|
csvCells.push(response.createdAt.toISOString());
|
|
csv.push(csvCells.join(","));
|
|
});
|
|
skip += take;
|
|
yield csv.join("\n");
|
|
}
|
|
return "";
|
|
}
|
|
|
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
const { args } = req.query;
|
|
if (!args) {
|
|
throw new Error("args must be set");
|
|
}
|
|
const formId = args[2];
|
|
if (!formId) {
|
|
throw new Error("formId must be provided");
|
|
}
|
|
|
|
const form = await prisma.app_RoutingForms_Form.findFirst({
|
|
where: {
|
|
id: formId,
|
|
},
|
|
});
|
|
|
|
if (!form) {
|
|
throw new Error("Form not found");
|
|
}
|
|
const serializableForm = await getSerializableForm(form, true);
|
|
res.setHeader("Content-Type", "text/csv; charset=UTF-8");
|
|
res.setHeader(
|
|
"Content-Disposition",
|
|
`attachment; filename="${serializableForm.name}-${serializableForm.id}.csv"`
|
|
);
|
|
res.setHeader("Transfer-Encoding", "chunked");
|
|
const headerFields = serializableForm.fields || [];
|
|
const csvIterator = getResponses(formId, headerFields);
|
|
|
|
// Make Header
|
|
res.write(
|
|
headerFields
|
|
.map((field) => `${field.label}${field.deleted ? "(Deleted)" : ""}`)
|
|
.concat(["Submission Time"])
|
|
.join(",") + "\n"
|
|
);
|
|
|
|
for await (const partialCsv of csvIterator) {
|
|
res.write(partialCsv);
|
|
res.write("\n");
|
|
}
|
|
res.end();
|
|
}
|