Some checks failed
CD / Build and push images (push) Successful in 3m5s
CI / Lint, typecheck, test (push) Successful in 2m31s
CI / Auth e2e pack (push) Failing after 2m0s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m14s
CD / Promote to Int (push) Successful in 12s
Pond Admins configure the vision's fine-grained cases through a plain-language surface, on top of the base roles from #54. - shared: `AccessRuleView` (a grant enriched with subject/scope display names) and pure conflict helpers `scopeSpecificity`/`sameGrantSubject`/ `isRuleShadowed` (unit-tested) for the client-side shadowed-rule hint. New `access` i18n namespace (de+en) with sentence templates (ADR 0012). - api: `GET /ponds/:id/grants/access-rules` (Pond-Admin) returns the pond's grants enriched with each user's display name and each label/page scope's name, resolved in one batched query per kind. - web `access/`: `AccessRulesManager` in Pond Settings — the pond's rules grouped by subject and rendered as readable de/en sentences ("Anna may not edit pages labeled “Confidential”"), an add form (subject = member or the `signed-in`/`public` pseudo-subjects; scope = label from the tree or a specific page; role; allow/deny) that warns when a rule would be shadowed by a more specific existing one (shared algorithm) and requires an explicit confirmation before granting anything to `public`. Semantics are the shared resolver's — the UI only reflects permissions.md. - tests: shared `conflicts.test.ts`; an api db case for the enriched endpoint; a browser `access-rules` pack that configures BOTH vision patterns through the UI and verifies their effect end to end — "deny label X" (an editor loses a labelled page) and "only label Y" (a signed-in non-member, new `fixture-viewer`, reads only the labelled pages) — plus the shadow hint and the public confirmation, with its own CI step. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
39 lines
1.4 KiB
TypeScript
39 lines
1.4 KiB
TypeScript
import type { AccessRuleView, CreateGrantInput } from '@dorfteich/shared';
|
|
import { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';
|
|
|
|
import { apiDelete, apiGet, apiPost } from '../lib/api';
|
|
|
|
/** Query key for a pond's enriched access rules (issue #55). */
|
|
export const accessRulesKey = (pondId: string): string[] => ['access-rules', pondId];
|
|
|
|
/** The pond's grants enriched with subject/scope names (Pond-Admin only). */
|
|
export function useAccessRules(pondId: string | undefined): UseQueryResult<AccessRuleView[]> {
|
|
return useQuery({
|
|
queryKey: accessRulesKey(pondId ?? ''),
|
|
queryFn: () => apiGet<AccessRuleView[]>(`/ponds/${pondId}/grants/access-rules`),
|
|
enabled: Boolean(pondId),
|
|
retry: false, // a non-admin gets 403 — do not hammer the endpoint
|
|
});
|
|
}
|
|
|
|
/** Add/remove access-rule grants; each refreshes the rule list. */
|
|
export function useAccessRuleMutations(pondId: string): {
|
|
add: (input: CreateGrantInput) => Promise<void>;
|
|
remove: (grantId: string) => Promise<void>;
|
|
} {
|
|
const queryClient = useQueryClient();
|
|
const invalidate = (): Promise<void> =>
|
|
queryClient.invalidateQueries({ queryKey: accessRulesKey(pondId) });
|
|
|
|
return {
|
|
add: async (input) => {
|
|
await apiPost(`/ponds/${pondId}/grants`, input);
|
|
await invalidate();
|
|
},
|
|
remove: async (grantId) => {
|
|
await apiDelete(`/ponds/${pondId}/grants/${grantId}`);
|
|
await invalidate();
|
|
},
|
|
};
|
|
}
|