mirror of
https://github.com/mfts/papermark.git
synced 2025-12-20 01:03:24 +08:00
feat: unify link and group permissions default settings
This commit is contained in:
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
|
||||
312
pages/api/teams/[teamId]/datarooms/[id]/apply-permissions.ts
Normal file
312
pages/api/teams/[teamId]/datarooms/[id]/apply-permissions.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 }),
|
||||
},
|
||||
|
||||
@@ -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" });
|
||||
}
|
||||
}
|
||||
@@ -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" });
|
||||
}
|
||||
}
|
||||
@@ -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 }),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user