feat: folder permissions (ACLs) - share folders with users/groups, inherit to subfolders, permission dialog UI

This commit is contained in:
Agent Zero
2026-07-28 23:58:19 +02:00
parent 784a771039
commit cc021cda99
12 changed files with 1185 additions and 4 deletions
+48
View File
@@ -11,6 +11,30 @@ export interface ContactFolder {
user_id: string;
sort_order: number;
contact_count: number;
access_level?: string; // owner | admin | write | read | none
is_shared?: boolean;
}
export type FolderPermissionLevel = 'none' | 'read' | 'write' | 'admin';
export interface FolderPermission {
id: string;
folder_id: string;
user_id: string | null;
group_id: string | null;
user_name: string | null;
group_name: string | null;
permission_level: FolderPermissionLevel;
inherit_to_subfolders: boolean;
created_at: string | null;
}
export interface FolderAccessInfo {
folder_id: string;
access_level: string;
is_owner: boolean;
is_shared: boolean;
inherited_from: string | null;
}
export interface ContactFolderTreeNode extends ContactFolder {
@@ -39,6 +63,30 @@ export const reorderContactFolders = (folderId: string, orders: { id: string; so
export const moveContactToFolder = (contactId: string, folderId: string | null) =>
apiPut<{ id: string; folder_id: string | null }>(`/contact-folders/contacts/${contactId}/move`, { folder_id: folderId });
// ── Folder Permissions ──
export const fetchFolderPermissions = (folderId: string) =>
apiGet<{ items: FolderPermission[]; total: number }>(`/contact-folders/${folderId}/permissions`);
export const createFolderPermission = (
folderId: string,
data: { user_id?: string; group_id?: string; permission_level: FolderPermissionLevel; inherit_to_subfolders?: boolean }
) =>
apiPost<FolderPermission>(`/contact-folders/${folderId}/permissions`, data);
export const updateFolderPermission = (
folderId: string,
permissionId: string,
data: { permission_level: FolderPermissionLevel; inherit_to_subfolders?: boolean }
) =>
apiPut<FolderPermission>(`/contact-folders/${folderId}/permissions/${permissionId}`, data);
export const deleteFolderPermission = (folderId: string, permissionId: string) =>
apiDelete(`/contact-folders/${folderId}/permissions/${permissionId}`);
export const fetchFolderAccess = (folderId: string) =>
apiGet<FolderAccessInfo>(`/contact-folders/${folderId}/access`);
// ── Tree builder ──
export function buildFolderTree(folders: ContactFolder[]): ContactFolderTreeNode[] {
+69
View File
@@ -112,3 +112,72 @@ export function useMoveContactToFolder() {
},
});
}
// ── Folder Permissions ──
export function useFolderPermissions(folderId: string | null) {
return useQuery({
queryKey: ['folderPermissions', folderId],
queryFn: async () => {
if (!folderId) return { items: [], total: 0 };
const res = await apiGet<{ items: import('./contactFolders').FolderPermission[]; total: number }>(
`/contact-folders/${folderId}/permissions`
);
return res;
},
enabled: !!folderId,
});
}
export function useCreateFolderPermission() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
folderId,
data,
}: {
folderId: string;
data: {
user_id?: string;
group_id?: string;
permission_level: string;
inherit_to_subfolders?: boolean;
};
}) => apiPost(`/contact-folders/${folderId}/permissions`, data),
onSuccess: (_data, vars) => {
queryClient.invalidateQueries({ queryKey: ['folderPermissions', vars.folderId] });
queryClient.invalidateQueries({ queryKey: ['contactFolders'] });
},
});
}
export function useUpdateFolderPermission() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
folderId,
permissionId,
data,
}: {
folderId: string;
permissionId: string;
data: { permission_level: string; inherit_to_subfolders?: boolean };
}) => apiPut(`/contact-folders/${folderId}/permissions/${permissionId}`, data),
onSuccess: (_data, vars) => {
queryClient.invalidateQueries({ queryKey: ['folderPermissions', vars.folderId] });
queryClient.invalidateQueries({ queryKey: ['contactFolders'] });
},
});
}
export function useDeleteFolderPermission() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({ folderId, permissionId }: { folderId: string; permissionId: string }) =>
apiDelete(`/contact-folders/${folderId}/permissions/${permissionId}`),
onSuccess: (_data, vars) => {
queryClient.invalidateQueries({ queryKey: ['folderPermissions', vars.folderId] });
queryClient.invalidateQueries({ queryKey: ['contactFolders'] });
},
});
}