sprint4+5: field-level permissions complete + universal ShareDialog frontend

This commit is contained in:
Agent Zero
2026-07-29 02:14:26 +02:00
parent b06aeeb720
commit 71ed592aa2
6 changed files with 748 additions and 3 deletions
+131
View File
@@ -0,0 +1,131 @@
/**
* React Query hooks for the universal entity permission API.
*/
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import {
fetchEntityPermissions,
createEntityPermission,
updateEntityPermission,
deleteEntityPermission,
fetchEntityAccess,
fetchEntityRegistry,
type CreateEntityPermissionPayload,
type UpdateEntityPermissionPayload,
} from './entityPermissions';
// ─── Query Key Factory ─────────────────────────────────────────────────────
export const entityPermissionKeys = {
all: ['entityPermissions'] as const,
list: (entityType: string, entityId: string) =>
[...entityPermissionKeys.all, entityType, entityId] as const,
access: (entityType: string, entityId: string) =>
['entityAccess', entityType, entityId] as const,
registry: () => ['entityRegistry'] as const,
};
// ─── Hooks ─────────────────────────────────────────────────────────────────
/**
* Fetch all permissions for a given entity.
*/
export function useEntityPermissions(entityType: string, entityId: string) {
return useQuery({
queryKey: entityPermissionKeys.list(entityType, entityId),
queryFn: () => fetchEntityPermissions(entityType, entityId),
enabled: !!entityType && !!entityId,
});
}
/**
* Create a new permission on an entity.
*/
export function useCreateEntityPermission() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
entityType,
entityId,
data,
}: {
entityType: string;
entityId: string;
data: CreateEntityPermissionPayload;
}) => createEntityPermission(entityType, entityId, data),
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: entityPermissionKeys.list(variables.entityType, variables.entityId),
});
},
});
}
/**
* Update an existing permission on an entity.
*/
export function useUpdateEntityPermission() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
entityType,
entityId,
permId,
data,
}: {
entityType: string;
entityId: string;
permId: string;
data: UpdateEntityPermissionPayload;
}) => updateEntityPermission(entityType, entityId, permId, data),
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: entityPermissionKeys.list(variables.entityType, variables.entityId),
});
},
});
}
/**
* Delete a permission from an entity.
*/
export function useDeleteEntityPermission() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: ({
entityType,
entityId,
permId,
}: {
entityType: string;
entityId: string;
permId: string;
}) => deleteEntityPermission(entityType, entityId, permId),
onSuccess: (_data, variables) => {
queryClient.invalidateQueries({
queryKey: entityPermissionKeys.list(variables.entityType, variables.entityId),
});
},
});
}
/**
* Fetch access info for the current user on an entity.
*/
export function useEntityAccess(entityType: string, entityId: string) {
return useQuery({
queryKey: entityPermissionKeys.access(entityType, entityId),
queryFn: () => fetchEntityAccess(entityType, entityId),
enabled: !!entityType && !!entityId,
});
}
/**
* Fetch the registry of all supported entity types.
*/
export function useEntityRegistry() {
return useQuery({
queryKey: entityPermissionKeys.registry(),
queryFn: () => fetchEntityRegistry(),
});
}
+138
View File
@@ -0,0 +1,138 @@
/**
* Universal Entity Permission API client.
*
* Works with any entity type via the generic permissions endpoint:
* /api/v1/permissions/{entity_type}/{entity_id}
*/
import { apiDelete, apiGet, apiPost, apiPut } from './client';
// ─── Types ─────────────────────────────────────────────────────────────────
export type PrincipalType = 'user' | 'group';
export type PermissionLevel = 'read' | 'write' | 'admin' | 'delete';
export interface EntityPermission {
id: string;
entity_type: string;
entity_id: string;
user_id: string | null;
group_id: string | null;
principal_type: PrincipalType;
user_name: string | null;
group_name: string | null;
user_email: string | null;
permission_level: PermissionLevel;
expires_at: string | null;
created_at: string | null;
updated_at: string | null;
}
export interface EntityAccessInfo {
entity_type: string;
entity_id: string;
access_level: PermissionLevel | 'owner' | 'none';
is_owner: boolean;
is_shared: boolean;
owner_name: string | null;
owner_email: string | null;
}
export interface EntityRegistryEntry {
entity_type: string;
display_name: string;
description: string | null;
}
export interface EntityPermissionListResponse {
items: EntityPermission[];
total: number;
}
export interface CreateEntityPermissionPayload {
user_id?: string;
group_id?: string;
permission_level: PermissionLevel;
expires_at?: string | null;
}
export interface UpdateEntityPermissionPayload {
permission_level?: PermissionLevel;
expires_at?: string | null;
}
// ─── API Functions ─────────────────────────────────────────────────────────
/**
* Fetch all permissions for a given entity.
*/
export function fetchEntityPermissions(
entityType: string,
entityId: string
): Promise<EntityPermissionListResponse> {
return apiGet<EntityPermissionListResponse>(
`/permissions/${entityType}/${entityId}`
);
}
/**
* Create a new permission on an entity.
*/
export function createEntityPermission(
entityType: string,
entityId: string,
data: CreateEntityPermissionPayload
): Promise<EntityPermission> {
return apiPost<EntityPermission>(
`/permissions/${entityType}/${entityId}`,
data
);
}
/**
* Update an existing permission on an entity.
*/
export function updateEntityPermission(
entityType: string,
entityId: string,
permId: string,
data: UpdateEntityPermissionPayload
): Promise<EntityPermission> {
return apiPut<EntityPermission>(
`/permissions/${entityType}/${entityId}/${permId}`,
data
);
}
/**
* Delete a permission from an entity.
*/
export function deleteEntityPermission(
entityType: string,
entityId: string,
permId: string
): Promise<void> {
return apiDelete<void>(
`/permissions/${entityType}/${entityId}/${permId}`
);
}
/**
* Fetch access info for the current user on an entity.
*/
export function fetchEntityAccess(
entityType: string,
entityId: string
): Promise<EntityAccessInfo> {
return apiGet<EntityAccessInfo>(
`/permissions/${entityType}/${entityId}/access`
);
}
/**
* Fetch the registry of all supported entity types.
*/
export function fetchEntityRegistry(): Promise<EntityRegistryEntry[]> {
return apiGet<EntityRegistryEntry[]>('/permissions/registry');
}