first commit
This commit is contained in:
27
calcom/packages/app-store/routing-forms/api/add.ts
Normal file
27
calcom/packages/app-store/routing-forms/api/add.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { AppDeclarativeHandler } from "@calcom/types/AppHandler";
|
||||
|
||||
import appConfig from "../config.json";
|
||||
|
||||
const handler: AppDeclarativeHandler = {
|
||||
appType: appConfig.type,
|
||||
variant: appConfig.variant,
|
||||
slug: appConfig.slug,
|
||||
supportsMultipleInstalls: false,
|
||||
handlerType: "add",
|
||||
createCredential: async ({ user, appType, slug, teamId }) => {
|
||||
return await prisma.credential.create({
|
||||
data: {
|
||||
type: appType,
|
||||
key: {},
|
||||
...(teamId ? { teamId } : { userId: user.id }),
|
||||
appId: slug,
|
||||
},
|
||||
});
|
||||
},
|
||||
redirect: {
|
||||
url: "/apps/routing-forms/forms",
|
||||
},
|
||||
};
|
||||
|
||||
export default handler;
|
||||
2
calcom/packages/app-store/routing-forms/api/index.ts
Normal file
2
calcom/packages/app-store/routing-forms/api/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as add } from "./add";
|
||||
export { default as responses } from "./responses/[formId]";
|
||||
@@ -0,0 +1,122 @@
|
||||
import type { App_RoutingForms_Form } from "@prisma/client";
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { getSession } from "next-auth/react";
|
||||
|
||||
import { entityPrismaWhereClause, canEditEntity } from "@calcom/lib/entityPermissionUtils";
|
||||
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 {
|
||||
// value can be a number as well for type Number field
|
||||
serializedValue = escapeCsvText(String(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 session = await getSession({ req });
|
||||
|
||||
if (!session) {
|
||||
return res.status(401).json({ message: "Unauthorized" });
|
||||
}
|
||||
|
||||
const { user } = session;
|
||||
|
||||
const form = await prisma.app_RoutingForms_Form.findFirst({
|
||||
where: {
|
||||
id: formId,
|
||||
...entityPrismaWhereClause({ userId: user.id }),
|
||||
},
|
||||
include: {
|
||||
team: {
|
||||
select: {
|
||||
members: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!form) {
|
||||
return res.status(404).json({ message: "Form not found or unauthorized" });
|
||||
}
|
||||
|
||||
if (!canEditEntity(form, user.id)) {
|
||||
return res.status(404).json({ message: "Form not found or unauthorized" });
|
||||
}
|
||||
|
||||
const serializableForm = await getSerializableForm({ form, withDeletedFields: 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();
|
||||
}
|
||||
Reference in New Issue
Block a user