feat: unify link and group permissions default settings

This commit is contained in:
Marc Seitz
2025-07-15 19:39:02 +02:00
parent 179e61111b
commit 47af1f2ffd
18 changed files with 524 additions and 2263 deletions

View File

@@ -15,6 +15,7 @@ import {
useSensor,
useSensors,
} from "@dnd-kit/core";
import { DefaultPermissionStrategy } from "@prisma/client";
import {
ArchiveXIcon,
FileIcon,
@@ -81,11 +82,7 @@ export function DataroomItemsList({
const { permissionGroups } = useDataroomPermissionGroups();
const { dataroom } = useDataroom();
const { isMobile } = useMediaQuery();
const {
applyDefaultPermissions,
inheritParentPermissions,
applyPermissionGroupPermissions,
} = useDataroomPermissions();
const { applyPermissions } = useDataroomPermissions();
const [uploads, setUploads] = useState<UploadState[]>([]);
const [rejectedFiles, setRejectedFiles] = useState<RejectedFile[]>([]);
@@ -558,70 +555,35 @@ export function DataroomItemsList({
}[],
) => {
// Check if there are any groups to apply permissions to
const hasViewerGroups = viewerGroups && viewerGroups.length > 0;
const hasPermissionGroups = permissionGroups && permissionGroups.length > 0;
const hasAnyGroups =
(viewerGroups && viewerGroups.length > 0) ||
(permissionGroups && permissionGroups.length > 0);
if (!hasViewerGroups && !hasPermissionGroups) return;
if (!hasAnyGroups) return;
const documentIds = files.map((file) => file.documentId);
const promises = [];
const strategy =
dataroom?.defaultPermissionStrategy ||
DefaultPermissionStrategy.INHERIT_FROM_PARENT;
// Handle ViewerGroup permissions
if (hasViewerGroups) {
const defaultPermission =
dataroom?.defaultGroupPermission || "ask_every_time";
if (strategy === DefaultPermissionStrategy.ASK_EVERY_TIME) {
setShowGroupPermissions(true);
setUploadedFiles(files);
} else if (strategy === DefaultPermissionStrategy.INHERIT_FROM_PARENT) {
const isRootLevel = !folderPathName || folderPathName.length === 0;
if (defaultPermission === "use_default_permissions") {
promises.push(applyDefaultPermissions(dataroomId, documentIds));
} else if (defaultPermission === "inherit_from_parent") {
const isRootLevel = !folderPathName || folderPathName.length === 0;
if (isRootLevel) {
// For root level, show modal or apply defaults
setShowGroupPermissions(true);
setUploadedFiles(files);
} else {
// For subfolder uploads, inherit permissions from parent folder
promises.push(
inheritParentPermissions(
dataroomId,
documentIds,
folderPathName?.join("/"),
),
);
}
}
}
// Handle PermissionGroup permissions
if (hasPermissionGroups) {
promises.push(
applyPermissionGroupPermissions(
dataroomId,
documentIds,
dataroom?.defaultLinkPermission || "inherit_from_parent",
folderPathName?.join("/"),
(message) => toast.error(message),
),
);
}
Promise.allSettled(promises)
.then((results) => {
const failures = results.filter(
(result) =>
result.status === "rejected" ||
(result.status === "fulfilled" && !result.value.success),
);
if (failures.length > 0) {
console.error("Failed to apply default permissions:", failures);
toast.error("Failed to apply default permissions");
}
})
.catch((error) => {
console.error("Failed to apply default permissions:", error);
toast.error("Failed to apply default permissions");
applyPermissions(
dataroomId,
documentIds,
"INHERIT_FROM_PARENT",
isRootLevel ? undefined : folderPathName?.join("/"),
(message: string) => toast.error(message),
).catch((error: any) => {
console.error("Failed to apply permissions:", error);
toast.error("Failed to apply permissions");
});
}
// strategy === DefaultPermissionStrategy.HIDDEN_BY_DEFAULT - do nothing
};
return (
@@ -786,4 +748,4 @@ export function DataroomItemsList({
<DeleteFolderModal />
</>
);
}
}

View File

@@ -1,480 +0,0 @@
import { useState } from "react";
import { useTeam } from "@/context/team-context";
import { AnimatePresence, motion } from "motion/react";
import { toast } from "sonner";
import useSWR from "swr";
import useDataroomPermissionGroups from "@/lib/swr/use-dataroom-permission-groups";
import { fetcher } from "@/lib/utils";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Switch } from "@/components/ui/switch";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
type DefaultLinkPermissionStrategy =
| "inherit_from_parent"
| "use_default_permissions"
| "use_simple_permissions";
interface LinkPermissionSettingsProps {
dataroomId: string;
}
export default function LinkPermissionSettings({
dataroomId,
}: LinkPermissionSettingsProps) {
const teamInfo = useTeam();
const teamId = teamInfo?.currentTeam?.id;
const { permissionGroups, mutate: mutateGroups } =
useDataroomPermissionGroups();
const { data: dataroomData, mutate: mutateDataroom } = useSWR<{
id: string;
name: string;
pId: string;
defaultLinkPermission: DefaultLinkPermissionStrategy;
defaultLinkCanView: boolean;
defaultLinkCanDownload: boolean;
}>(
teamId && dataroomId
? `/api/teams/${teamId}/datarooms/${dataroomId}`
: null,
fetcher,
);
const [isUpdating, setIsUpdating] = useState(false);
const handlePermissionChange = async (
value: DefaultLinkPermissionStrategy,
) => {
if (!dataroomId || !teamId || isUpdating || !dataroomData) return;
setIsUpdating(true);
const optimisticData = { ...dataroomData, defaultLinkPermission: value };
const mutation = async () => {
const res = await fetch(`/api/teams/${teamId}/datarooms/${dataroomId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
defaultLinkPermission: value,
}),
});
if (!res.ok) {
throw new Error("Failed to update link permission settings");
}
return res.json();
};
try {
await toast.promise(
mutateDataroom(mutation(), {
optimisticData,
rollbackOnError: true,
populateCache: true,
revalidate: false,
}),
{
loading: "Updating link permission settings...",
success: "Link permission settings updated successfully",
error: (err) => err.message,
},
);
} catch (error) {
console.error(error);
} finally {
setIsUpdating(false);
}
};
const handleSimplePermissionChange = async (
field: "defaultLinkCanView" | "defaultLinkCanDownload",
checked: boolean,
) => {
if (!dataroomId || !teamId || isUpdating || !dataroomData) return;
setIsUpdating(true);
let updateData: Record<string, boolean> = { [field]: checked };
if (
field === "defaultLinkCanDownload" &&
checked &&
!dataroomData.defaultLinkCanView
) {
// Enabling download but view is disabled - auto enable view
updateData.defaultLinkCanView = true;
} else if (
field === "defaultLinkCanView" &&
!checked &&
dataroomData.defaultLinkCanDownload
) {
// Disabling view but download is enabled - auto disable download
updateData.defaultLinkCanDownload = false;
}
const optimisticData = { ...dataroomData, ...updateData };
const mutation = async () => {
const res = await fetch(`/api/teams/${teamId}/datarooms/${dataroomId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updateData),
});
if (!res.ok) {
throw new Error("Failed to update simple permission settings");
}
return res.json();
};
try {
await toast.promise(
mutateDataroom(mutation(), {
optimisticData,
rollbackOnError: true,
populateCache: true,
revalidate: false,
}),
{
loading: "Updating simple permission settings...",
success: "Simple permission settings updated successfully",
error: (err) => err.message,
},
);
} catch (error) {
console.error(error);
} finally {
setIsUpdating(false);
}
};
const updateGroupPermissions = async (
groupId: string,
permissions: {
defaultCanView?: boolean;
defaultCanDownload?: boolean;
},
) => {
if (!teamId) return;
toast.promise(
fetch(
`/api/teams/${teamId}/datarooms/${dataroomId}/permission-groups/${groupId}`,
{
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(permissions),
},
).then(async (res) => {
if (!res.ok) {
throw new Error("Failed to update permission group permissions");
}
await mutateGroups();
}),
{
loading: "Updating permission group permissions...",
success: "Permission group permissions updated successfully",
error: "Failed to update permission group permissions",
},
);
};
const handleViewPermissionChange = async (
groupId: string,
checked: boolean,
) => {
const group = permissionGroups?.find((g) => g.id === groupId);
if (!group) return;
// If disabling view, also disable download in single API call
if (!checked && (group.defaultCanDownload ?? false)) {
await updateGroupPermissions(groupId, {
defaultCanView: checked,
defaultCanDownload: false,
});
} else {
await updateGroupPermissions(groupId, {
defaultCanView: checked,
});
}
};
const handleDownloadPermissionChange = async (
groupId: string,
checked: boolean,
) => {
const group = permissionGroups?.find((g) => g.id === groupId);
if (!group) return;
// If enabling download, also enable view in single API call
if (checked && !(group.defaultCanView ?? false)) {
await updateGroupPermissions(groupId, {
defaultCanView: true,
defaultCanDownload: checked,
});
} else {
await updateGroupPermissions(groupId, {
defaultCanDownload: checked,
});
}
};
const currentStrategy =
dataroomData?.defaultLinkPermission ?? "inherit_from_parent";
const showGroupSettings = currentStrategy === "use_default_permissions";
const showSimpleSettings = currentStrategy === "use_simple_permissions";
return (
<Card>
<CardHeader>
<CardTitle>Manage Link Default Permissions</CardTitle>
<CardDescription>
Configure how permissions are applied when adding new documents and
folders to this dataroom.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<RadioGroup
value={currentStrategy}
onValueChange={handlePermissionChange}
disabled={isUpdating || !dataroomData}
className="space-y-3"
>
<div className="flex items-center space-x-3">
<RadioGroupItem
value="inherit_from_parent"
id="inherit_from_parent"
/>
<div>
<Label
htmlFor="inherit_from_parent"
className="text-sm font-medium"
>
Inherit from parent folder
</Label>
<p className="text-xs text-muted-foreground">
New documents and folders will automatically inherit the same
permissions as their parent folder. If added to the root level,
they will be view-only by default.
</p>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center space-x-3">
<RadioGroupItem
value="use_default_permissions"
id="use_default_permissions"
/>
<div>
<Label
htmlFor="use_default_permissions"
className="text-sm font-medium"
>
Use default link permissions
</Label>
<p className="text-xs text-muted-foreground">
When you add new documents or folders, all existing links will
automatically get the permissions you configure below. This
ensures consistent access across your dataroom.
</p>
</div>
</div>
<AnimatePresence>
{showGroupSettings && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="ml-6 overflow-hidden"
>
<div className="space-y-2">
{permissionGroups === undefined ? (
<div className="rounded-lg border bg-muted/20 p-6 text-center">
<p className="text-xs text-muted-foreground">
Loading permission groups...
</p>
</div>
) : permissionGroups.length > 0 ? (
<div className="rounded-lg border bg-muted/30">
<Table>
<TableHeader>
<TableRow className="border-b">
<TableHead className="h-9 w-[60%] text-xs font-medium">
Link Name
</TableHead>
<TableHead className="h-9 w-[20%] text-center text-xs font-medium">
Can View
</TableHead>
<TableHead className="h-9 w-[20%] text-center text-xs font-medium">
Can Download
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{permissionGroups.map((group) => {
// Get the link name - usually there's one link per permission group
const linkName =
group.links?.[0]?.name ||
(group.links?.[0]?.id
? `Link #${group.links[0].id.slice(-5)}`
: null);
return (
<TableRow
key={group.id}
className="border-b last:border-b-0"
>
<TableCell className="py-2 text-sm">
{linkName || group.name}
{group.links && group.links.length > 1 && (
<span className="ml-2 text-xs text-muted-foreground">
(+{group.links.length - 1} more)
</span>
)}
</TableCell>
<TableCell className="py-2 text-center">
<Switch
checked={group.defaultCanView ?? false}
onCheckedChange={(checked) =>
handleViewPermissionChange(
group.id,
checked,
)
}
/>
</TableCell>
<TableCell className="py-2 text-center">
<Switch
checked={
group.defaultCanDownload ?? false
}
onCheckedChange={(checked) =>
handleDownloadPermissionChange(
group.id,
checked,
)
}
/>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</div>
) : (
<div className="rounded-lg border border-dashed bg-muted/20 p-6 text-center">
<p className="text-xs text-muted-foreground">
No links found. Create links to configure permissions
for your dataroom.
</p>
</div>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
<div className="space-y-4">
<div className="flex items-center space-x-3">
<RadioGroupItem
value="use_simple_permissions"
id="use_simple_permissions"
/>
<div>
<Label
htmlFor="use_simple_permissions"
className="text-sm font-medium"
>
Use simple default permissions
</Label>
<p className="text-xs text-muted-foreground">
Apply the same view and download permissions to all new
documents and folders in this dataroom, regardless of where
they're added. This gives you full control over access levels
within this dataroom.
</p>
</div>
</div>
<AnimatePresence>
{showSimpleSettings && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="ml-6 overflow-hidden"
>
<div className="space-y-4 rounded-lg border bg-muted/30 p-4">
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label className="text-sm font-medium">Can View</Label>
</div>
<Switch
checked={dataroomData?.defaultLinkCanView ?? false}
onCheckedChange={(checked) =>
handleSimplePermissionChange(
"defaultLinkCanView",
checked,
)
}
disabled={isUpdating}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-1">
<Label className="text-sm font-medium">
Can Download
</Label>
</div>
<Switch
checked={dataroomData?.defaultLinkCanDownload ?? false}
onCheckedChange={(checked) =>
handleSimplePermissionChange(
"defaultLinkCanDownload",
checked,
)
}
disabled={isUpdating}
/>
</div>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</RadioGroup>
</CardContent>
</Card>
);
}

View File

@@ -1,11 +1,9 @@
import { useState } from "react";
import { useTeam } from "@/context/team-context";
import { AnimatePresence, motion } from "motion/react";
import { toast } from "sonner";
import useSWR from "swr";
import useDataroomGroups from "@/lib/swr/use-dataroom-groups";
import { fetcher } from "@/lib/utils";
import {
@@ -17,21 +15,11 @@ import {
} from "@/components/ui/card";
import { Label } from "@/components/ui/label";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { Switch } from "@/components/ui/switch";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
type DefaultGroupPermissionStrategy =
| "ask_every_time"
| "inherit_from_parent"
| "use_default_permissions"
| "no_permissions";
type DefaultPermissionStrategy =
| "INHERIT_FROM_PARENT"
| "ASK_EVERY_TIME"
| "HIDDEN_BY_DEFAULT";
interface PermissionSettingsProps {
dataroomId: string;
@@ -42,13 +30,12 @@ export default function PermissionSettings({
}: PermissionSettingsProps) {
const teamInfo = useTeam();
const teamId = teamInfo?.currentTeam?.id;
const { viewerGroups, mutate: mutateGroups } = useDataroomGroups();
const { data: dataroomData, mutate: mutateDataroom } = useSWR<{
id: string;
name: string;
pId: string;
defaultGroupPermission: DefaultGroupPermissionStrategy;
defaultPermissionStrategy: DefaultPermissionStrategy;
}>(
teamId && dataroomId
? `/api/teams/${teamId}/datarooms/${dataroomId}`
@@ -58,13 +45,14 @@ export default function PermissionSettings({
const [isUpdating, setIsUpdating] = useState(false);
const handlePermissionChange = async (
value: DefaultGroupPermissionStrategy,
) => {
const handlePermissionChange = async (value: DefaultPermissionStrategy) => {
if (!dataroomId || !teamId || isUpdating || !dataroomData) return;
setIsUpdating(true);
const optimisticData = { ...dataroomData, defaultGroupPermission: value };
const optimisticData = {
...dataroomData,
defaultPermissionStrategy: value,
};
const mutation = async () => {
const res = await fetch(`/api/teams/${teamId}/datarooms/${dataroomId}`, {
@@ -73,7 +61,7 @@ export default function PermissionSettings({
"Content-Type": "application/json",
},
body: JSON.stringify({
defaultGroupPermission: value,
defaultPermissionStrategy: value,
}),
});
@@ -105,64 +93,16 @@ export default function PermissionSettings({
}
};
const updateGroupPermissions = async (
groupId: string,
permissions: {
defaultCanView?: boolean;
defaultCanDownload?: boolean;
},
) => {
if (!teamId) return;
toast.promise(
fetch(`/api/teams/${teamId}/datarooms/${dataroomId}/groups/${groupId}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(permissions),
}).then(async (res) => {
if (!res.ok) {
throw new Error("Failed to update group permissions");
}
await mutateGroups();
}),
{
loading: "Updating group permissions...",
success: "Group permissions updated successfully",
error: "Failed to update group permissions",
},
);
};
const handleViewPermissionChange = async (
groupId: string,
checked: boolean,
) => {
await updateGroupPermissions(groupId, {
defaultCanView: checked,
});
};
const handleDownloadPermissionChange = async (
groupId: string,
checked: boolean,
) => {
await updateGroupPermissions(groupId, {
defaultCanDownload: checked,
});
};
const currentStrategy =
dataroomData?.defaultGroupPermission ?? "ask_every_time";
const showGroupSettings = currentStrategy === "use_default_permissions";
dataroomData?.defaultPermissionStrategy ?? "HIDDEN_BY_DEFAULT";
return (
<Card>
<CardHeader>
<CardTitle>Group Default Permissions</CardTitle>
<CardTitle>Default File Permissions</CardTitle>
<CardDescription>
Configure how group permissions are handled for new documents.
Configure how permissions are handled for new documents and folders
added to this dataroom.
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
@@ -173,145 +113,41 @@ export default function PermissionSettings({
className="space-y-3"
>
<div className="flex items-center space-x-3">
<RadioGroupItem value="ask_every_time" id="ask_every_time" />
<RadioGroupItem value="INHERIT_FROM_PARENT" id="inherit" />
<div>
<Label htmlFor="ask_every_time" className="text-sm font-medium">
Ask every time
</Label>
<p className="text-xs text-muted-foreground">
Show permissions modal for each upload
</p>
</div>
</div>
<div className="flex items-center space-x-3">
<RadioGroupItem
value="inherit_from_parent"
id="inherit_from_parent"
/>
<div>
<Label
htmlFor="inherit_from_parent"
className="text-sm font-medium"
>
<Label htmlFor="inherit" className="text-sm font-medium">
Inherit from parent folder
</Label>
<p className="text-xs text-muted-foreground">
New documents inherit permissions from their parent folder.
New documents and folders automatically inherit permissions from
their parent folder. Root-level items get view-only permissions
by default.
</p>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center space-x-3">
<RadioGroupItem
value="use_default_permissions"
id="use_default_permissions"
/>
<div>
<Label
htmlFor="use_default_permissions"
className="text-sm font-medium"
>
Use default group permissions
</Label>
<p className="text-xs text-muted-foreground">
Apply configured group defaults automatically
</p>
</div>
<div className="flex items-center space-x-3">
<RadioGroupItem value="ASK_EVERY_TIME" id="ask" />
<div>
<Label htmlFor="ask" className="text-sm font-medium">
Ask every time
</Label>
<p className="text-xs text-muted-foreground">
Show permissions modal for each document upload to manually
configure permissions.
</p>
</div>
<AnimatePresence>
{showGroupSettings && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
className="ml-6 overflow-hidden"
>
<div className="space-y-2">
{viewerGroups === undefined ? (
<div className="rounded-lg border bg-muted/20 p-6 text-center">
<p className="text-xs text-muted-foreground">
Loading groups...
</p>
</div>
) : viewerGroups.length > 0 ? (
<div className="rounded-lg border bg-muted/30">
<Table>
<TableHeader>
<TableRow className="border-b">
<TableHead className="h-9 w-[60%] text-xs font-medium">
Group
</TableHead>
<TableHead className="h-9 w-[20%] text-center text-xs font-medium">
Can View
</TableHead>
<TableHead className="h-9 w-[20%] text-center text-xs font-medium">
Can Download
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{viewerGroups.map((group) => (
<TableRow
key={group.id}
className="border-b last:border-b-0"
>
<TableCell className="py-2 text-sm">
{group.name}
</TableCell>
<TableCell className="py-2 text-center">
<Switch
checked={group.defaultCanView ?? false}
onCheckedChange={(checked) =>
handleViewPermissionChange(
group.id,
checked,
)
}
/>
</TableCell>
<TableCell className="py-2 text-center">
<Switch
checked={group.defaultCanDownload ?? false}
onCheckedChange={(checked) =>
handleDownloadPermissionChange(
group.id,
checked,
)
}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
) : (
<div className="rounded-lg border border-dashed bg-muted/20 p-6 text-center">
<p className="text-xs text-muted-foreground">
No groups found. Create groups to configure
permissions.
</p>
</div>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
<div className="flex items-center space-x-3">
<RadioGroupItem value="no_permissions" id="no_permissions" />
<RadioGroupItem value="HIDDEN_BY_DEFAULT" id="hidden" />
<div>
<Label htmlFor="no_permissions" className="text-sm font-medium">
No permissions by default
<Label htmlFor="hidden" className="text-sm font-medium">
Hidden by default
</Label>
<p className="text-xs text-muted-foreground">
No group permissions applied automatically. Permissions modal
will not open.
New documents and folders are hidden by default. Permissions
must be configured manually.
</p>
</div>
</div>
@@ -319,4 +155,4 @@ export default function PermissionSettings({
</CardContent>
</Card>
);
}
}

View File

@@ -1,7 +1,8 @@
import Link from "next/link";
import { useRouter } from "next/router";
import { BellIcon, CogIcon, LinkIcon, ShieldIcon } from "lucide-react";
import { BellIcon, CogIcon, ShieldIcon } from "lucide-react";
import { cn } from "@/lib/utils";
interface SettingsTabsProps {
@@ -39,19 +40,7 @@ export default function SettingsTabs({ dataroomId }: SettingsTabsProps) {
Notifications
</Link>
<Link
href={`/datarooms/${dataroomId}/settings/link-permission`}
className={cn(
"flex items-center gap-x-2 rounded-md p-2 text-primary hover:bg-muted",
{
"bg-muted font-medium": router.pathname.includes("link-permission"),
},
)}
>
<LinkIcon className="h-4 w-4" />
Link Permission
</Link>
<Link
href={`/datarooms/${dataroomId}/settings/permissions`}
href={`/datarooms/${dataroomId}/settings/file-permissions`}
className={cn(
"flex items-center gap-x-2 rounded-md p-2 text-primary hover:bg-muted",
{
@@ -60,8 +49,8 @@ export default function SettingsTabs({ dataroomId }: SettingsTabsProps) {
)}
>
<ShieldIcon className="h-4 w-4" />
Group Permissions
File Permissions
</Link>
</nav>
);
}
}

View File

@@ -5,6 +5,7 @@ import { FormEvent, useEffect, useState } from "react";
import { useTeam } from "@/context/team-context";
import { PlanEnum } from "@/ee/stripe/constants";
import { DefaultPermissionStrategy } from "@prisma/client";
import { parsePageId } from "notion-utils";
import { toast } from "sonner";
import { mutate } from "swr";
@@ -85,11 +86,7 @@ export function AddDocumentModal({
const { dataroom } = useDataroom();
const teamId = teamInfo?.currentTeam?.id as string;
const {
applyDefaultPermissions,
inheritParentPermissions,
applyPermissionGroupPermissions,
} = useDataroomPermissions();
const { applyPermissions } = useDataroomPermissions();
useEffect(() => {
if (openModal) setIsOpen(openModal);
@@ -150,7 +147,7 @@ export function AddDocumentModal({
"Failed to apply default permissions. Update the group permissions in the group settings.",
);
const applyViewerGroupPermissions = async (
const applyUnifiedPermissionsToDocument = async (
document: any,
dataroomDocument: DataroomDocument & {
dataroom: {
@@ -159,81 +156,47 @@ export function AddDocumentModal({
},
currentFolderPath?: string[],
): Promise<void> => {
const hasViewerGroups = dataroomDocument.dataroom._count.viewerGroups > 0;
const hasPermissionGroups =
const hasAnyGroups =
dataroomDocument.dataroom._count.viewerGroups > 0 ||
dataroomDocument.dataroom._count.permissionGroups > 0;
if (hasViewerGroups) {
const defaultPermission =
dataroom?.defaultGroupPermission || "ask_every_time";
if (!hasAnyGroups) return;
if (defaultPermission === "ask_every_time") {
setShowGroupPermissions(true);
setUploadedFiles([
{
documentId: document.id,
dataroomDocumentId: dataroomDocument.id,
fileName: document.name,
},
]);
} else if (defaultPermission === "inherit_from_parent") {
const isRootLevel =
!currentFolderPath || currentFolderPath.length === 0;
if (isRootLevel) {
setShowGroupPermissions(true);
setUploadedFiles([
{
documentId: document.id,
dataroomDocumentId: dataroomDocument.id,
fileName: document.name,
},
]);
} else {
try {
const result = await inheritParentPermissions(
dataroomId!,
[document.id],
currentFolderPath?.join("/"),
);
const strategy =
dataroom?.defaultPermissionStrategy ||
DefaultPermissionStrategy.INHERIT_FROM_PARENT;
if (!result.success) {
console.error(
"Failed to inherit parent permissions:",
result.error,
);
toastErrorMessage();
}
} catch (error) {
console.error("Failed to inherit parent permissions:", error);
toastErrorMessage();
}
}
} else if (defaultPermission === "use_default_permissions") {
try {
const result = await applyDefaultPermissions(dataroomId!, [
document.id,
]);
if (!result.success) {
console.error("Failed to apply default permissions:", result.error);
toastErrorMessage();
}
} catch (error) {
console.error("Failed to apply default permissions:", error);
if (strategy === DefaultPermissionStrategy.ASK_EVERY_TIME) {
setShowGroupPermissions(true);
setUploadedFiles([
{
documentId: document.id,
dataroomDocumentId: dataroomDocument.id,
fileName: document.name,
},
]);
} else if (strategy === DefaultPermissionStrategy.INHERIT_FROM_PARENT) {
const isRootLevel = !currentFolderPath || currentFolderPath.length === 0;
try {
const result = await applyPermissions(
dataroomId!,
[document.id],
"INHERIT_FROM_PARENT",
isRootLevel ? undefined : currentFolderPath?.join("/"),
toastErrorMessage,
);
if (!result.success) {
console.error("Failed to apply permissions:", result.error);
toastErrorMessage();
}
} catch (error) {
console.error("Failed to apply permissions:", error);
toastErrorMessage();
}
}
// Handle PermissionGroup permissions
if (hasPermissionGroups) {
await applyPermissionGroupPermissions(
dataroomId!,
[document.id],
dataroom?.defaultLinkPermission || "inherit_from_parent",
currentFolderPath?.join("/"),
toastErrorMessage,
);
}
// strategy === DefaultPermissionStrategy.HIDDEN_BY_DEFAULT - do nothing, documents remain hidden
};
const handleFileUpload = async (
@@ -330,7 +293,7 @@ export function AddDocumentModal({
};
};
await applyViewerGroupPermissions(
await applyUnifiedPermissionsToDocument(
document,
dataroomDocument,
currentFolderPath,
@@ -482,7 +445,7 @@ export function AddDocumentModal({
};
};
await applyViewerGroupPermissions(
await applyUnifiedPermissionsToDocument(
document,
dataroomDocument,
currentFolderPath,
@@ -736,4 +699,4 @@ export function AddDocumentModal({
)}
</>
);
}
}

View File

@@ -75,8 +75,12 @@ const NavItem: React.FC<Props["navigation"][0]> = ({
}
// Special case for permissions - also active when pathname includes "groups"
if (segment === "permissions" && router.pathname.includes("groups")) {
active = true;
// but NOT when it's within settings (like settings/file-permissions)
if (segment === "permissions") {
active =
(router.pathname.includes("permissions") &&
!router.pathname.includes("settings")) ||
router.pathname.includes("groups");
}
if (segment === "analytics" && router.pathname.includes("groups")) {

View File

@@ -1,280 +1,63 @@
import { useTeam } from "@/context/team-context";
export const useDataroomPermissions = () => {
const teamInfo = useTeam();
const teamId = teamInfo?.currentTeam?.id;
const teamInfo = useTeam();
const teamId = teamInfo?.currentTeam?.id;
const applyDefaultPermissions = async (
dataroomId: string,
documentIds: string[],
): Promise<{ success: boolean; error?: string }> => {
if (!teamId) {
return { success: false, error: "Team ID not available" };
}
const applyPermissions = async (
dataroomId: string,
documentIds: string[],
strategy: "INHERIT_FROM_PARENT" | "ASK_EVERY_TIME" | "HIDDEN_BY_DEFAULT",
folderPath?: string,
onError?: (message: string) => void,
): Promise<{ success: boolean; error?: string }> => {
if (!teamId) {
return { success: false, error: "Team ID not available" };
}
if (!documentIds || documentIds.length === 0) {
return { success: false, error: "No document IDs provided" };
}
if (!documentIds || documentIds.length === 0) {
return { success: false, error: "No document IDs provided" };
}
try {
const response = await fetch(
`/api/teams/${encodeURIComponent(teamId)}/datarooms/${encodeURIComponent(dataroomId)}/groups/apply-default-permissions`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
documentIds,
}),
},
);
try {
const response = await fetch(
`/api/teams/${encodeURIComponent(teamId)}/datarooms/${encodeURIComponent(dataroomId)}/apply-permissions`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
documentIds,
strategy,
folderPath,
}),
},
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
return {
success: false,
error: errorData.message || `HTTP ${response.status}`
};
}
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
onError?.(errorData.message || `HTTP ${response.status}`);
return {
success: false,
error: errorData.message || `HTTP ${response.status}`,
};
}
return { success: true };
} catch (error) {
console.error("Failed to apply default permissions:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error"
};
}
};
return { success: true };
} catch (error) {
console.error("Failed to apply permissions:", error);
const errorMessage =
error instanceof Error ? error.message : "Unknown error";
onError?.(errorMessage);
return {
success: false,
error: errorMessage,
};
}
};
const inheritParentPermissions = async (
dataroomId: string,
documentIds: string[],
folderPath?: string,
): Promise<{ success: boolean; error?: string }> => {
if (!teamId) {
return { success: false, error: "Team ID not available" };
}
if (!documentIds || documentIds.length === 0) {
return { success: false, error: "No document IDs provided" };
}
try {
const response = await fetch(
`/api/teams/${encodeURIComponent(teamId)}/datarooms/${encodeURIComponent(dataroomId)}/groups/inherit-parent-permissions`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
documentIds,
folderPath,
}),
},
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
return {
success: false,
error: errorData.message || `HTTP ${response.status}`
};
}
return { success: true };
} catch (error) {
console.error("Failed to inherit parent permissions:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error"
};
}
};
const applyDefaultPermissionGroupPermissions = async (
dataroomId: string,
documentIds: string[],
): Promise<{ success: boolean; error?: string }> => {
if (!teamId) {
return { success: false, error: "Team ID not available" };
}
if (!documentIds || documentIds.length === 0) {
return { success: false, error: "No document IDs provided" };
}
try {
const response = await fetch(
`/api/teams/${encodeURIComponent(teamId)}/datarooms/${encodeURIComponent(dataroomId)}/permission-groups/apply-default-permissions`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
documentIds,
}),
},
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
return {
success: false,
error: errorData.message || `HTTP ${response.status}`
};
}
return { success: true };
} catch (error) {
console.error("Failed to apply default permission group permissions:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error"
};
}
};
const inheritParentPermissionGroupPermissions = async (
dataroomId: string,
documentIds: string[],
folderPath?: string,
): Promise<{ success: boolean; error?: string }> => {
if (!teamId) {
return { success: false, error: "Team ID not available" };
}
if (!documentIds || documentIds.length === 0) {
return { success: false, error: "No document IDs provided" };
}
try {
const response = await fetch(
`/api/teams/${encodeURIComponent(teamId)}/datarooms/${encodeURIComponent(dataroomId)}/permission-groups/inherit-parent-permissions`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
documentIds,
folderPath,
}),
},
);
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
return {
success: false,
error: errorData.message || `HTTP ${response.status}`
};
}
return { success: true };
} catch (error) {
console.error("Failed to inherit parent permission group permissions:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error"
};
}
};
const applyPermissionGroupPermissions = async (
dataroomId: string,
documentIds: string[],
defaultLinkPermission: string,
folderPath?: string,
onError?: (message: string) => void,
): Promise<{ success: boolean; error?: string }> => {
if (defaultLinkPermission === "inherit_from_parent") {
try {
const result = await inheritParentPermissionGroupPermissions(
dataroomId,
documentIds,
folderPath,
);
if (!result.success) {
console.error(
"Failed to inherit parent PermissionGroup permissions:",
result.error,
);
onError?.("Failed to inherit parent PermissionGroup permissions");
}
return result;
} catch (error) {
console.error(
"Failed to inherit parent PermissionGroup permissions:",
error,
);
onError?.("Failed to inherit parent PermissionGroup permissions");
return { success: false, error: error instanceof Error ? error.message : "Unknown error" };
}
} else if (
defaultLinkPermission === "use_default_permissions" ||
defaultLinkPermission === "use_simple_permissions"
) {
const isRootLevel = !folderPath || folderPath.length === 0;
if (isRootLevel) {
try {
const result = await applyDefaultPermissionGroupPermissions(
dataroomId,
documentIds,
);
if (!result.success) {
console.error(
"Failed to apply default PermissionGroup permissions:",
result.error,
);
onError?.("Failed to apply default PermissionGroup permissions");
}
return result;
} catch (error) {
console.error(
"Failed to apply default PermissionGroup permissions:",
error,
);
onError?.("Failed to apply default PermissionGroup permissions");
return { success: false, error: error instanceof Error ? error.message : "Unknown error" };
}
} else {
try {
const result = await inheritParentPermissionGroupPermissions(
dataroomId,
documentIds,
folderPath,
);
if (!result.success) {
console.error(
"Failed to inherit parent PermissionGroup permissions:",
result.error,
);
onError?.("Failed to inherit parent PermissionGroup permissions");
}
return result;
} catch (error) {
console.error(
"Failed to inherit parent PermissionGroup permissions:",
error,
);
onError?.("Failed to inherit parent PermissionGroup permissions");
return { success: false, error: error instanceof Error ? error.message : "Unknown error" };
}
}
}
return { success: true };
};
return {
applyDefaultPermissions,
inheritParentPermissions,
applyDefaultPermissionGroupPermissions,
inheritParentPermissionGroupPermissions,
applyPermissionGroupPermissions
};
};
return {
applyPermissions,
};
};

View File

@@ -0,0 +1,312 @@
import { NextApiRequest, NextApiResponse } from "next";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { ItemType } from "@prisma/client";
import { getServerSession } from "next-auth";
import { errorhandler } from "@/lib/errorHandler";
import prisma from "@/lib/prisma";
import { CustomUser } from "@/lib/types";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== "POST") {
res.setHeader("Allow", ["POST"]);
return res.status(405).end(`Method ${req.method} Not Allowed`);
}
const session = await getServerSession(req, res, authOptions);
if (!session) {
return res.status(401).json({ message: "Unauthorized" });
}
const { teamId, id: dataroomId } = req.query as {
teamId: string;
id: string;
};
const userId = (session.user as CustomUser).id;
try {
const { documentIds, strategy, folderPath } = req.body as {
documentIds: string[];
strategy: string;
folderPath?: string;
};
// Validate input
if (
!documentIds ||
!Array.isArray(documentIds) ||
documentIds.length === 0
) {
return res.status(400).json({ message: "Document IDs are required" });
}
if (!strategy) {
return res.status(400).json({ message: "Strategy is required" });
}
// Validate strategy
if (
!["INHERIT_FROM_PARENT", "ASK_EVERY_TIME", "HIDDEN_BY_DEFAULT"].includes(
strategy,
)
) {
return res.status(400).json({ message: "Invalid strategy" });
}
// Check if the user is part of the team
const team = await prisma.team.findFirst({
where: {
id: teamId,
users: { some: { userId } },
},
});
if (!team) {
return res.status(403).json({ message: "Unauthorized" });
}
// Get dataroom and verify it exists and belongs to the team
const dataroom = await prisma.dataroom.findUnique({
where: { id: dataroomId },
select: {
id: true,
teamId: true,
defaultPermissionStrategy: true,
},
});
if (!dataroom || dataroom.teamId !== teamId) {
return res.status(404).json({ message: "Dataroom not found" });
}
// Get dataroom documents for the provided document IDs
const dataroomDocuments = await prisma.dataroomDocument.findMany({
where: {
documentId: { in: documentIds },
dataroomId,
},
select: { id: true, documentId: true, folderId: true },
});
if (dataroomDocuments.length === 0) {
return res
.status(404)
.json({ message: "No documents found in this dataroom" });
}
// Apply permissions based on strategy
await applyPermissionStrategy(
dataroomId,
dataroomDocuments,
strategy,
folderPath,
);
return res.status(200).json({
message: "Permissions applied successfully",
documentsProcessed: dataroomDocuments.length,
});
} catch (error) {
errorhandler(error, res);
}
}
async function applyPermissionStrategy(
dataroomId: string,
dataroomDocuments: {
id: string;
documentId: string;
folderId: string | null;
}[],
strategy: string,
folderPath?: string,
) {
if (strategy === "INHERIT_FROM_PARENT") {
const isRootLevel = !folderPath || folderPath.length === 0;
if (isRootLevel) {
// For root level, apply view-only permissions to all groups
await applyRootLevelPermissions(dataroomId, dataroomDocuments);
} else {
// For subfolders, inherit from parent folder
await inheritFromParentFolder(dataroomId, dataroomDocuments, folderPath);
}
} else if (strategy === "ASK_EVERY_TIME") {
// Do nothing here - the UI will handle showing the permission modal
return;
} else if (strategy === "HIDDEN_BY_DEFAULT") {
// Do nothing here - documents remain hidden with no permissions
return;
}
}
async function applyRootLevelPermissions(
dataroomId: string,
dataroomDocuments: {
id: string;
documentId: string;
folderId: string | null;
}[],
) {
// Get both ViewerGroups and PermissionGroups
const [viewerGroups, permissionGroups] = await Promise.all([
prisma.viewerGroup.findMany({
where: { dataroomId },
select: { id: true },
}),
prisma.permissionGroup.findMany({
where: { dataroomId },
select: { id: true },
}),
]);
const viewerGroupPermissionsToCreate: any[] = [];
const permissionGroupPermissionsToCreate: any[] = [];
// ViewerGroup permissions - all get view-only access
viewerGroups.forEach((group) => {
dataroomDocuments.forEach((doc) => {
viewerGroupPermissionsToCreate.push({
groupId: group.id,
itemId: doc.id,
itemType: ItemType.DATAROOM_DOCUMENT,
canView: true,
canDownload: false,
});
});
});
// PermissionGroup permissions - all get view-only access
permissionGroups.forEach((group) => {
dataroomDocuments.forEach((doc) => {
permissionGroupPermissionsToCreate.push({
groupId: group.id,
itemId: doc.id,
itemType: ItemType.DATAROOM_DOCUMENT,
canView: true,
canDownload: false,
canDownloadOriginal: false,
});
});
});
// Apply permissions in a transaction
await prisma.$transaction(async (tx) => {
// Create new permissions
if (viewerGroupPermissionsToCreate.length > 0) {
await tx.viewerGroupAccessControls.createMany({
data: viewerGroupPermissionsToCreate,
skipDuplicates: true,
});
}
if (permissionGroupPermissionsToCreate.length > 0) {
await tx.permissionGroupAccessControls.createMany({
data: permissionGroupPermissionsToCreate,
skipDuplicates: true,
});
}
});
}
async function inheritFromParentFolder(
dataroomId: string,
dataroomDocuments: {
id: string;
documentId: string;
folderId: string | null;
}[],
folderPath: string,
) {
// Get parent folder permissions and apply them to new documents
const pathSegments = folderPath.split("/").filter(Boolean);
const parentPath = "/" + pathSegments.slice(0, -1).join("/");
const parentFolder = await prisma.dataroomFolder.findUnique({
where: {
dataroomId_path: { dataroomId, path: parentPath },
},
select: { id: true },
});
if (!parentFolder) {
// If no parent folder found, apply root level permissions
await applyRootLevelPermissions(dataroomId, dataroomDocuments);
return;
}
// Get existing permissions for the parent folder
const [parentViewerPermissions, parentPermissionGroupPermissions] =
await Promise.all([
prisma.viewerGroupAccessControls.findMany({
where: {
itemId: parentFolder.id,
itemType: ItemType.DATAROOM_FOLDER,
},
select: { groupId: true, canView: true, canDownload: true },
}),
prisma.permissionGroupAccessControls.findMany({
where: {
itemId: parentFolder.id,
itemType: ItemType.DATAROOM_FOLDER,
},
select: {
groupId: true,
canView: true,
canDownload: true,
canDownloadOriginal: true,
},
}),
]);
// Apply parent permissions to documents
await prisma.$transaction(async (tx) => {
// Create permissions based on parent folder permissions
const viewerGroupPermissionsToCreate: any[] = [];
const permissionGroupPermissionsToCreate: any[] = [];
parentViewerPermissions.forEach((parentPerm) => {
dataroomDocuments.forEach((doc) => {
viewerGroupPermissionsToCreate.push({
groupId: parentPerm.groupId,
itemId: doc.id,
itemType: ItemType.DATAROOM_DOCUMENT,
canView: parentPerm.canView,
canDownload: parentPerm.canDownload,
});
});
});
parentPermissionGroupPermissions.forEach((parentPerm) => {
dataroomDocuments.forEach((doc) => {
permissionGroupPermissionsToCreate.push({
groupId: parentPerm.groupId,
itemId: doc.id,
itemType: ItemType.DATAROOM_DOCUMENT,
canView: parentPerm.canView,
canDownload: parentPerm.canDownload,
canDownloadOriginal: parentPerm.canDownloadOriginal,
});
});
});
if (viewerGroupPermissionsToCreate.length > 0) {
await tx.viewerGroupAccessControls.createMany({
data: viewerGroupPermissionsToCreate,
skipDuplicates: true,
});
}
if (permissionGroupPermissionsToCreate.length > 0) {
await tx.permissionGroupAccessControls.createMany({
data: permissionGroupPermissionsToCreate,
skipDuplicates: true,
});
}
});
}

View File

@@ -1,7 +1,7 @@
import { NextApiRequest, NextApiResponse } from "next";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { ItemType } from "@prisma/client";
import { DefaultPermissionStrategy, ItemType } from "@prisma/client";
import slugify from "@sindresorhus/slugify";
import { getServerSession } from "next-auth/next";
@@ -21,8 +21,6 @@ async function applyFolderPermissions(
}
}
async function applyDefaultFolderPermissions(
dataroomId: string,
folderId: string,
@@ -31,9 +29,7 @@ async function applyDefaultFolderPermissions(
prisma.dataroom.findUnique({
where: { id: dataroomId },
select: {
defaultLinkPermission: true,
defaultLinkCanView: true,
defaultLinkCanDownload: true,
defaultPermissionStrategy: true,
teamId: true,
},
}),
@@ -41,8 +37,6 @@ async function applyDefaultFolderPermissions(
where: { dataroomId },
select: {
id: true,
defaultCanView: true,
defaultCanDownload: true,
},
}),
prisma.permissionGroup.findMany({
@@ -50,15 +44,12 @@ async function applyDefaultFolderPermissions(
select: {
id: true,
name: true,
defaultCanView: true,
defaultCanDownload: true,
},
}),
]);
if (!dataroom) return;
const allPermissionGroupData: {
groupId: string;
itemId: string;
@@ -69,8 +60,11 @@ async function applyDefaultFolderPermissions(
}[] = [];
if (permissionGroups.length > 0) {
if (dataroom.defaultLinkPermission === "inherit_from_parent") {
permissionGroups.forEach(group => {
if (
dataroom.defaultPermissionStrategy ===
DefaultPermissionStrategy.INHERIT_FROM_PARENT
) {
permissionGroups.forEach((group) => {
allPermissionGroupData.push({
groupId: group.id,
itemId: folderId,
@@ -80,57 +74,33 @@ async function applyDefaultFolderPermissions(
canDownloadOriginal: false,
});
});
} else if (dataroom.defaultLinkPermission === "use_simple_permissions") {
const globalCanView = dataroom.defaultLinkCanView ?? false;
const globalCanDownload = dataroom.defaultLinkCanDownload ?? false;
if (globalCanView || globalCanDownload) {
permissionGroups.forEach(group => {
allPermissionGroupData.push({
groupId: group.id,
itemId: folderId,
itemType: ItemType.DATAROOM_FOLDER,
canView: globalCanView || globalCanDownload,
canDownload: globalCanDownload,
canDownloadOriginal: false,
});
});
}
} else {
permissionGroups
.filter(group => group.defaultCanView || group.defaultCanDownload)
.forEach(group => {
const canDownload = group.defaultCanDownload ?? false;
const canView = group.defaultCanView ?? false;
allPermissionGroupData.push({
groupId: group.id,
itemId: folderId,
itemType: ItemType.DATAROOM_FOLDER,
canView: canView || canDownload,
canDownload: canDownload,
canDownloadOriginal: false,
});
});
}
// For other strategies (ASK_EVERY_TIME, HIDDEN_BY_DEFAULT), don't auto-create permissions
}
const viewerGroupData = viewerGroups.map((group) => ({
groupId: group.id,
itemId: folderId,
itemType: ItemType.DATAROOM_FOLDER,
canView:
dataroom.defaultPermissionStrategy ===
DefaultPermissionStrategy.INHERIT_FROM_PARENT,
canDownload: false,
}));
await Promise.all([
viewerGroups.length > 0 && prisma.viewerGroupAccessControls.createMany({
data: viewerGroups
.filter(group => group.defaultCanView || group.defaultCanDownload)
.map(group => ({
groupId: group.id,
itemId: folderId,
itemType: ItemType.DATAROOM_FOLDER,
canView: group.defaultCanView ?? false,
canDownload: group.defaultCanDownload ?? false,
})),
skipDuplicates: true,
}),
allPermissionGroupData.length > 0 && prisma.permissionGroupAccessControls.createMany({
data: allPermissionGroupData,
skipDuplicates: true,
}),
viewerGroupData.length > 0 &&
dataroom.defaultPermissionStrategy ===
DefaultPermissionStrategy.INHERIT_FROM_PARENT &&
prisma.viewerGroupAccessControls.createMany({
data: viewerGroupData,
skipDuplicates: true,
}),
allPermissionGroupData.length > 0 &&
prisma.permissionGroupAccessControls.createMany({
data: allPermissionGroupData,
skipDuplicates: true,
}),
]);
}
@@ -182,12 +152,7 @@ export default async function handle(
dataroomId,
parentId: null,
},
orderBy: [
{ orderIndex: "asc" },
{
name: "asc",
},
],
orderBy: [{ orderIndex: "asc" }, { name: "asc" }],
include: {
_count: {
select: { documents: true, childFolders: true },
@@ -373,10 +338,11 @@ export default async function handle(
let counter = 1;
const MAX_RETRIES = 50;
// Split path into segments
// Split path into segments
// Slugify the final folder name
const pathSegments = path ? path.split('/').filter(Boolean) : [];
const basePath = pathSegments.length > 0 ? '/' + pathSegments.join('/') + '/' : '/';
const pathSegments = path ? path.split("/").filter(Boolean) : [];
const basePath =
pathSegments.length > 0 ? "/" + pathSegments.join("/") + "/" : "/";
let childFolderPath = basePath + slugify(folderName);

View File

@@ -89,10 +89,8 @@ export default async function handle(
groupId: string;
};
const { name, defaultCanView, defaultCanDownload, allowAll, domains } = req.body as {
const { name, allowAll, domains } = req.body as {
name?: string;
defaultCanView?: boolean;
defaultCanDownload?: boolean;
allowAll?: boolean;
domains?: string[];
};
@@ -129,8 +127,6 @@ export default async function handle(
},
data: {
...(name && { name }),
...(typeof defaultCanView === "boolean" && { defaultCanView }),
...(typeof defaultCanDownload === "boolean" && { defaultCanDownload }),
...(typeof allowAll === "boolean" && { allowAll }),
...(domains && { domains }),
},

View File

@@ -1,178 +0,0 @@
import { NextApiRequest, NextApiResponse } from "next";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { DefaultGroupPermissionStrategy, ItemType } from "@prisma/client";
import { getServerSession } from "next-auth/next";
import prisma from "@/lib/prisma";
import { CustomUser } from "@/lib/types";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== "POST") {
return res.status(405).json({ message: "Method not allowed" });
}
// POST /api/teams/:teamId/datarooms/:id/groups/apply-default-permissions
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).end("Unauthorized");
return;
}
const userId = (session.user as CustomUser).id;
const { teamId, id: dataroomId } = req.query as { teamId: string; id: string };
try {
const { documentIds } = req.body as {
documentIds: string[];
};
// Validate input
if (!documentIds || !Array.isArray(documentIds) || documentIds.length === 0) {
return res.status(400).json({ message: "Document IDs are required" });
}
// Check if the user is part of the team
const team = await prisma.team.findFirst({
where: {
id: teamId,
users: {
some: {
userId,
},
},
},
});
if (!team) {
return res.status(403).json({ message: "Unauthorized" });
}
// Get dataroom and verify it exists and belongs to the team
const dataroom = await prisma.dataroom.findUnique({
where: {
id: dataroomId,
},
select: {
id: true,
teamId: true,
defaultGroupPermission: true,
},
});
if (!dataroom || dataroom.teamId !== teamId) {
return res.status(404).json({ message: "Dataroom not found" });
}
// Check if dataroom is set to use default permissions
if (dataroom.defaultGroupPermission !== DefaultGroupPermissionStrategy.use_default_permissions) {
return res.status(400).json({
message: "Dataroom is not configured to use default permissions"
});
}
// Get all viewer groups with their default permissions
const viewerGroups = await prisma.viewerGroup.findMany({
where: {
dataroomId,
},
select: {
id: true,
defaultCanView: true,
defaultCanDownload: true,
},
});
if (viewerGroups.length === 0) {
return res.status(200).json({
message: "No viewer groups found, no permissions applied"
});
}
// Get dataroom document IDs for the provided document IDs
const dataroomDocuments = await prisma.dataroomDocument.findMany({
where: {
documentId: {
in: documentIds,
},
dataroomId,
},
select: {
id: true,
documentId: true,
},
});
if (dataroomDocuments.length === 0) {
return res.status(404).json({
message: "No documents found in this dataroom"
});
}
// Build permissions to create
const permissionsToCreate: {
groupId: string;
itemId: string;
itemType: ItemType;
canView: boolean;
canDownload: boolean;
}[] = [];
viewerGroups.forEach((group) => {
// Only create permissions if the group has view or download enabled
if (group.defaultCanView || group.defaultCanDownload) {
dataroomDocuments.forEach((doc) => {
permissionsToCreate.push({
groupId: group.id,
itemId: doc.id,
itemType: ItemType.DATAROOM_DOCUMENT,
canView: group.defaultCanView ?? false,
canDownload: group.defaultCanDownload ?? false,
});
});
}
});
if (permissionsToCreate.length === 0) {
return res.status(200).json({
message: "No default permissions configured, no permissions applied"
});
}
// Apply permissions in a transaction
await prisma.$transaction(async (tx) => {
// First, remove any existing permissions for these documents and groups
await tx.viewerGroupAccessControls.deleteMany({
where: {
itemId: {
in: dataroomDocuments.map(doc => doc.id),
},
itemType: ItemType.DATAROOM_DOCUMENT,
groupId: {
in: viewerGroups.map(group => group.id),
},
},
});
// Then create the new default permissions
await tx.viewerGroupAccessControls.createMany({
data: permissionsToCreate,
skipDuplicates: true,
});
});
res.status(200).json({
message: "Default permissions applied successfully",
appliedPermissions: permissionsToCreate.length,
documentsProcessed: dataroomDocuments.length,
groupsProcessed: viewerGroups.length,
});
} catch (error) {
console.error("Error applying default permissions:", error);
res.status(500).json({ message: "Internal server error" });
}
}

View File

@@ -1,219 +0,0 @@
import { NextApiRequest, NextApiResponse } from "next";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { ItemType } from "@prisma/client";
import { getServerSession } from "next-auth/next";
import prisma from "@/lib/prisma";
import { CustomUser } from "@/lib/types";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== "POST") {
return res.status(405).json({ message: "Method not allowed" });
}
// POST /api/teams/:teamId/datarooms/:id/groups/inherit-parent-permissions
const session = await getServerSession(req, res, authOptions);
if (!session) {
res.status(401).end("Unauthorized");
return;
}
const userId = (session.user as CustomUser).id;
const { teamId, id: dataroomId } = req.query as { teamId: string; id: string };
try {
const { documentIds, folderPath } = req.body as {
documentIds: string[];
folderPath?: string;
};
// Validate input
if (!documentIds || !Array.isArray(documentIds) || documentIds.length === 0) {
return res.status(400).json({ message: "Document IDs are required" });
}
// Check if the user is part of the team
const team = await prisma.team.findFirst({
where: {
id: teamId,
users: {
some: {
userId,
},
},
},
});
if (!team) {
return res.status(403).json({ message: "Unauthorized" });
}
// Get dataroom and verify it exists and belongs to the team
const dataroom = await prisma.dataroom.findFirst({
where: {
id: dataroomId,
teamId,
},
select: {
id: true,
defaultGroupPermission: true,
},
});
if (!dataroom) {
return res.status(404).json({ message: "Dataroom not found" });
}
// Get all viewer groups
const viewerGroups = await prisma.viewerGroup.findMany({
where: {
dataroomId,
},
select: {
id: true,
name: true,
},
});
if (viewerGroups.length === 0) {
return res.status(200).json({
message: "No viewer groups found, no permissions applied"
});
}
// Get dataroom document IDs for the new documents
const newDataroomDocuments = await prisma.dataroomDocument.findMany({
where: {
documentId: {
in: documentIds,
},
dataroomId,
},
select: {
id: true,
documentId: true,
folderId: true,
},
});
if (newDataroomDocuments.length === 0) {
return res.status(404).json({
message: "No documents found in this dataroom"
});
}
// Determine the parent folder ID based on the folderPath or new documents' folderId
let parentFolderId: string | null = null;
if (folderPath) {
// Find the folder by path
const folder = await prisma.dataroomFolder.findUnique({
where: {
dataroomId_path: {
dataroomId,
path: `/${folderPath}`,
},
},
select: {
id: true,
},
});
parentFolderId = folder?.id || null;
} else {
// Use the folderId from the first new document (they should all be in the same folder)
parentFolderId = newDataroomDocuments[0]?.folderId || null;
}
if (!parentFolderId) {
return res.status(200).json({
message: "Root level upload: please set permissions manually."
});
}
// Get all permissions for the parent folder
const parentFolderPermissions = await prisma.viewerGroupAccessControls.findMany({
where: {
itemId: parentFolderId,
itemType: ItemType.DATAROOM_FOLDER,
groupId: {
in: viewerGroups.map(group => group.id),
},
},
select: {
groupId: true,
canView: true,
canDownload: true,
},
});
if (parentFolderPermissions.length === 0) {
return res.status(200).json({
message: "Parent folder has no permissions to inherit."
});
}
// Build permissions to create for new documents
const permissionsToCreate: {
groupId: string;
itemId: string;
itemType: ItemType;
canView: boolean;
canDownload: boolean;
}[] = [];
parentFolderPermissions.forEach((permission) => {
newDataroomDocuments.forEach((doc) => {
permissionsToCreate.push({
groupId: permission.groupId,
itemId: doc.id,
itemType: ItemType.DATAROOM_DOCUMENT,
canView: permission.canView,
canDownload: permission.canDownload,
});
});
});
if (permissionsToCreate.length === 0) {
return res.status(200).json({
message: "No consistent permissions found to inherit"
});
}
// Apply inherited permissions in a transaction
await prisma.$transaction(async (tx) => {
// First, remove any existing permissions for these documents and groups
await tx.viewerGroupAccessControls.deleteMany({
where: {
itemId: {
in: newDataroomDocuments.map(doc => doc.id),
},
itemType: ItemType.DATAROOM_DOCUMENT,
groupId: {
in: viewerGroups.map(group => group.id),
},
},
});
// Then create the inherited permissions
await tx.viewerGroupAccessControls.createMany({
data: permissionsToCreate,
skipDuplicates: true,
});
});
res.status(200).json({
message: "Parent folder permissions inherited successfully",
appliedPermissions: permissionsToCreate.length,
documentsProcessed: newDataroomDocuments.length,
groupsProcessed: viewerGroups.length,
});
} catch (error) {
console.error("Error inheriting parent permissions:", error);
res.status(500).json({ message: "Internal server error" });
}
}

View File

@@ -1,13 +1,13 @@
import { NextApiRequest, NextApiResponse } from "next";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { DefaultPermissionStrategy } from "@prisma/client";
import { getServerSession } from "next-auth/next";
import { errorhandler } from "@/lib/errorHandler";
import { getFeatureFlags } from "@/lib/featureFlags";
import prisma from "@/lib/prisma";
import { CustomUser } from "@/lib/types";
import { DefaultGroupPermissionStrategy, DefaultLinkPermissionStrategy } from "@prisma/client";
export default async function handle(
req: NextApiRequest,
@@ -100,14 +100,12 @@ export default async function handle(
return res.status(401).end("Unauthorized");
}
const { name, enableChangeNotifications, defaultGroupPermission, defaultLinkPermission, defaultLinkCanView, defaultLinkCanDownload } = req.body as {
name?: string;
enableChangeNotifications?: boolean;
defaultGroupPermission?: DefaultGroupPermissionStrategy;
defaultLinkPermission?: DefaultLinkPermissionStrategy;
defaultLinkCanView?: boolean;
defaultLinkCanDownload?: boolean;
};
const { name, enableChangeNotifications, defaultPermissionStrategy } =
req.body as {
name?: string;
enableChangeNotifications?: boolean;
defaultPermissionStrategy?: DefaultPermissionStrategy;
};
const featureFlags = await getFeatureFlags({ teamId: team.id });
const isDataroomsPlus = team.plan.includes("datarooms-plus");
@@ -133,10 +131,7 @@ export default async function handle(
...(typeof enableChangeNotifications === "boolean" && {
enableChangeNotifications,
}),
...(defaultGroupPermission && { defaultGroupPermission }),
...(defaultLinkPermission && { defaultLinkPermission }),
...(typeof defaultLinkCanView === "boolean" && { defaultLinkCanView }),
...(typeof defaultLinkCanDownload === "boolean" && { defaultLinkCanDownload }),
...(defaultPermissionStrategy && { defaultPermissionStrategy }),
},
});

View File

@@ -20,9 +20,7 @@ const permissionsSchema = z.record(z.string(), itemPermissionSchema);
const patchPermissionGroupSchema = z.object({
name: z.string().optional(),
description: z.string().nullable().optional(),
defaultCanView: z.boolean().optional(),
defaultCanDownload: z.boolean().optional(),
description: z.string().optional(),
});
export default async function handle(

View File

@@ -1,211 +0,0 @@
import { NextApiRequest, NextApiResponse } from "next";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { DefaultGroupPermissionStrategy, ItemType } from "@prisma/client";
import { getServerSession } from "next-auth/next";
import { errorhandler } from "@/lib/errorHandler";
import prisma from "@/lib/prisma";
import { CustomUser } from "@/lib/types";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== "POST") {
res.setHeader("Allow", ["POST"]);
return res.status(405).end(`Method ${req.method} Not Allowed`);
}
const session = await getServerSession(req, res, authOptions);
if (!session) {
return res.status(401).end("Unauthorized");
}
const { teamId, id: dataroomId } = req.query as {
teamId: string;
id: string;
};
const userId = (session.user as CustomUser).id;
try {
const { documentIds } = req.body as {
documentIds: string[];
};
if (!documentIds || !Array.isArray(documentIds) || documentIds.length === 0) {
return res.status(400).json({ message: "Document IDs are required" });
}
// Check if the user is part of the team
const team = await prisma.team.findFirst({
where: {
id: teamId,
users: {
some: {
userId,
},
},
},
});
if (!team) {
return res.status(403).json({ message: "Unauthorized" });
}
// Get dataroom and verify it exists and belongs to the team
const dataroom = await prisma.dataroom.findUnique({
where: {
id: dataroomId,
},
select: {
id: true,
teamId: true,
defaultGroupPermission: true,
defaultLinkPermission: true,
defaultLinkCanView: true,
defaultLinkCanDownload: true,
},
});
if (!dataroom || dataroom.teamId !== teamId) {
return res.status(404).json({ message: "Dataroom not found" });
}
// Check if dataroom is set to use default permissions for PermissionGroups
if (dataroom.defaultLinkPermission !== "use_default_permissions" && dataroom.defaultLinkPermission !== "use_simple_permissions") {
return res.status(400).json({
message: "Dataroom is not configured to use default permissions for PermissionGroups"
});
}
// Get all permission groups with their default permissions
const permissionGroups = await prisma.permissionGroup.findMany({
where: {
dataroomId,
},
select: {
id: true,
defaultCanView: true,
defaultCanDownload: true,
},
});
if (permissionGroups.length === 0) {
return res.status(200).json({
message: "No permission groups found, no permissions applied"
});
}
// Get dataroom document IDs for the provided document IDs
const dataroomDocuments = await prisma.dataroomDocument.findMany({
where: {
documentId: {
in: documentIds,
},
dataroomId,
},
select: {
id: true,
documentId: true,
},
});
if (dataroomDocuments.length === 0) {
return res.status(404).json({
message: "No documents found in this dataroom"
});
}
// Build permissions to create
const permissionsToCreate: {
groupId: string;
itemId: string;
itemType: ItemType;
canView: boolean;
canDownload: boolean;
canDownloadOriginal: boolean;
}[] = [];
if (dataroom.defaultLinkPermission === "use_simple_permissions") {
// For simple permissions, use global dataroom settings for all groups
const globalCanView = dataroom.defaultLinkCanView ?? false;
const globalCanDownload = dataroom.defaultLinkCanDownload ?? false;
if (globalCanView || globalCanDownload) {
permissionGroups.forEach((group) => {
dataroomDocuments.forEach((doc) => {
permissionsToCreate.push({
groupId: group.id,
itemId: doc.id,
itemType: ItemType.DATAROOM_DOCUMENT,
// If download is enabled, automatically enable view
canView: globalCanView || globalCanDownload,
canDownload: globalCanDownload,
canDownloadOriginal: false,
});
});
});
}
} else {
// For other strategies, use individual group defaults
permissionGroups.forEach((group) => {
// Only create permissions if the group has view or download enabled
if (group.defaultCanView || group.defaultCanDownload) {
dataroomDocuments.forEach((doc) => {
const canDownload = group.defaultCanDownload ?? false;
const canView = group.defaultCanView ?? false;
permissionsToCreate.push({
groupId: group.id,
itemId: doc.id,
itemType: ItemType.DATAROOM_DOCUMENT,
// If download is enabled, automatically enable view
canView: canView || canDownload,
canDownload: canDownload,
canDownloadOriginal: false,
});
});
}
});
}
if (permissionsToCreate.length === 0) {
return res.status(200).json({
message: "No default permissions configured, no permissions applied"
});
}
// Apply permissions in a transaction
await prisma.$transaction(async (tx) => {
// First, remove any existing permissions for these documents and groups
await tx.permissionGroupAccessControls.deleteMany({
where: {
itemId: {
in: dataroomDocuments.map(doc => doc.id),
},
itemType: ItemType.DATAROOM_DOCUMENT,
groupId: {
in: permissionGroups.map(group => group.id),
},
},
});
await tx.permissionGroupAccessControls.createMany({
data: permissionsToCreate,
skipDuplicates: true,
});
});
res.status(200).json({
message: "Default permissions applied successfully",
appliedPermissions: permissionsToCreate.length,
documentsProcessed: dataroomDocuments.length,
groupsProcessed: permissionGroups.length,
});
} catch (error) {
console.error("Error applying default permissions:", error);
errorhandler(error, res);
}
}

View File

@@ -1,415 +0,0 @@
import { NextApiRequest, NextApiResponse } from "next";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import { ItemType } from "@prisma/client";
import { getServerSession } from "next-auth/next";
import { errorhandler } from "@/lib/errorHandler";
import prisma from "@/lib/prisma";
import { CustomUser } from "@/lib/types";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse,
) {
if (req.method !== "POST") {
res.setHeader("Allow", ["POST"]);
return res.status(405).end(`Method ${req.method} Not Allowed`);
}
const session = await getServerSession(req, res, authOptions);
if (!session) {
return res.status(401).end("Unauthorized");
}
const { teamId, id: dataroomId } = req.query as {
teamId: string;
id: string;
};
const userId = (session.user as CustomUser).id;
try {
const { documentIds, folderPath } = req.body as {
documentIds: string[];
folderPath: string;
};
if (!documentIds || !Array.isArray(documentIds) || documentIds.length === 0) {
return res.status(400).json({ message: "Document IDs are required" });
}
if (!folderPath) {
return res.status(400).json({ message: "Folder path is required" });
}
const team = await prisma.team.findFirst({
where: {
id: teamId,
users: {
some: {
userId,
},
},
},
});
if (!team) {
return res.status(403).json({ message: "Unauthorized" });
}
const dataroom = await prisma.dataroom.findUnique({
where: {
id: dataroomId,
},
select: {
id: true,
teamId: true,
defaultGroupPermission: true,
defaultLinkPermission: true,
defaultLinkCanView: true,
defaultLinkCanDownload: true,
},
});
if (!dataroom || dataroom.teamId !== teamId) {
return res.status(404).json({ message: "Dataroom not found" });
}
const permissionGroups = await prisma.permissionGroup.findMany({
where: {
dataroomId,
},
select: {
id: true,
},
});
if (permissionGroups.length === 0) {
return res.status(200).json({
message: "No permission groups found, no permissions applied"
});
}
const pathSegments = folderPath.split("/").filter(Boolean);
let parentFolder: { id: string } | null = null;
let parentPath: string;
if (pathSegments.length <= 1) {
// since there's no parent to inherit from
if (dataroom.defaultLinkPermission === "inherit_from_parent") {
const permissionsToCreate: {
groupId: string;
itemId: string;
itemType: ItemType;
canView: boolean;
canDownload: boolean;
canDownloadOriginal: boolean;
}[] = [];
// Get dataroom documents for the provided document IDs
const newDataroomDocuments = await prisma.dataroomDocument.findMany({
where: {
documentId: {
in: documentIds,
},
dataroomId,
},
select: {
id: true,
documentId: true,
},
});
if (newDataroomDocuments.length === 0) {
return res.status(404).json({
message: "No documents found in this dataroom"
});
}
// For root level with inherit_from_parent, use canView: true, canDownload: false
permissionGroups.forEach((group) => {
newDataroomDocuments.forEach((doc) => {
permissionsToCreate.push({
groupId: group.id,
itemId: doc.id,
itemType: ItemType.DATAROOM_DOCUMENT,
canView: true,
canDownload: false,
canDownloadOriginal: false,
});
});
});
await prisma.$transaction(async (tx) => {
await tx.permissionGroupAccessControls.deleteMany({
where: {
itemId: {
in: newDataroomDocuments.map(doc => doc.id),
},
itemType: ItemType.DATAROOM_DOCUMENT,
groupId: {
in: permissionGroups.map(group => group.id),
},
},
});
await tx.permissionGroupAccessControls.createMany({
data: permissionsToCreate,
skipDuplicates: true,
});
});
return res.status(200).json({
message: "View-only permissions applied for root-level folder (inherit_from_parent strategy)",
appliedPermissions: permissionsToCreate.length,
documentsProcessed: newDataroomDocuments.length,
groupsProcessed: permissionGroups.length,
});
} else {
// For other strategies (use_default_permissions, use_simple_permissions), use default permissions
const permissionsToCreate: {
groupId: string;
itemId: string;
itemType: ItemType;
canView: boolean;
canDownload: boolean;
canDownloadOriginal: boolean;
}[] = [];
// Get dataroom documents for the provided document IDs
const newDataroomDocuments = await prisma.dataroomDocument.findMany({
where: {
documentId: {
in: documentIds,
},
dataroomId,
},
select: {
id: true,
documentId: true,
},
});
if (newDataroomDocuments.length === 0) {
return res.status(404).json({
message: "No documents found in this dataroom"
});
}
// Get permission groups with their default permissions
const permissionGroupsWithDefaults = await prisma.permissionGroup.findMany({
where: {
dataroomId,
},
select: {
id: true,
defaultCanView: true,
defaultCanDownload: true,
},
});
// Build permissions using appropriate strategy for root-level folder
if (dataroom.defaultLinkPermission === "use_simple_permissions") {
// For simple permissions, use global dataroom settings for all groups
const globalCanView = dataroom.defaultLinkCanView ?? false;
const globalCanDownload = dataroom.defaultLinkCanDownload ?? false;
if (globalCanView || globalCanDownload) {
permissionGroupsWithDefaults.forEach((group) => {
newDataroomDocuments.forEach((doc) => {
permissionsToCreate.push({
groupId: group.id,
itemId: doc.id,
itemType: ItemType.DATAROOM_DOCUMENT,
// If download is enabled, automatically enable view
canView: globalCanView || globalCanDownload,
canDownload: globalCanDownload,
canDownloadOriginal: false,
});
});
});
}
} else {
// For other strategies, use individual group defaults
permissionGroupsWithDefaults.forEach((group) => {
newDataroomDocuments.forEach((doc) => {
const canDownload = group.defaultCanDownload ?? false;
const canView = group.defaultCanView ?? false;
permissionsToCreate.push({
groupId: group.id,
itemId: doc.id,
itemType: ItemType.DATAROOM_DOCUMENT,
// If download is enabled, automatically enable view
canView: canView || canDownload,
canDownload: canDownload,
canDownloadOriginal: false,
});
});
});
}
if (permissionsToCreate.length === 0) {
return res.status(200).json({
message: "No default permissions configured for permission groups"
});
}
// Apply default permissions in a transaction
await prisma.$transaction(async (tx) => {
// First, remove any existing permissions for these documents and groups
await tx.permissionGroupAccessControls.deleteMany({
where: {
itemId: {
in: newDataroomDocuments.map(doc => doc.id),
},
itemType: ItemType.DATAROOM_DOCUMENT,
groupId: {
in: permissionGroupsWithDefaults.map(group => group.id),
},
},
});
// Then create the default permissions
await tx.permissionGroupAccessControls.createMany({
data: permissionsToCreate,
skipDuplicates: true,
});
});
return res.status(200).json({
message: "Default permissions applied for root-level folder",
appliedPermissions: permissionsToCreate.length,
documentsProcessed: newDataroomDocuments.length,
groupsProcessed: permissionGroupsWithDefaults.length,
});
}
}
// Get parent folder path for non-root folders
parentPath = "/" + pathSegments.slice(0, -1).join("/");
parentFolder = await prisma.dataroomFolder.findUnique({
where: {
dataroomId_path: {
dataroomId,
path: parentPath,
},
},
select: {
id: true,
},
});
if (!parentFolder) {
return res.status(404).json({
message: "Parent folder not found"
});
}
// Get dataroom documents for the provided document IDs
const newDataroomDocuments = await prisma.dataroomDocument.findMany({
where: {
documentId: {
in: documentIds,
},
dataroomId,
},
select: {
id: true,
documentId: true,
},
});
if (newDataroomDocuments.length === 0) {
return res.status(404).json({
message: "No documents found in this dataroom"
});
}
// Get all permissions for the parent folder
const parentFolderPermissions = await prisma.permissionGroupAccessControls.findMany({
where: {
itemId: parentFolder.id,
itemType: ItemType.DATAROOM_FOLDER,
groupId: {
in: permissionGroups.map(group => group.id),
},
},
select: {
groupId: true,
canView: true,
canDownload: true,
canDownloadOriginal: true,
},
});
if (parentFolderPermissions.length === 0) {
return res.status(200).json({
message: "Parent folder has no permissions to inherit."
});
}
// Build permissions to create for new documents
const permissionsToCreate: {
groupId: string;
itemId: string;
itemType: ItemType;
canView: boolean;
canDownload: boolean;
canDownloadOriginal: boolean;
}[] = [];
parentFolderPermissions.forEach((permission) => {
newDataroomDocuments.forEach((doc) => {
permissionsToCreate.push({
groupId: permission.groupId,
itemId: doc.id,
itemType: ItemType.DATAROOM_DOCUMENT,
canView: permission.canView,
canDownload: permission.canDownload,
canDownloadOriginal: permission.canDownloadOriginal,
});
});
});
if (permissionsToCreate.length === 0) {
return res.status(200).json({
message: "No consistent permissions found to inherit"
});
}
// Apply inherited permissions in a transaction
await prisma.$transaction(async (tx) => {
// First, remove any existing permissions for these documents and groups
await tx.permissionGroupAccessControls.deleteMany({
where: {
itemId: {
in: newDataroomDocuments.map(doc => doc.id),
},
itemType: ItemType.DATAROOM_DOCUMENT,
groupId: {
in: permissionGroups.map(group => group.id),
},
},
});
// Then create the inherited permissions
await tx.permissionGroupAccessControls.createMany({
data: permissionsToCreate,
skipDuplicates: true,
});
});
res.status(200).json({
message: "Parent permissions inherited successfully",
appliedPermissions: permissionsToCreate.length,
documentsProcessed: newDataroomDocuments.length,
groupsProcessed: permissionGroups.length,
});
} catch (error) {
console.error("Error inheriting parent permissions:", error);
errorhandler(error, res);
}
}

View File

@@ -1,40 +0,0 @@
import { useDataroom } from "@/lib/swr/use-dataroom";
import { DataroomHeader } from "@/components/datarooms/dataroom-header";
import { DataroomNavigation } from "@/components/datarooms/dataroom-navigation";
import LinkPermissionSettings from "@/components/datarooms/settings/link-permission-settings";
import SettingsTabs from "@/components/datarooms/settings/settings-tabs";
import AppLayout from "@/components/layouts/app";
export default function LinkPermissionSettingsPage() {
const { dataroom } = useDataroom();
if (!dataroom) {
return <div>Loading...</div>;
}
return (
<AppLayout>
<main className="relative mx-2 mb-10 mt-4 space-y-8 overflow-hidden px-1 sm:mx-3 md:mx-5 md:mt-5 lg:mx-7 lg:mt-8 xl:mx-10">
<header>
<DataroomHeader
title={dataroom.name}
description={dataroom.pId}
actions={[]}
/>
<DataroomNavigation dataroomId={dataroom.id} />
</header>
<div className="mx-auto grid w-full gap-2">
<h1 className="text-2xl font-semibold">Settings</h1>
</div>
<div className="mx-auto grid w-full items-start gap-6 md:grid-cols-[180px_1fr] lg:grid-cols-[250px_1fr]">
<SettingsTabs dataroomId={dataroom.id} />
<div className="grid gap-6">
<LinkPermissionSettings dataroomId={dataroom.id} />
</div>
</div>
</main>
</AppLayout>
);
}