mirror of
https://github.com/hanzoai/dataroom.git
synced 2026-10-30 18:41:07 +00:00
328 lines
8.6 KiB
TypeScript
328 lines
8.6 KiB
TypeScript
import { get } from "@vercel/edge-config";
|
|
import { parsePageId } from "notion-utils";
|
|
|
|
import { DocumentData } from "@/lib/documents/create-document";
|
|
import { isTrustedTeam } from "@/lib/edge-config/trusted-teams";
|
|
import { copyFileToBucketServer } from "@/lib/files/copy-file-to-bucket-server";
|
|
import notion from "@/lib/notion";
|
|
import { getNotionPageIdFromSlug } from "@/lib/notion/utils";
|
|
import prisma from "@/lib/prisma";
|
|
import {
|
|
convertCadToPdfTask,
|
|
convertFilesToPdfTask,
|
|
convertKeynoteToPdfTask,
|
|
} from "@/lib/trigger/convert-files";
|
|
import { processVideo } from "@/lib/trigger/optimize-video-files";
|
|
import { convertPdfToImageRoute } from "@/lib/trigger/pdf-to-image-route";
|
|
import { getExtension, log } from "@/lib/utils";
|
|
import { conversionQueue } from "@/lib/utils/trigger-utils";
|
|
import { sendDocumentCreatedWebhook } from "@/lib/webhook/triggers/document-created";
|
|
import { sendLinkCreatedWebhook } from "@/lib/webhook/triggers/link-created";
|
|
|
|
type ProcessDocumentParams = {
|
|
documentData: DocumentData;
|
|
teamId: string;
|
|
teamPlan: string;
|
|
userId?: string;
|
|
folderPathName?: string;
|
|
createLink?: boolean;
|
|
isExternalUpload?: boolean;
|
|
};
|
|
|
|
export const processDocument = async ({
|
|
documentData,
|
|
teamId,
|
|
teamPlan,
|
|
userId,
|
|
folderPathName,
|
|
createLink = false,
|
|
isExternalUpload = false,
|
|
}: ProcessDocumentParams) => {
|
|
const {
|
|
name,
|
|
key,
|
|
storageType,
|
|
contentType,
|
|
supportedFileType,
|
|
fileSize,
|
|
numPages,
|
|
enableExcelAdvancedMode,
|
|
} = documentData;
|
|
|
|
// Get passed type property or alternatively, the file extension and save it as the type
|
|
const type = supportedFileType || getExtension(name);
|
|
|
|
// Check whether the Notion page is publically accessible or not
|
|
if (type === "notion") {
|
|
try {
|
|
let pageId = parsePageId(key, { uuid: false });
|
|
|
|
// If parsePageId fails, try to get page ID from slug
|
|
if (!pageId) {
|
|
try {
|
|
const pageIdFromSlug = await getNotionPageIdFromSlug(key);
|
|
pageId = pageIdFromSlug || undefined;
|
|
} catch (slugError) {
|
|
throw new Error("Unable to extract page ID from Notion URL");
|
|
}
|
|
}
|
|
|
|
// if the page isn't accessible then end the process here.
|
|
if (!pageId) {
|
|
throw new Error("Notion page not found");
|
|
}
|
|
await notion.getPage(pageId);
|
|
} catch (error) {
|
|
throw new Error("This Notion page isn't publically available.");
|
|
}
|
|
}
|
|
|
|
// For link type, validate URL format
|
|
if (type === "link") {
|
|
try {
|
|
new URL(key);
|
|
|
|
// Skip keyword check for trusted teams
|
|
const trusted = await isTrustedTeam(teamId);
|
|
if (!trusted) {
|
|
const keywords = await get("keywords");
|
|
if (Array.isArray(keywords) && keywords.length > 0) {
|
|
const matchedKeyword = keywords.find(
|
|
(keyword) =>
|
|
typeof keyword === "string" &&
|
|
key.toLowerCase().includes(keyword.toLowerCase()),
|
|
);
|
|
|
|
if (matchedKeyword) {
|
|
log({
|
|
message: `Link document creation blocked: ${matchedKeyword} \n\n \`Metadata: {teamId: ${teamId}, url: ${key}}\``,
|
|
type: "error",
|
|
mention: true,
|
|
});
|
|
throw new Error("This URL is not allowed");
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
throw new Error("Invalid URL format for link document.");
|
|
}
|
|
}
|
|
|
|
const folder = await prisma.folder.findUnique({
|
|
where: {
|
|
teamId_path: {
|
|
teamId,
|
|
path: "/" + folderPathName,
|
|
},
|
|
},
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
// determine if the document is download only
|
|
const isDownloadOnly =
|
|
type === "zip" ||
|
|
type === "map" ||
|
|
type === "email" ||
|
|
contentType === "text/tab-separated-values";
|
|
|
|
// Save data to the database
|
|
const document = await prisma.document.create({
|
|
data: {
|
|
name: name,
|
|
numPages: numPages,
|
|
file: key,
|
|
originalFile: key,
|
|
contentType: contentType,
|
|
type: type,
|
|
storageType,
|
|
ownerId: userId,
|
|
teamId: teamId,
|
|
advancedExcelEnabled: enableExcelAdvancedMode,
|
|
downloadOnly: isDownloadOnly,
|
|
...(createLink && {
|
|
links: {
|
|
create: {
|
|
teamId,
|
|
ownerId: userId,
|
|
},
|
|
},
|
|
}),
|
|
versions: {
|
|
create: {
|
|
file: key,
|
|
originalFile: key,
|
|
contentType: contentType,
|
|
type: type,
|
|
storageType,
|
|
numPages: numPages,
|
|
isPrimary: true,
|
|
versionNumber: 1,
|
|
fileSize: fileSize,
|
|
},
|
|
},
|
|
folderId: folder?.id ?? null,
|
|
isExternalUpload,
|
|
},
|
|
include: {
|
|
links: true,
|
|
versions: true,
|
|
},
|
|
});
|
|
|
|
// Trigger appropriate conversion tasks based on document type
|
|
// Check if it's a Keynote file (slides type with Keynote content type)
|
|
if (
|
|
type === "slides" &&
|
|
(contentType === "application/vnd.apple.keynote" ||
|
|
contentType === "application/x-iwork-keynote-sffkey")
|
|
) {
|
|
await convertKeynoteToPdfTask.trigger(
|
|
{
|
|
documentId: document.id,
|
|
documentVersionId: document.versions[0].id,
|
|
teamId,
|
|
},
|
|
{
|
|
idempotencyKey: `${teamId}-${document.versions[0].id}-keynote`,
|
|
tags: [
|
|
`team_${teamId}`,
|
|
`document_${document.id}`,
|
|
`version:${document.versions[0].id}`,
|
|
],
|
|
queue: conversionQueue(teamPlan),
|
|
concurrencyKey: teamId,
|
|
},
|
|
);
|
|
} else if (type === "docs" || type === "slides") {
|
|
await convertFilesToPdfTask.trigger(
|
|
{
|
|
documentId: document.id,
|
|
documentVersionId: document.versions[0].id,
|
|
teamId,
|
|
},
|
|
{
|
|
idempotencyKey: `${teamId}-${document.versions[0].id}-docs`,
|
|
tags: [
|
|
`team_${teamId}`,
|
|
`document_${document.id}`,
|
|
`version:${document.versions[0].id}`,
|
|
],
|
|
queue: conversionQueue(teamPlan),
|
|
concurrencyKey: teamId,
|
|
},
|
|
);
|
|
}
|
|
|
|
if (type === "cad") {
|
|
await convertCadToPdfTask.trigger(
|
|
{
|
|
documentId: document.id,
|
|
documentVersionId: document.versions[0].id,
|
|
teamId,
|
|
},
|
|
{
|
|
idempotencyKey: `${teamId}-${document.versions[0].id}-cad`,
|
|
tags: [
|
|
`team_${teamId}`,
|
|
`document_${document.id}`,
|
|
`version:${document.versions[0].id}`,
|
|
],
|
|
queue: conversionQueue(teamPlan),
|
|
concurrencyKey: teamId,
|
|
},
|
|
);
|
|
}
|
|
|
|
if (
|
|
type === "video" &&
|
|
contentType !== "video/mp4" &&
|
|
contentType?.startsWith("video/")
|
|
) {
|
|
await processVideo.trigger(
|
|
{
|
|
videoUrl: key,
|
|
teamId,
|
|
docId: key.split("/")[1], // Extract doc_xxxx from teamId/doc_xxxx/filename
|
|
documentVersionId: document.versions[0].id,
|
|
fileSize: fileSize || 0,
|
|
},
|
|
{
|
|
idempotencyKey: `${teamId}-${document.versions[0].id}`,
|
|
tags: [
|
|
`team_${teamId}`,
|
|
`document_${document.id}`,
|
|
`version:${document.versions[0].id}`,
|
|
],
|
|
queue: conversionQueue(teamPlan),
|
|
concurrencyKey: teamId,
|
|
},
|
|
);
|
|
}
|
|
|
|
// skip triggering convert-pdf-to-image job for "notion" / "excel" documents
|
|
if (type === "pdf") {
|
|
await convertPdfToImageRoute.trigger(
|
|
{
|
|
documentId: document.id,
|
|
documentVersionId: document.versions[0].id,
|
|
teamId,
|
|
},
|
|
{
|
|
idempotencyKey: `${teamId}-${document.versions[0].id}`,
|
|
tags: [
|
|
`team_${teamId}`,
|
|
`document_${document.id}`,
|
|
`version:${document.versions[0].id}`,
|
|
],
|
|
queue: conversionQueue(teamPlan),
|
|
concurrencyKey: teamId,
|
|
},
|
|
);
|
|
}
|
|
|
|
if (type === "sheet" && enableExcelAdvancedMode) {
|
|
await copyFileToBucketServer({
|
|
filePath: document.versions[0].file,
|
|
storageType: document.versions[0].storageType,
|
|
teamId,
|
|
});
|
|
|
|
await prisma.documentVersion.update({
|
|
where: { id: document.versions[0].id },
|
|
data: { numPages: 1 },
|
|
});
|
|
|
|
try {
|
|
await fetch(
|
|
`${process.env.NEXTAUTH_URL}/api/revalidate?secret=${process.env.REVALIDATE_TOKEN}&documentId=${document.id}`,
|
|
);
|
|
} catch (error) {
|
|
console.error("Failed to revalidate document:", error);
|
|
// The document is still updated, so we can continue without throwing
|
|
}
|
|
}
|
|
|
|
// Send webhooks
|
|
await Promise.all([
|
|
!isExternalUpload &&
|
|
sendDocumentCreatedWebhook({
|
|
teamId,
|
|
data: {
|
|
document_id: document.id,
|
|
},
|
|
}),
|
|
createLink &&
|
|
sendLinkCreatedWebhook({
|
|
teamId,
|
|
data: {
|
|
document_id: document.id,
|
|
link_id: document.links[0].id,
|
|
},
|
|
}),
|
|
]);
|
|
|
|
return document;
|
|
};
|