mirror of
https://github.com/hanzo-apps/dataroom.git
synced 2026-11-02 22:04:20 +00:00
`waitUntil` existed because a serverless invocation is frozen the moment it responds — anything still running is killed unless the platform is told to wait. This app is a long-lived Node process on Kubernetes. Nothing freezes, and those promises were never at risk. lib/after.ts is the one call sites make. Inside the App Router it delegates to Next's own `after()`, which schedules the work against the request so it runs after the response is flushed instead of racing it. That needs App Router request context and THROWS without one, and ten of these fifteen sites are Pages Router API routes — so outside that context the promise runs detached, which on a persistent server is all waitUntil ever bought. Either path catches. An unhandled background rejection takes the process down under Node's default policy, and losing a pod because an analytics call 500'd is not a trade worth making — waitUntil swallowed those, so this is not a behaviour we are giving up. @vercel/functions now has no importers. Verified with npx tsc --noEmit: 100 errors before and after, 0 introduced, compared by file+code+message so inserted imports cannot masquerade as new ones. Co-authored-by: Hanzo Dev <[email protected]>
238 lines
6.5 KiB
TypeScript
238 lines
6.5 KiB
TypeScript
import { NextApiRequest, NextApiResponse } from "next";
|
|
|
|
import { authOptions } from "@/pages/api/auth/[...nextauth]";
|
|
import { runs } from "@trigger.dev/sdk/v3";
|
|
import { after } from "@/lib/after";
|
|
import { getServerSession } from "next-auth/next";
|
|
|
|
import { errorhandler } from "@/lib/errorHandler";
|
|
import prisma from "@/lib/prisma";
|
|
import { sendDataroomChangeNotificationTask } from "@/lib/trigger/dataroom-change-notification";
|
|
import { CustomUser } from "@/lib/types";
|
|
import { log, serializeFileSize } from "@/lib/utils";
|
|
import { sortItemsByIndexAndName } from "@/lib/utils/sort-items-by-index-name";
|
|
|
|
export const config = {
|
|
// so background work can outlive the response
|
|
supportsResponseStreaming: true,
|
|
};
|
|
|
|
export default async function handle(
|
|
req: NextApiRequest,
|
|
res: NextApiResponse,
|
|
) {
|
|
if (req.method === "GET") {
|
|
// GET /api/teams/:teamId/datarooms/:id/documents
|
|
const session = await getServerSession(req, res, authOptions);
|
|
if (!session) {
|
|
return res.status(401).end("Unauthorized");
|
|
}
|
|
|
|
const userId = (session.user as CustomUser).id;
|
|
const { teamId, id: dataroomId } = req.query as {
|
|
teamId: string;
|
|
id: string;
|
|
};
|
|
|
|
try {
|
|
// Check if the user is part of the team
|
|
const team = await prisma.team.findUnique({
|
|
where: {
|
|
id: teamId,
|
|
users: {
|
|
some: {
|
|
userId: userId,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!team) {
|
|
return res.status(401).end("Unauthorized");
|
|
}
|
|
|
|
const documents = await prisma.dataroomDocument.findMany({
|
|
where: {
|
|
dataroomId: dataroomId,
|
|
folderId: null,
|
|
},
|
|
orderBy: [
|
|
{ orderIndex: "asc" },
|
|
{
|
|
document: {
|
|
name: "asc",
|
|
},
|
|
},
|
|
],
|
|
select: {
|
|
id: true,
|
|
dataroomId: true,
|
|
folderId: true,
|
|
orderIndex: true,
|
|
hierarchicalIndex: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
document: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
type: true,
|
|
versions: {
|
|
select: { id: true, hasPages: true },
|
|
},
|
|
isExternalUpload: true,
|
|
_count: {
|
|
select: {
|
|
views: { where: { dataroomId } },
|
|
versions: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
const sortedDocuments = sortItemsByIndexAndName(documents);
|
|
|
|
return res.status(200).json(sortedDocuments);
|
|
} catch (error) {
|
|
console.error("Request error", error);
|
|
return res
|
|
.status(500)
|
|
.json({ error: "Error fetching documents from dataroom" });
|
|
}
|
|
} else if (req.method === "POST") {
|
|
// POST /api/teams/:teamId/datarooms/:id/documents
|
|
const session = await getServerSession(req, res, authOptions);
|
|
if (!session) {
|
|
res.status(401).end("Unauthorized");
|
|
return;
|
|
}
|
|
|
|
const { teamId, id: dataroomId } = req.query as {
|
|
teamId: string;
|
|
id: string;
|
|
};
|
|
|
|
const userId = (session.user as CustomUser).id;
|
|
|
|
// Assuming data is an object with `name` and `description` properties
|
|
const { documentId, folderPathName } = req.body as {
|
|
documentId: string;
|
|
folderPathName?: string;
|
|
};
|
|
|
|
try {
|
|
// Check if the user is part of the team
|
|
const team = await prisma.team.findUnique({
|
|
where: {
|
|
id: teamId,
|
|
users: {
|
|
some: {
|
|
userId: userId,
|
|
},
|
|
},
|
|
},
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
if (!team) {
|
|
return res.status(401).end("Unauthorized");
|
|
}
|
|
|
|
const folder = await prisma.dataroomFolder.findUnique({
|
|
where: {
|
|
dataroomId_path: {
|
|
dataroomId,
|
|
path: "/" + folderPathName,
|
|
},
|
|
},
|
|
select: {
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
const dataroomDocument = await prisma.dataroomDocument.create({
|
|
data: {
|
|
documentId,
|
|
dataroomId,
|
|
folderId: folder?.id,
|
|
},
|
|
include: {
|
|
document: {
|
|
include: {
|
|
versions: {
|
|
where: { isPrimary: true },
|
|
take: 1,
|
|
},
|
|
},
|
|
},
|
|
dataroom: {
|
|
select: {
|
|
teamId: true,
|
|
name: true,
|
|
enableChangeNotifications: true,
|
|
links: {
|
|
select: { id: true },
|
|
orderBy: { createdAt: "desc" },
|
|
take: 1,
|
|
},
|
|
_count: {
|
|
select: { viewerGroups: true, permissionGroups: true },
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
// Check if the team has the dataroom change notification enabled
|
|
if (dataroomDocument.dataroom.enableChangeNotifications) {
|
|
// Get all delayed and queued runs for this dataroom
|
|
const allRuns = await runs.list({
|
|
taskIdentifier: ["send-dataroom-change-notification"],
|
|
tag: [`dataroom_${dataroomId}`],
|
|
status: ["DELAYED", "QUEUED"],
|
|
period: "10m",
|
|
});
|
|
|
|
// Cancel any existing unsent notification runs for this dataroom
|
|
await Promise.all(allRuns.data.map((run) => runs.cancel(run.id)));
|
|
|
|
after(
|
|
sendDataroomChangeNotificationTask.trigger(
|
|
{
|
|
dataroomId,
|
|
dataroomDocumentId: dataroomDocument.id,
|
|
senderUserId: userId,
|
|
teamId,
|
|
},
|
|
{
|
|
idempotencyKey: `dataroom-notification-${teamId}-${dataroomId}-${dataroomDocument.id}`,
|
|
tags: [
|
|
`team_${teamId}`,
|
|
`dataroom_${dataroomId}`,
|
|
`document_${dataroomDocument.id}`,
|
|
],
|
|
delay: new Date(Date.now() + 10 * 60 * 1000), // 10 minute delay
|
|
},
|
|
),
|
|
);
|
|
}
|
|
|
|
return res.status(201).json(serializeFileSize(dataroomDocument));
|
|
} catch (error) {
|
|
log({
|
|
message: `Failed to create dataroom document. \n\n*teamId*: _${teamId}_, \n\n*dataroomId*: ${dataroomId} \n\n ${error}`,
|
|
type: "error",
|
|
});
|
|
errorhandler(error, res);
|
|
}
|
|
} else {
|
|
// We only allow GET requests
|
|
res.setHeader("Allow", ["GET", "POST"]);
|
|
return res.status(405).end(`Method ${req.method} Not Allowed`);
|
|
}
|
|
}
|