mirror of
https://github.com/hanzoai/dataroom.git
synced 2026-10-30 18:41:07 +00:00
808 lines
22 KiB
TypeScript
808 lines
22 KiB
TypeScript
import {
|
|
Brand,
|
|
DataroomBrand,
|
|
ItemType,
|
|
LinkAudienceType,
|
|
LinkType,
|
|
PermissionGroupAccessControls,
|
|
Prisma,
|
|
ViewerGroupAccessControls,
|
|
} from "@prisma/client";
|
|
|
|
import prisma from "@/lib/prisma";
|
|
import { sortItemsByIndexAndName } from "@/lib/utils/sort-items-by-index-name";
|
|
|
|
// ============================================================================
|
|
// Types
|
|
// ============================================================================
|
|
|
|
type LinkFetchStatus = "ok" | "not_found" | "archived" | "deleted" | "free";
|
|
|
|
export type LinkFetchResult =
|
|
| {
|
|
status: "ok";
|
|
linkType: LinkType;
|
|
link: any;
|
|
brand: Partial<Brand> | Partial<DataroomBrand> | null;
|
|
linkId?: string;
|
|
}
|
|
| {
|
|
status: Exclude<LinkFetchStatus, "ok">;
|
|
};
|
|
|
|
// Common select object for link queries
|
|
const linkSelect = {
|
|
id: true,
|
|
expiresAt: true,
|
|
emailProtected: true,
|
|
emailAuthenticated: true,
|
|
allowDownload: true,
|
|
enableFeedback: true,
|
|
enableScreenshotProtection: true,
|
|
password: true,
|
|
isArchived: true,
|
|
deletedAt: true,
|
|
enableIndexFile: true,
|
|
enableCustomMetatag: true,
|
|
metaTitle: true,
|
|
metaDescription: true,
|
|
metaImage: true,
|
|
metaFavicon: true,
|
|
welcomeMessage: true,
|
|
enableQuestion: true,
|
|
linkType: true,
|
|
feedback: {
|
|
select: {
|
|
id: true,
|
|
data: true,
|
|
},
|
|
},
|
|
enableAgreement: true,
|
|
agreement: true,
|
|
showBanner: true,
|
|
enableWatermark: true,
|
|
watermarkConfig: true,
|
|
groupId: true,
|
|
permissionGroupId: true,
|
|
audienceType: true,
|
|
dataroomId: true,
|
|
teamId: true,
|
|
team: {
|
|
select: {
|
|
plan: true,
|
|
globalBlockList: true,
|
|
},
|
|
},
|
|
customFields: {
|
|
select: {
|
|
id: true,
|
|
type: true,
|
|
identifier: true,
|
|
label: true,
|
|
placeholder: true,
|
|
required: true,
|
|
disabled: true,
|
|
orderIndex: true,
|
|
},
|
|
orderBy: {
|
|
orderIndex: "asc" as const,
|
|
},
|
|
},
|
|
} satisfies Prisma.LinkSelect;
|
|
|
|
// Type for the link record returned by the common select query
|
|
type LinkRecord = Prisma.LinkGetPayload<{ select: typeof linkSelect }>;
|
|
|
|
// ============================================================================
|
|
// Internal Helpers
|
|
// ============================================================================
|
|
|
|
// Helper function to get all parent folder IDs for given folder IDs
|
|
async function getAllParentFolderIds(
|
|
folderIds: string[],
|
|
dataroomId: string,
|
|
): Promise<string[]> {
|
|
if (folderIds.length === 0) return [];
|
|
|
|
const allRequiredFolderIds = new Set(folderIds);
|
|
|
|
// Get all folders in the dataroom to build the hierarchy
|
|
const allFolders = await prisma.dataroomFolder.findMany({
|
|
where: { dataroomId },
|
|
select: { id: true, parentId: true },
|
|
});
|
|
|
|
// Use Map for O(1) parent lookup: folderId -> parentId
|
|
// This is more efficient than Set because we need key-value relationship for traversal
|
|
const folderMap = new Map(
|
|
allFolders.map((folder) => [folder.id, folder.parentId]),
|
|
);
|
|
|
|
// For each accessible folder, traverse up to find all parent folders
|
|
for (const folderId of folderIds) {
|
|
let currentId: string | null = folderId;
|
|
while (currentId) {
|
|
allRequiredFolderIds.add(currentId);
|
|
currentId = folderMap.get(currentId) || null;
|
|
}
|
|
}
|
|
|
|
return Array.from(allRequiredFolderIds);
|
|
}
|
|
|
|
// ============================================================================
|
|
// Data Fetchers (used by both API routes and getStaticProps)
|
|
// ============================================================================
|
|
|
|
export async function fetchDataroomLinkData({
|
|
linkId,
|
|
dataroomId,
|
|
teamId,
|
|
groupId,
|
|
permissionGroupId,
|
|
}: {
|
|
linkId: string;
|
|
dataroomId: string | null;
|
|
teamId: string;
|
|
groupId?: string;
|
|
permissionGroupId?: string;
|
|
}) {
|
|
let groupPermissions:
|
|
| ViewerGroupAccessControls[]
|
|
| PermissionGroupAccessControls[] = [];
|
|
let documentIds: string[] = [];
|
|
let folderIds: string[] = [];
|
|
let allRequiredFolderIds: string[] = [];
|
|
|
|
const effectiveGroupId = groupId || permissionGroupId;
|
|
|
|
if (effectiveGroupId) {
|
|
// Check if this is a ViewerGroup (legacy) or PermissionGroup
|
|
// First try to find ViewerGroup permissions (for backwards compatibility)
|
|
if (groupId) {
|
|
// This is a ViewerGroup (legacy behavior)
|
|
groupPermissions = await prisma.viewerGroupAccessControls.findMany({
|
|
where: {
|
|
groupId: groupId,
|
|
OR: [{ canView: true }, { canDownload: true }],
|
|
},
|
|
});
|
|
} else if (permissionGroupId) {
|
|
// This is a PermissionGroup (new behavior)
|
|
groupPermissions = await prisma.permissionGroupAccessControls.findMany({
|
|
where: {
|
|
groupId: permissionGroupId,
|
|
OR: [{ canView: true }, { canDownload: true }],
|
|
},
|
|
});
|
|
}
|
|
|
|
documentIds = groupPermissions
|
|
.filter(
|
|
(permission) => permission.itemType === ItemType.DATAROOM_DOCUMENT,
|
|
)
|
|
.map((permission) => permission.itemId);
|
|
folderIds = groupPermissions
|
|
.filter((permission) => permission.itemType === ItemType.DATAROOM_FOLDER)
|
|
.map((permission) => permission.itemId);
|
|
|
|
// Include parent folders if we have group permissions and they're actually being applied
|
|
// This ensures that if a group has access to a subfolder, all parent folders
|
|
// are also included to maintain proper hierarchy (even without explicit permissions)
|
|
allRequiredFolderIds = folderIds;
|
|
if (dataroomId && folderIds.length > 0) {
|
|
allRequiredFolderIds = await getAllParentFolderIds(folderIds, dataroomId);
|
|
}
|
|
}
|
|
|
|
const linkData = await prisma.link.findUnique({
|
|
where: { id: linkId, teamId },
|
|
select: {
|
|
dataroom: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
description: true,
|
|
teamId: true,
|
|
allowBulkDownload: true,
|
|
showLastUpdated: true,
|
|
introductionEnabled: true,
|
|
introductionContent: true,
|
|
createdAt: true,
|
|
documents: {
|
|
where:
|
|
groupPermissions.length > 0 || effectiveGroupId
|
|
? { id: { in: documentIds } }
|
|
: undefined,
|
|
select: {
|
|
id: true,
|
|
folderId: true,
|
|
updatedAt: true,
|
|
orderIndex: true,
|
|
hierarchicalIndex: true,
|
|
document: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
advancedExcelEnabled: true,
|
|
downloadOnly: true,
|
|
versions: {
|
|
where: { isPrimary: true },
|
|
select: {
|
|
id: true,
|
|
versionNumber: true,
|
|
type: true,
|
|
hasPages: true,
|
|
file: true,
|
|
isVertical: true,
|
|
updatedAt: true,
|
|
},
|
|
take: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
orderBy: [
|
|
{ orderIndex: "asc" },
|
|
{
|
|
document: { name: "asc" },
|
|
},
|
|
],
|
|
},
|
|
folders: {
|
|
where:
|
|
groupPermissions.length > 0 || effectiveGroupId
|
|
? { id: { in: allRequiredFolderIds } }
|
|
: undefined,
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
path: true,
|
|
parentId: true,
|
|
dataroomId: true,
|
|
orderIndex: true,
|
|
hierarchicalIndex: true,
|
|
icon: true,
|
|
color: true,
|
|
createdAt: true,
|
|
updatedAt: true,
|
|
},
|
|
orderBy: [{ orderIndex: "asc" }, { name: "asc" }],
|
|
},
|
|
},
|
|
},
|
|
group: {
|
|
select: {
|
|
accessControls: true,
|
|
},
|
|
},
|
|
permissionGroup: {
|
|
select: {
|
|
accessControls: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!linkData?.dataroom) {
|
|
throw new Error("Dataroom not found");
|
|
}
|
|
|
|
// Sort documents by index or name
|
|
linkData.dataroom.documents = sortItemsByIndexAndName(
|
|
linkData.dataroom.documents,
|
|
);
|
|
|
|
const dataroomBrand = await prisma.dataroomBrand.findFirst({
|
|
where: { dataroomId: linkData.dataroom.id },
|
|
select: {
|
|
logo: true,
|
|
banner: true,
|
|
brandColor: true,
|
|
accentColor: true,
|
|
applyAccentColorToDataroomView: true,
|
|
welcomeMessage: true,
|
|
},
|
|
});
|
|
|
|
const teamBrand = await prisma.brand.findFirst({
|
|
where: { teamId: linkData.dataroom.teamId },
|
|
select: {
|
|
logo: true,
|
|
banner: true,
|
|
brandColor: true,
|
|
accentColor: true,
|
|
applyAccentColorToDataroomView: true,
|
|
welcomeMessage: true,
|
|
},
|
|
});
|
|
|
|
const brand = {
|
|
logo: dataroomBrand?.logo || teamBrand?.logo,
|
|
banner: dataroomBrand?.banner || teamBrand?.banner || null,
|
|
brandColor: dataroomBrand?.brandColor || teamBrand?.brandColor,
|
|
accentColor: dataroomBrand?.accentColor || teamBrand?.accentColor,
|
|
applyAccentColorToDataroomView:
|
|
dataroomBrand?.applyAccentColorToDataroomView ??
|
|
teamBrand?.applyAccentColorToDataroomView ??
|
|
false,
|
|
welcomeMessage: dataroomBrand?.welcomeMessage || teamBrand?.welcomeMessage,
|
|
};
|
|
|
|
// Extract access controls from either ViewerGroup or PermissionGroup
|
|
const accessControls =
|
|
linkData.group?.accessControls ||
|
|
linkData.permissionGroup?.accessControls ||
|
|
[];
|
|
|
|
return { linkData, brand, accessControls };
|
|
}
|
|
|
|
export async function fetchDataroomDocumentLinkData({
|
|
linkId,
|
|
teamId,
|
|
dataroomDocumentId,
|
|
groupId,
|
|
permissionGroupId,
|
|
}: {
|
|
linkId: string;
|
|
teamId: string;
|
|
dataroomDocumentId: string;
|
|
groupId?: string;
|
|
permissionGroupId?: string;
|
|
}) {
|
|
let groupPermissions:
|
|
| ViewerGroupAccessControls[]
|
|
| PermissionGroupAccessControls[] = [];
|
|
|
|
const effectiveGroupId = groupId || permissionGroupId;
|
|
|
|
if (effectiveGroupId) {
|
|
let hasAccess = false;
|
|
|
|
if (groupId) {
|
|
// This is a ViewerGroup (legacy behavior)
|
|
groupPermissions = await prisma.viewerGroupAccessControls.findMany({
|
|
where: {
|
|
groupId: groupId,
|
|
itemId: dataroomDocumentId,
|
|
itemType: ItemType.DATAROOM_DOCUMENT,
|
|
OR: [{ canView: true }, { canDownload: true }],
|
|
},
|
|
});
|
|
hasAccess = groupPermissions.length > 0;
|
|
} else if (permissionGroupId) {
|
|
// This is a PermissionGroup (new behavior)
|
|
groupPermissions = await prisma.permissionGroupAccessControls.findMany({
|
|
where: {
|
|
groupId: permissionGroupId,
|
|
itemId: dataroomDocumentId,
|
|
itemType: ItemType.DATAROOM_DOCUMENT,
|
|
OR: [{ canView: true }, { canDownload: true }],
|
|
},
|
|
});
|
|
hasAccess = groupPermissions.length > 0;
|
|
}
|
|
|
|
// if it's a group/permission link, we need to check if the document is accessible
|
|
if (!hasAccess) {
|
|
throw new Error("Document not found in group");
|
|
}
|
|
}
|
|
|
|
const linkData = await prisma.link.findUnique({
|
|
where: { id: linkId, teamId, linkType: "DATAROOM_LINK", deletedAt: null },
|
|
select: {
|
|
dataroom: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
description: true,
|
|
teamId: true,
|
|
allowBulkDownload: true,
|
|
showLastUpdated: true,
|
|
documents: {
|
|
where: { id: dataroomDocumentId },
|
|
select: {
|
|
id: true,
|
|
updatedAt: true,
|
|
orderIndex: true,
|
|
hierarchicalIndex: true,
|
|
document: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
advancedExcelEnabled: true,
|
|
downloadOnly: true,
|
|
versions: {
|
|
where: { isPrimary: true },
|
|
select: {
|
|
id: true,
|
|
versionNumber: true,
|
|
type: true,
|
|
hasPages: true,
|
|
file: true,
|
|
isVertical: true,
|
|
},
|
|
take: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!linkData?.dataroom) {
|
|
throw new Error("Dataroom not found");
|
|
}
|
|
|
|
const dataroomBrand = await prisma.dataroomBrand.findFirst({
|
|
where: { dataroomId: linkData.dataroom.id },
|
|
select: {
|
|
logo: true,
|
|
banner: true,
|
|
brandColor: true,
|
|
accentColor: true,
|
|
applyAccentColorToDataroomView: true,
|
|
welcomeMessage: true,
|
|
},
|
|
});
|
|
|
|
const teamBrand = await prisma.brand.findFirst({
|
|
where: { teamId: linkData.dataroom.teamId },
|
|
select: {
|
|
logo: true,
|
|
banner: true,
|
|
brandColor: true,
|
|
accentColor: true,
|
|
applyAccentColorToDataroomView: true,
|
|
welcomeMessage: true,
|
|
},
|
|
});
|
|
|
|
const brand = {
|
|
logo: dataroomBrand?.logo || teamBrand?.logo,
|
|
banner: dataroomBrand?.banner || teamBrand?.banner || null,
|
|
brandColor: dataroomBrand?.brandColor || teamBrand?.brandColor,
|
|
accentColor: dataroomBrand?.accentColor || teamBrand?.accentColor,
|
|
applyAccentColorToDataroomView:
|
|
dataroomBrand?.applyAccentColorToDataroomView ??
|
|
teamBrand?.applyAccentColorToDataroomView ??
|
|
false,
|
|
welcomeMessage: dataroomBrand?.welcomeMessage || teamBrand?.welcomeMessage,
|
|
};
|
|
|
|
return { linkData, brand };
|
|
}
|
|
|
|
export async function fetchDocumentLinkData({
|
|
linkId,
|
|
teamId,
|
|
}: {
|
|
linkId: string;
|
|
teamId: string;
|
|
}) {
|
|
const linkData = await prisma.link.findUnique({
|
|
where: { id: linkId, teamId, deletedAt: null },
|
|
select: {
|
|
document: {
|
|
select: {
|
|
id: true,
|
|
name: true,
|
|
advancedExcelEnabled: true,
|
|
downloadOnly: true,
|
|
teamId: true,
|
|
ownerId: true,
|
|
team: {
|
|
select: { plan: true },
|
|
},
|
|
versions: {
|
|
where: { isPrimary: true },
|
|
select: {
|
|
id: true,
|
|
versionNumber: true,
|
|
type: true,
|
|
hasPages: true,
|
|
file: true,
|
|
isVertical: true,
|
|
},
|
|
take: 1,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!linkData?.document) {
|
|
throw new Error("Document not found");
|
|
}
|
|
|
|
const brand = await prisma.brand.findFirst({
|
|
where: { teamId: linkData.document.teamId },
|
|
select: {
|
|
logo: true,
|
|
brandColor: true,
|
|
accentColor: true,
|
|
welcomeMessage: true,
|
|
},
|
|
});
|
|
|
|
return { linkData, brand };
|
|
}
|
|
|
|
// ============================================================================
|
|
// Unified Link Data Fetcher for getStaticProps
|
|
// Avoids internal HTTP fetch which can be blocked by Vercel edge (403 errors)
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Core function to process link data after fetching the link record.
|
|
* Handles all link types: DOCUMENT_LINK, DATAROOM_LINK, WORKFLOW_LINK
|
|
*/
|
|
async function processLinkData(
|
|
link: LinkRecord,
|
|
options: {
|
|
dataroomDocumentId?: string;
|
|
isCustomDomain?: boolean;
|
|
} = {},
|
|
): Promise<LinkFetchResult> {
|
|
const { dataroomDocumentId, isCustomDomain } = options;
|
|
const teamPlan = link.team?.plan || "free";
|
|
const linkType = link.linkType;
|
|
|
|
// For custom domains, free plan is not allowed
|
|
if (isCustomDomain && teamPlan.includes("free")) {
|
|
return { status: "free" };
|
|
}
|
|
|
|
// Handle WORKFLOW_LINK
|
|
if (linkType === "WORKFLOW_LINK") {
|
|
let brand: Partial<Brand> | null = null;
|
|
if (link.teamId) {
|
|
const teamBrand = await prisma.brand.findUnique({
|
|
where: { teamId: link.teamId },
|
|
select: {
|
|
logo: true,
|
|
brandColor: true,
|
|
accentColor: true,
|
|
},
|
|
});
|
|
brand = teamBrand;
|
|
}
|
|
|
|
// For workflow links, return the link with minimal processing
|
|
// Remove team object (contains plan, globalBlockList) but keep teamId for feature flags
|
|
const sanitizedLink = {
|
|
...link,
|
|
team: undefined,
|
|
deletedAt: undefined,
|
|
};
|
|
|
|
// Serialize to convert Date objects to strings (required for Next.js getStaticProps)
|
|
const serializedLink = JSON.parse(JSON.stringify(sanitizedLink));
|
|
const serializedBrand = brand ? JSON.parse(JSON.stringify(brand)) : null;
|
|
|
|
return {
|
|
status: "ok",
|
|
linkType,
|
|
brand: serializedBrand,
|
|
linkId: link.id,
|
|
link: serializedLink,
|
|
};
|
|
}
|
|
|
|
let brand: Partial<Brand> | Partial<DataroomBrand> | null = null;
|
|
let linkData: any;
|
|
|
|
// Handle DOCUMENT_LINK
|
|
if (linkType === "DOCUMENT_LINK") {
|
|
// Guard: teamId is required for document links
|
|
if (!link.teamId) {
|
|
return { status: "not_found" };
|
|
}
|
|
|
|
try {
|
|
const data = await fetchDocumentLinkData({
|
|
linkId: link.id,
|
|
teamId: link.teamId,
|
|
});
|
|
linkData = data.linkData;
|
|
brand = data.brand;
|
|
} catch {
|
|
return { status: "not_found" };
|
|
}
|
|
}
|
|
// Handle DATAROOM_LINK
|
|
else if (linkType === "DATAROOM_LINK") {
|
|
// Guard: teamId is required for dataroom links
|
|
if (!link.teamId) {
|
|
return { status: "not_found" };
|
|
}
|
|
|
|
if (dataroomDocumentId) {
|
|
// Fetching specific document within dataroom
|
|
try {
|
|
const data = await fetchDataroomDocumentLinkData({
|
|
linkId: link.id,
|
|
teamId: link.teamId,
|
|
dataroomDocumentId: dataroomDocumentId,
|
|
permissionGroupId: link.permissionGroupId || undefined,
|
|
...(link.audienceType === LinkAudienceType.GROUP &&
|
|
link.groupId && {
|
|
groupId: link.groupId,
|
|
}),
|
|
});
|
|
linkData = data.linkData;
|
|
brand = data.brand;
|
|
} catch {
|
|
return { status: "not_found" };
|
|
}
|
|
} else {
|
|
// Fetching full dataroom
|
|
try {
|
|
const data = await fetchDataroomLinkData({
|
|
linkId: link.id,
|
|
dataroomId: link.dataroomId,
|
|
teamId: link.teamId,
|
|
permissionGroupId: link.permissionGroupId || undefined,
|
|
...(link.audienceType === LinkAudienceType.GROUP &&
|
|
link.groupId && {
|
|
groupId: link.groupId,
|
|
}),
|
|
});
|
|
linkData = data.linkData;
|
|
brand = data.brand;
|
|
linkData.accessControls = data.accessControls;
|
|
} catch {
|
|
return { status: "not_found" };
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only include agreement if enabled (no need to expose it otherwise)
|
|
const sanitizedAgreement =
|
|
link.enableAgreement && link.agreement
|
|
? {
|
|
id: link.agreement.id,
|
|
name: link.agreement.name,
|
|
content: link.agreement.content,
|
|
contentType: link.agreement.contentType,
|
|
requireName: link.agreement.requireName,
|
|
}
|
|
: null;
|
|
|
|
// Sanitize document - keep fields needed by getStaticProps
|
|
// Note: team/teamId are used server-side for feature flags and are stripped before client props
|
|
const sanitizedDocument = linkData?.document
|
|
? {
|
|
id: linkData.document.id,
|
|
name: linkData.document.name,
|
|
teamId: linkData.document.teamId,
|
|
team: linkData.document.team, // Used server-side for plan check, stripped before client
|
|
downloadOnly: linkData.document.downloadOnly,
|
|
advancedExcelEnabled: linkData.document.advancedExcelEnabled,
|
|
versions: linkData.document.versions,
|
|
}
|
|
: undefined;
|
|
|
|
// Sanitize link for return - remove sensitive/internal data
|
|
const sanitizedLink = {
|
|
...link,
|
|
// Remove team object (contains plan, globalBlockList) but keep teamId for feature flags
|
|
team: undefined,
|
|
// Remove internal fields
|
|
deletedAt: undefined,
|
|
document: undefined,
|
|
dataroom: undefined,
|
|
password: link.password ? "protected" : null,
|
|
// Use sanitized agreement
|
|
agreement: sanitizedAgreement,
|
|
...(teamPlan === "free" && {
|
|
customFields: [],
|
|
enableAgreement: false,
|
|
enableWatermark: false,
|
|
permissionGroupId: null,
|
|
}),
|
|
};
|
|
|
|
const returnLink = {
|
|
...sanitizedLink,
|
|
...linkData,
|
|
// Override with sanitized document
|
|
document: sanitizedDocument,
|
|
// Keep dataroomId for DATAROOM_LINK types (needed for session verification and API calls)
|
|
// For DOCUMENT_LINK types, set to undefined
|
|
dataroomId:
|
|
linkType === "DATAROOM_LINK"
|
|
? link.dataroomId || linkData?.dataroom?.id
|
|
: undefined,
|
|
dataroomDocument: linkData?.dataroom?.documents?.[0] || undefined,
|
|
};
|
|
|
|
// Serialize to convert Date objects to strings (required for Next.js getStaticProps)
|
|
const serializedLink = JSON.parse(JSON.stringify(returnLink));
|
|
const serializedBrand = brand ? JSON.parse(JSON.stringify(brand)) : null;
|
|
|
|
return {
|
|
status: "ok",
|
|
linkType,
|
|
link: serializedLink,
|
|
brand: serializedBrand,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Fetch link data by linkId (for /view/[linkId] routes)
|
|
*/
|
|
export async function fetchLinkDataById({
|
|
linkId,
|
|
dataroomDocumentId,
|
|
}: {
|
|
linkId: string;
|
|
dataroomDocumentId?: string;
|
|
}): Promise<LinkFetchResult> {
|
|
const link = await prisma.link.findUnique({
|
|
where: { id: linkId },
|
|
select: linkSelect,
|
|
});
|
|
|
|
if (!link) {
|
|
return { status: "not_found" };
|
|
}
|
|
|
|
if (link.deletedAt) {
|
|
return { status: "deleted" };
|
|
}
|
|
|
|
if (link.isArchived) {
|
|
return { status: "archived" };
|
|
}
|
|
|
|
return processLinkData(link, { dataroomDocumentId, isCustomDomain: false });
|
|
}
|
|
|
|
/**
|
|
* Fetch link data by domain and slug (for /view/domains/[domain]/[slug] routes)
|
|
* Includes free plan check since custom domains require paid plan
|
|
*/
|
|
export async function fetchLinkDataByDomainSlug({
|
|
domain,
|
|
slug,
|
|
dataroomDocumentId,
|
|
}: {
|
|
domain: string;
|
|
slug: string;
|
|
dataroomDocumentId?: string;
|
|
}): Promise<LinkFetchResult> {
|
|
const link = await prisma.link.findUnique({
|
|
where: {
|
|
domainSlug_slug: {
|
|
slug: slug,
|
|
domainSlug: domain,
|
|
},
|
|
},
|
|
select: linkSelect,
|
|
});
|
|
|
|
if (!link) {
|
|
return { status: "not_found" };
|
|
}
|
|
|
|
if (link.deletedAt) {
|
|
return { status: "deleted" };
|
|
}
|
|
|
|
if (link.isArchived) {
|
|
return { status: "archived" };
|
|
}
|
|
|
|
return processLinkData(link, { dataroomDocumentId, isCustomDomain: true });
|
|
}
|
|
|
|
// Legacy export aliases for backward compatibility
|
|
export const fetchCustomDomainLinkData = fetchLinkDataByDomainSlug;
|
|
export type CustomDomainLinkResult = LinkFetchResult;
|