@carlonicora/nextjs-jsonapi 1.78.0 → 1.79.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{AssistantMessageInterface-DS_tyJTV.d.ts → AssistantMessageInterface-DWnbd6J7.d.ts} +1 -1
- package/dist/{AssistantMessageInterface-D0Kwf8CR.d.mts → AssistantMessageInterface-Mla6kgPe.d.mts} +1 -1
- package/dist/{AuthComponent-Blbs06ud.d.ts → AuthComponent-B6DIk8Vf.d.ts} +1 -1
- package/dist/{AuthComponent-huIaK5rm.d.mts → AuthComponent-BKI0ZbtD.d.mts} +1 -1
- package/dist/{BlockNoteEditor-JXK3JGKJ.mjs → BlockNoteEditor-6CBDTVKV.mjs} +4 -4
- package/dist/{BlockNoteEditor-2G5UYALC.js → BlockNoteEditor-EH4HWI7H.js} +14 -14
- package/dist/{BlockNoteEditor-2G5UYALC.js.map → BlockNoteEditor-EH4HWI7H.js.map} +1 -1
- package/dist/RbacTypes-BTbr27Ew.d.mts +43 -0
- package/dist/RbacTypes-BTbr27Ew.d.ts +43 -0
- package/dist/{auth.interface-CQJ6A2Cj.d.ts → auth.interface-BBUgMZzs.d.ts} +1 -1
- package/dist/{auth.interface-Bdq7-8iV.d.mts → auth.interface-XYEREOD6.d.mts} +1 -1
- package/dist/billing/index.js +346 -346
- package/dist/billing/index.mjs +3 -3
- package/dist/{chunk-FDJQRIMY.js → chunk-5IEWLLLD.js} +61 -2
- package/dist/chunk-5IEWLLLD.js.map +1 -0
- package/dist/{chunk-I65SSQ5Z.mjs → chunk-BKM5U3DE.mjs} +60 -1
- package/dist/chunk-BKM5U3DE.mjs.map +1 -0
- package/dist/{chunk-NB6TIKHK.mjs → chunk-ENRSFVOS.mjs} +2064 -2295
- package/dist/chunk-ENRSFVOS.mjs.map +1 -0
- package/dist/{chunk-NZOUEN67.mjs → chunk-MEWXQEVE.mjs} +38 -29
- package/dist/{chunk-NZOUEN67.mjs.map → chunk-MEWXQEVE.mjs.map} +1 -1
- package/dist/{chunk-X4YDETTD.js → chunk-TWDSDTHU.js} +39 -30
- package/dist/chunk-TWDSDTHU.js.map +1 -0
- package/dist/{chunk-ZEDB6JVB.js → chunk-ZDP3MBUI.js} +1142 -1373
- package/dist/chunk-ZDP3MBUI.js.map +1 -0
- package/dist/client/index.d.mts +6 -24
- package/dist/client/index.d.ts +6 -24
- package/dist/client/index.js +4 -10
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +3 -9
- package/dist/components/index.d.mts +32 -34
- package/dist/components/index.d.ts +32 -34
- package/dist/components/index.js +4 -10
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +3 -9
- package/dist/{config-B3jKt9P7.d.ts → config-B5oBQVEA.d.ts} +1 -1
- package/dist/{config-DkHF61xA.d.mts → config-Bx_uh22h.d.mts} +1 -1
- package/dist/contexts/index.d.mts +41 -4
- package/dist/contexts/index.d.ts +41 -4
- package/dist/contexts/index.js +8 -4
- package/dist/contexts/index.js.map +1 -1
- package/dist/contexts/index.mjs +7 -3
- package/dist/core/index.d.mts +19 -11
- package/dist/core/index.d.ts +19 -11
- package/dist/core/index.js +4 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +3 -1
- package/dist/index.d.mts +117 -20
- package/dist/index.d.ts +117 -20
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +6 -2
- package/dist/{notification.interface-DG6obXUH.d.mts → notification.interface-DLZGtV7Z.d.mts} +1 -1
- package/dist/{notification.interface-DcSuc9CL.d.ts → notification.interface-aLEJbA_g.d.ts} +1 -1
- package/dist/{s3.service-DGilbikH.d.mts → s3.service-CVgLWaDc.d.mts} +2 -2
- package/dist/{s3.service-DjwEQJPe.d.ts → s3.service-SLlX0Zbz.d.ts} +2 -2
- package/dist/server/index.d.mts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +3 -3
- package/dist/server/index.mjs +1 -1
- package/dist/useDataListRetriever-BqJSFBck.d.mts +33 -0
- package/dist/useDataListRetriever-BqJSFBck.d.ts +33 -0
- package/dist/{useSocket-CmzVtg32.d.mts → useSocket-BkxHHujj.d.mts} +1 -1
- package/dist/{useSocket-8eUtnL7J.d.ts → useSocket-CMDjWFYm.d.ts} +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +0 -4
- package/src/components/index.ts +0 -3
- package/src/contexts/index.ts +1 -0
- package/src/core/registry/ModuleRegistry.ts +1 -0
- package/src/features/rbac/components/RbacContainer.tsx +318 -49
- package/src/features/rbac/components/RbacPermissionPicker.tsx +144 -121
- package/src/features/rbac/contexts/RbacContext.tsx +209 -0
- package/src/features/rbac/contexts/index.ts +1 -0
- package/src/features/rbac/data/RbacMatrixModel.ts +84 -0
- package/src/features/rbac/data/RbacService.ts +61 -33
- package/src/features/rbac/data/RbacTypes.ts +28 -0
- package/src/features/rbac/data/index.ts +1 -0
- package/src/features/rbac/index.ts +1 -10
- package/src/features/rbac/rbac.module.ts +13 -0
- package/dist/ModulePathsInterface-BrdqgteS.d.mts +0 -31
- package/dist/ModulePathsInterface-DJKs7s_s.d.ts +0 -31
- package/dist/chunk-FDJQRIMY.js.map +0 -1
- package/dist/chunk-I65SSQ5Z.mjs.map +0 -1
- package/dist/chunk-NB6TIKHK.mjs.map +0 -1
- package/dist/chunk-X4YDETTD.js.map +0 -1
- package/dist/chunk-ZEDB6JVB.js.map +0 -1
- package/dist/useRbacState-C88O-5L8.d.ts +0 -77
- package/dist/useRbacState-mqYiRp3J.d.mts +0 -77
- package/src/features/rbac/components/RbacFeatureSection.tsx +0 -66
- package/src/features/rbac/components/RbacModuleTable.tsx +0 -121
- package/src/features/rbac/components/RbacToolbar.tsx +0 -40
- package/src/features/rbac/hooks/useRbacState.test.ts +0 -180
- package/src/features/rbac/hooks/useRbacState.ts +0 -319
- package/src/features/rbac/utils/RbacMigrationGenerator.test.ts +0 -124
- package/src/features/rbac/utils/RbacMigrationGenerator.ts +0 -184
- /package/dist/{BlockNoteEditor-JXK3JGKJ.mjs.map → BlockNoteEditor-6CBDTVKV.mjs.map} +0 -0
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { F as FeatureInterface } from './feature.interface-BO25VLlx.js';
|
|
2
|
-
import { R as RoleInterface } from './notification.interface-DcSuc9CL.js';
|
|
3
|
-
import { P as PermissionMappingInterface, M as ModulePathsInterface, A as ActionType, a as PermissionValue, b as PermissionsMap } from './ModulePathsInterface-DJKs7s_s.js';
|
|
4
|
-
|
|
5
|
-
type PageInfo = {
|
|
6
|
-
startItem: number;
|
|
7
|
-
endItem: number;
|
|
8
|
-
pageSize: number;
|
|
9
|
-
};
|
|
10
|
-
type DataListRetriever<T> = {
|
|
11
|
-
ready?: boolean;
|
|
12
|
-
setReady: (state: boolean) => void;
|
|
13
|
-
isLoaded: boolean;
|
|
14
|
-
data: T[] | undefined;
|
|
15
|
-
total?: number;
|
|
16
|
-
next?: (onlyNewRecords?: boolean) => Promise<void>;
|
|
17
|
-
previous?: (onlyNewRecords?: boolean) => Promise<void>;
|
|
18
|
-
search: (search: string) => Promise<void>;
|
|
19
|
-
refresh: () => Promise<void>;
|
|
20
|
-
addAdditionalParameter: (key: string, value: any | null) => void;
|
|
21
|
-
removeAdditionalParameter: (key: string) => void;
|
|
22
|
-
setRefreshedElement: (element: T) => void;
|
|
23
|
-
removeElement: (element: T) => void;
|
|
24
|
-
isSearch: boolean;
|
|
25
|
-
pageInfo?: PageInfo;
|
|
26
|
-
};
|
|
27
|
-
declare function useDataListRetriever<T>(params: {
|
|
28
|
-
ready?: boolean;
|
|
29
|
-
retriever: (params: any) => Promise<T[]>;
|
|
30
|
-
retrieverParams?: any;
|
|
31
|
-
search?: string;
|
|
32
|
-
addAdditionalParameter?: (key: string, value: any | null) => void;
|
|
33
|
-
requiresSearch?: boolean;
|
|
34
|
-
module: any;
|
|
35
|
-
}): DataListRetriever<T>;
|
|
36
|
-
|
|
37
|
-
interface OriginalData {
|
|
38
|
-
features: FeatureInterface[];
|
|
39
|
-
roles: RoleInterface[];
|
|
40
|
-
permissionMappings: PermissionMappingInterface[];
|
|
41
|
-
moduleRelationshipPaths: Map<string, string[]>;
|
|
42
|
-
}
|
|
43
|
-
declare function useRbacState(): {
|
|
44
|
-
original: OriginalData | null;
|
|
45
|
-
isDirty: boolean;
|
|
46
|
-
init: (features: FeatureInterface[], roles: RoleInterface[], permissionMappings: PermissionMappingInterface[], modulePaths: ModulePathsInterface[]) => void;
|
|
47
|
-
setFeatureIsCore: (featureId: string, isCore: boolean) => void;
|
|
48
|
-
setModuleDefaultPermission: (moduleId: string, actionType: ActionType, value: PermissionValue) => void;
|
|
49
|
-
setRolePermission: (roleId: string, moduleId: string, actionType: ActionType, value: PermissionValue) => void;
|
|
50
|
-
clearRolePermission: (roleId: string, moduleId: string, actionType: ActionType) => void;
|
|
51
|
-
clearAllRolePermissions: (roleId: string, moduleId: string) => void;
|
|
52
|
-
resetModulePermissions: (moduleId: string, roles: {
|
|
53
|
-
id: string;
|
|
54
|
-
}[]) => void;
|
|
55
|
-
reset: () => void;
|
|
56
|
-
getFeatureIsCore: (featureId: string) => boolean;
|
|
57
|
-
getModuleDefaultPermission: (moduleId: string, actionType: ActionType) => PermissionValue | undefined;
|
|
58
|
-
getRolePermission: (roleId: string, moduleId: string, actionType: ActionType) => PermissionValue | undefined | null;
|
|
59
|
-
getEffectiveConfiguration: () => {
|
|
60
|
-
features: {
|
|
61
|
-
id: string;
|
|
62
|
-
name: string;
|
|
63
|
-
isCore: boolean;
|
|
64
|
-
modules: {
|
|
65
|
-
id: string;
|
|
66
|
-
name: string;
|
|
67
|
-
permissions: PermissionsMap;
|
|
68
|
-
}[];
|
|
69
|
-
}[];
|
|
70
|
-
roles: RoleInterface[];
|
|
71
|
-
rolePermissionsMap: Map<string, PermissionsMap>;
|
|
72
|
-
} | null;
|
|
73
|
-
getModuleRelationshipPaths: (moduleId: string) => string[];
|
|
74
|
-
};
|
|
75
|
-
type RbacStateApi = ReturnType<typeof useRbacState>;
|
|
76
|
-
|
|
77
|
-
export { type DataListRetriever as D, type RbacStateApi as R, useDataListRetriever as a, useRbacState as u };
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { F as FeatureInterface } from './feature.interface-CXb1-vNq.mjs';
|
|
2
|
-
import { R as RoleInterface } from './notification.interface-DG6obXUH.mjs';
|
|
3
|
-
import { P as PermissionMappingInterface, M as ModulePathsInterface, A as ActionType, a as PermissionValue, b as PermissionsMap } from './ModulePathsInterface-BrdqgteS.mjs';
|
|
4
|
-
|
|
5
|
-
type PageInfo = {
|
|
6
|
-
startItem: number;
|
|
7
|
-
endItem: number;
|
|
8
|
-
pageSize: number;
|
|
9
|
-
};
|
|
10
|
-
type DataListRetriever<T> = {
|
|
11
|
-
ready?: boolean;
|
|
12
|
-
setReady: (state: boolean) => void;
|
|
13
|
-
isLoaded: boolean;
|
|
14
|
-
data: T[] | undefined;
|
|
15
|
-
total?: number;
|
|
16
|
-
next?: (onlyNewRecords?: boolean) => Promise<void>;
|
|
17
|
-
previous?: (onlyNewRecords?: boolean) => Promise<void>;
|
|
18
|
-
search: (search: string) => Promise<void>;
|
|
19
|
-
refresh: () => Promise<void>;
|
|
20
|
-
addAdditionalParameter: (key: string, value: any | null) => void;
|
|
21
|
-
removeAdditionalParameter: (key: string) => void;
|
|
22
|
-
setRefreshedElement: (element: T) => void;
|
|
23
|
-
removeElement: (element: T) => void;
|
|
24
|
-
isSearch: boolean;
|
|
25
|
-
pageInfo?: PageInfo;
|
|
26
|
-
};
|
|
27
|
-
declare function useDataListRetriever<T>(params: {
|
|
28
|
-
ready?: boolean;
|
|
29
|
-
retriever: (params: any) => Promise<T[]>;
|
|
30
|
-
retrieverParams?: any;
|
|
31
|
-
search?: string;
|
|
32
|
-
addAdditionalParameter?: (key: string, value: any | null) => void;
|
|
33
|
-
requiresSearch?: boolean;
|
|
34
|
-
module: any;
|
|
35
|
-
}): DataListRetriever<T>;
|
|
36
|
-
|
|
37
|
-
interface OriginalData {
|
|
38
|
-
features: FeatureInterface[];
|
|
39
|
-
roles: RoleInterface[];
|
|
40
|
-
permissionMappings: PermissionMappingInterface[];
|
|
41
|
-
moduleRelationshipPaths: Map<string, string[]>;
|
|
42
|
-
}
|
|
43
|
-
declare function useRbacState(): {
|
|
44
|
-
original: OriginalData | null;
|
|
45
|
-
isDirty: boolean;
|
|
46
|
-
init: (features: FeatureInterface[], roles: RoleInterface[], permissionMappings: PermissionMappingInterface[], modulePaths: ModulePathsInterface[]) => void;
|
|
47
|
-
setFeatureIsCore: (featureId: string, isCore: boolean) => void;
|
|
48
|
-
setModuleDefaultPermission: (moduleId: string, actionType: ActionType, value: PermissionValue) => void;
|
|
49
|
-
setRolePermission: (roleId: string, moduleId: string, actionType: ActionType, value: PermissionValue) => void;
|
|
50
|
-
clearRolePermission: (roleId: string, moduleId: string, actionType: ActionType) => void;
|
|
51
|
-
clearAllRolePermissions: (roleId: string, moduleId: string) => void;
|
|
52
|
-
resetModulePermissions: (moduleId: string, roles: {
|
|
53
|
-
id: string;
|
|
54
|
-
}[]) => void;
|
|
55
|
-
reset: () => void;
|
|
56
|
-
getFeatureIsCore: (featureId: string) => boolean;
|
|
57
|
-
getModuleDefaultPermission: (moduleId: string, actionType: ActionType) => PermissionValue | undefined;
|
|
58
|
-
getRolePermission: (roleId: string, moduleId: string, actionType: ActionType) => PermissionValue | undefined | null;
|
|
59
|
-
getEffectiveConfiguration: () => {
|
|
60
|
-
features: {
|
|
61
|
-
id: string;
|
|
62
|
-
name: string;
|
|
63
|
-
isCore: boolean;
|
|
64
|
-
modules: {
|
|
65
|
-
id: string;
|
|
66
|
-
name: string;
|
|
67
|
-
permissions: PermissionsMap;
|
|
68
|
-
}[];
|
|
69
|
-
}[];
|
|
70
|
-
roles: RoleInterface[];
|
|
71
|
-
rolePermissionsMap: Map<string, PermissionsMap>;
|
|
72
|
-
} | null;
|
|
73
|
-
getModuleRelationshipPaths: (moduleId: string) => string[];
|
|
74
|
-
};
|
|
75
|
-
type RbacStateApi = ReturnType<typeof useRbacState>;
|
|
76
|
-
|
|
77
|
-
export { type DataListRetriever as D, type RbacStateApi as R, useDataListRetriever as a, useRbacState as u };
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { cn } from "../../../lib/utils";
|
|
4
|
-
import { ChevronDownIcon } from "lucide-react";
|
|
5
|
-
import { useTranslations } from "next-intl";
|
|
6
|
-
import { useState } from "react";
|
|
7
|
-
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "../../../shadcnui";
|
|
8
|
-
import { Switch } from "../../../shadcnui";
|
|
9
|
-
import { FeatureInterface } from "../../feature";
|
|
10
|
-
import { RoleInterface } from "../../role";
|
|
11
|
-
import { RbacStateApi } from "../hooks/useRbacState";
|
|
12
|
-
import RbacModuleTable from "./RbacModuleTable";
|
|
13
|
-
|
|
14
|
-
interface RbacFeatureSectionProps {
|
|
15
|
-
feature: FeatureInterface;
|
|
16
|
-
roles: RoleInterface[];
|
|
17
|
-
stateApi: RbacStateApi;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export default function RbacFeatureSection({ feature, roles, stateApi }: RbacFeatureSectionProps) {
|
|
21
|
-
const t = useTranslations();
|
|
22
|
-
const [isOpen, setIsOpen] = useState(true);
|
|
23
|
-
const featureIsCore = stateApi.getFeatureIsCore(feature.id);
|
|
24
|
-
|
|
25
|
-
return (
|
|
26
|
-
<Collapsible open={isOpen} onOpenChange={setIsOpen}>
|
|
27
|
-
<div className="rounded-lg border bg-card">
|
|
28
|
-
{/* Feature header */}
|
|
29
|
-
<CollapsibleTrigger className="w-full">
|
|
30
|
-
<div className="flex cursor-pointer items-center justify-between px-4 py-3 hover:bg-muted/50 transition-colors">
|
|
31
|
-
<div className="flex items-center gap-3">
|
|
32
|
-
<ChevronDownIcon
|
|
33
|
-
className={cn("h-4 w-4 text-muted-foreground transition-transform", !isOpen && "-rotate-90")}
|
|
34
|
-
/>
|
|
35
|
-
<h3 className="text-base font-semibold">{feature.name}</h3>
|
|
36
|
-
<span className="text-xs text-muted-foreground">
|
|
37
|
-
{t("rbac.module_count", { count: feature.modules.length })}
|
|
38
|
-
</span>
|
|
39
|
-
</div>
|
|
40
|
-
<div className="flex items-center gap-2" onClick={(e) => e.stopPropagation()}>
|
|
41
|
-
<span className="text-xs text-muted-foreground">{t("rbac.core")}</span>
|
|
42
|
-
<Switch
|
|
43
|
-
checked={featureIsCore}
|
|
44
|
-
onCheckedChange={(checked) => stateApi.setFeatureIsCore(feature.id, checked)}
|
|
45
|
-
className="data-checked:bg-accent data-unchecked:bg-gray-300"
|
|
46
|
-
/>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
</CollapsibleTrigger>
|
|
50
|
-
|
|
51
|
-
<CollapsibleContent>
|
|
52
|
-
<div className="space-y-3 p-4 pt-0">
|
|
53
|
-
{feature.modules.map((mod) => (
|
|
54
|
-
<RbacModuleTable key={mod.id} module={mod} roles={roles} stateApi={stateApi} />
|
|
55
|
-
))}
|
|
56
|
-
{feature.modules.length === 0 && (
|
|
57
|
-
<p className="text-sm text-muted-foreground italic py-4 text-center">{t("rbac.no_modules")}</p>
|
|
58
|
-
)}
|
|
59
|
-
</div>
|
|
60
|
-
</CollapsibleContent>
|
|
61
|
-
</div>
|
|
62
|
-
</Collapsible>
|
|
63
|
-
);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export { RbacFeatureSection };
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { RotateCcwIcon } from "lucide-react";
|
|
4
|
-
import { useTranslations } from "next-intl";
|
|
5
|
-
import { Button } from "../../../shadcnui";
|
|
6
|
-
import { ModuleInterface } from "../../module";
|
|
7
|
-
import { RoleInterface } from "../../role";
|
|
8
|
-
import { ACTION_TYPES, ActionType, COMPANY_ADMINISTRATOR_ROLE_ID } from "../data/RbacTypes";
|
|
9
|
-
import { RbacStateApi } from "../hooks/useRbacState";
|
|
10
|
-
import RbacPermissionPicker from "./RbacPermissionPicker";
|
|
11
|
-
|
|
12
|
-
interface RbacModuleTableProps {
|
|
13
|
-
module: ModuleInterface;
|
|
14
|
-
roles: RoleInterface[];
|
|
15
|
-
stateApi: RbacStateApi;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const ACTION_LABELS: Record<ActionType, string> = {
|
|
19
|
-
read: "Read",
|
|
20
|
-
create: "Create",
|
|
21
|
-
update: "Update",
|
|
22
|
-
delete: "Delete",
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export default function RbacModuleTable({ module, roles, stateApi }: RbacModuleTableProps) {
|
|
26
|
-
const t = useTranslations();
|
|
27
|
-
const handleReset = () => {
|
|
28
|
-
stateApi.resetModulePermissions(module.id, roles);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
return (
|
|
32
|
-
<div className="rounded-lg border border-accent bg-card">
|
|
33
|
-
{/* Module header */}
|
|
34
|
-
<div className="flex items-center justify-between border-b px-4 py-2">
|
|
35
|
-
<h4 className="text-sm font-medium">{module.name}</h4>
|
|
36
|
-
<Button
|
|
37
|
-
variant="ghost"
|
|
38
|
-
size="sm"
|
|
39
|
-
onClick={handleReset}
|
|
40
|
-
className="h-7 px-2 text-muted-foreground hover:text-foreground"
|
|
41
|
-
>
|
|
42
|
-
<RotateCcwIcon className="h-3.5 w-3.5" />
|
|
43
|
-
</Button>
|
|
44
|
-
</div>
|
|
45
|
-
|
|
46
|
-
{/* Permission grid - Rows = Default + Roles, Columns = Actions */}
|
|
47
|
-
<div className="overflow-x-auto">
|
|
48
|
-
<table className="w-full text-sm">
|
|
49
|
-
<thead>
|
|
50
|
-
<tr className="border-b bg-muted/50">
|
|
51
|
-
<th className="px-4 py-2 text-left text-xs font-medium text-muted-foreground w-40">{t("rbac.role")}</th>
|
|
52
|
-
{ACTION_TYPES.map((actionType) => (
|
|
53
|
-
<th
|
|
54
|
-
key={actionType}
|
|
55
|
-
className="px-2 py-2 text-center text-xs font-medium text-muted-foreground min-w-28"
|
|
56
|
-
>
|
|
57
|
-
{ACTION_LABELS[actionType]}
|
|
58
|
-
</th>
|
|
59
|
-
))}
|
|
60
|
-
</tr>
|
|
61
|
-
</thead>
|
|
62
|
-
<tbody>
|
|
63
|
-
{/* Default permissions row */}
|
|
64
|
-
<tr className="border-b">
|
|
65
|
-
<td className="px-4 py-1 text-xs font-medium text-muted-foreground">{t("rbac.defaults")}</td>
|
|
66
|
-
{ACTION_TYPES.map((actionType) => {
|
|
67
|
-
const defaultValue = stateApi.getModuleDefaultPermission(module.id, actionType) ?? false;
|
|
68
|
-
const originalDefaultValue = module.permissions[actionType] ?? false;
|
|
69
|
-
|
|
70
|
-
return (
|
|
71
|
-
<td key={actionType} className="px-2 py-1">
|
|
72
|
-
<RbacPermissionPicker
|
|
73
|
-
value={defaultValue}
|
|
74
|
-
originalValue={originalDefaultValue}
|
|
75
|
-
isRoleColumn={false}
|
|
76
|
-
knownSegments={stateApi.getModuleRelationshipPaths(module.id)}
|
|
77
|
-
onSetValue={(value) => stateApi.setModuleDefaultPermission(module.id, actionType, value)}
|
|
78
|
-
/>
|
|
79
|
-
</td>
|
|
80
|
-
);
|
|
81
|
-
})}
|
|
82
|
-
</tr>
|
|
83
|
-
|
|
84
|
-
{/* Role rows (CompanyAdministrator hidden — always all-true in migration) */}
|
|
85
|
-
{roles
|
|
86
|
-
.filter((role) => role.id !== COMPANY_ADMINISTRATOR_ROLE_ID)
|
|
87
|
-
.map((role) => (
|
|
88
|
-
<tr key={role.id} className="border-b last:border-b-0">
|
|
89
|
-
<td className="px-4 py-1 text-xs font-medium text-muted-foreground">{role.name}</td>
|
|
90
|
-
{ACTION_TYPES.map((actionType) => {
|
|
91
|
-
const roleValue = stateApi.getRolePermission(role.id, module.id, actionType);
|
|
92
|
-
const originalMapping = stateApi.original?.permissionMappings.find(
|
|
93
|
-
(pm) => pm.roleId === role.id && pm.moduleId === module.id,
|
|
94
|
-
);
|
|
95
|
-
const originalRoleValue = originalMapping
|
|
96
|
-
? (originalMapping.permissions[actionType] ?? null)
|
|
97
|
-
: undefined;
|
|
98
|
-
|
|
99
|
-
return (
|
|
100
|
-
<td key={actionType} className="px-2 py-1">
|
|
101
|
-
<RbacPermissionPicker
|
|
102
|
-
value={roleValue}
|
|
103
|
-
originalValue={originalRoleValue}
|
|
104
|
-
isRoleColumn={true}
|
|
105
|
-
knownSegments={stateApi.getModuleRelationshipPaths(module.id)}
|
|
106
|
-
onSetValue={(value) => stateApi.setRolePermission(role.id, module.id, actionType, value)}
|
|
107
|
-
onClear={() => stateApi.clearRolePermission(role.id, module.id, actionType)}
|
|
108
|
-
/>
|
|
109
|
-
</td>
|
|
110
|
-
);
|
|
111
|
-
})}
|
|
112
|
-
</tr>
|
|
113
|
-
))}
|
|
114
|
-
</tbody>
|
|
115
|
-
</table>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export { RbacModuleTable };
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { DownloadIcon, RotateCcwIcon } from "lucide-react";
|
|
4
|
-
import { useTranslations } from "next-intl";
|
|
5
|
-
import { Badge, Button } from "../../../shadcnui";
|
|
6
|
-
|
|
7
|
-
interface RbacToolbarProps {
|
|
8
|
-
isDirty: boolean;
|
|
9
|
-
onGenerate: () => void;
|
|
10
|
-
onReset: () => void;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export default function RbacToolbar({ isDirty, onGenerate, onReset }: RbacToolbarProps) {
|
|
14
|
-
const t = useTranslations();
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<div className="flex items-center justify-between rounded-lg border bg-card px-4 py-3">
|
|
18
|
-
<div className="flex items-center gap-3">
|
|
19
|
-
<h2 className="text-lg font-semibold">{t("rbac.title")}</h2>
|
|
20
|
-
{isDirty && (
|
|
21
|
-
<Badge variant="outline" className="border-amber-400 text-amber-600">
|
|
22
|
-
{t("rbac.unsaved_changes")}
|
|
23
|
-
</Badge>
|
|
24
|
-
)}
|
|
25
|
-
</div>
|
|
26
|
-
<div className="flex items-center gap-2">
|
|
27
|
-
<Button variant="outline" size="sm" onClick={onReset} disabled={!isDirty} className="gap-1">
|
|
28
|
-
<RotateCcwIcon className="h-3.5 w-3.5" />
|
|
29
|
-
{t("rbac.reset")}
|
|
30
|
-
</Button>
|
|
31
|
-
<Button size="sm" onClick={onGenerate} disabled={!isDirty} className="gap-1">
|
|
32
|
-
<DownloadIcon className="h-3.5 w-3.5" />
|
|
33
|
-
{t("rbac.generate_migration")}
|
|
34
|
-
</Button>
|
|
35
|
-
</div>
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export { RbacToolbar };
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { renderHook, act } from "@testing-library/react";
|
|
3
|
-
import { useRbacState } from "./useRbacState";
|
|
4
|
-
import { FeatureInterface } from "../../feature";
|
|
5
|
-
import { RoleInterface } from "../../role";
|
|
6
|
-
import { PermissionMappingInterface } from "../data/PermissionMappingInterface";
|
|
7
|
-
import { ModulePathsInterface } from "../data/ModulePathsInterface";
|
|
8
|
-
import { ModuleInterface } from "../../module";
|
|
9
|
-
|
|
10
|
-
const mockModule: ModuleInterface = {
|
|
11
|
-
id: "mod-1",
|
|
12
|
-
type: "modules",
|
|
13
|
-
included: [],
|
|
14
|
-
createdAt: new Date(),
|
|
15
|
-
updatedAt: new Date(),
|
|
16
|
-
identifier: "pipelines",
|
|
17
|
-
name: "pipelines",
|
|
18
|
-
permissions: { create: true, read: true, update: true, delete: false },
|
|
19
|
-
} as ModuleInterface;
|
|
20
|
-
|
|
21
|
-
const mockFeatures: FeatureInterface[] = [
|
|
22
|
-
{
|
|
23
|
-
id: "feat-1",
|
|
24
|
-
type: "features",
|
|
25
|
-
included: [],
|
|
26
|
-
createdAt: new Date(),
|
|
27
|
-
updatedAt: new Date(),
|
|
28
|
-
identifier: "CRM",
|
|
29
|
-
name: "CRM",
|
|
30
|
-
isCore: false,
|
|
31
|
-
modules: [mockModule],
|
|
32
|
-
} as FeatureInterface,
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
const mockRoles: RoleInterface[] = [
|
|
36
|
-
{
|
|
37
|
-
id: "role-1",
|
|
38
|
-
type: "roles",
|
|
39
|
-
included: [],
|
|
40
|
-
createdAt: new Date(),
|
|
41
|
-
updatedAt: new Date(),
|
|
42
|
-
name: "Manager",
|
|
43
|
-
description: "",
|
|
44
|
-
isSelectable: true,
|
|
45
|
-
requiredFeature: undefined,
|
|
46
|
-
} as unknown as RoleInterface,
|
|
47
|
-
{
|
|
48
|
-
id: "role-2",
|
|
49
|
-
type: "roles",
|
|
50
|
-
included: [],
|
|
51
|
-
createdAt: new Date(),
|
|
52
|
-
updatedAt: new Date(),
|
|
53
|
-
name: "Viewer",
|
|
54
|
-
description: "",
|
|
55
|
-
isSelectable: true,
|
|
56
|
-
requiredFeature: undefined,
|
|
57
|
-
} as unknown as RoleInterface,
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
const mockPermissionMappings: PermissionMappingInterface[] = [];
|
|
61
|
-
|
|
62
|
-
const mockModulePaths: ModulePathsInterface[] = [
|
|
63
|
-
{
|
|
64
|
-
id: "mod-1",
|
|
65
|
-
type: "module-paths",
|
|
66
|
-
included: [],
|
|
67
|
-
createdAt: new Date(),
|
|
68
|
-
updatedAt: new Date(),
|
|
69
|
-
moduleId: "mod-1",
|
|
70
|
-
paths: ["owner", "company.user"],
|
|
71
|
-
} as unknown as ModulePathsInterface,
|
|
72
|
-
];
|
|
73
|
-
|
|
74
|
-
function initHook() {
|
|
75
|
-
const { result } = renderHook(() => useRbacState());
|
|
76
|
-
act(() => {
|
|
77
|
-
result.current.init(mockFeatures, mockRoles, mockPermissionMappings, mockModulePaths);
|
|
78
|
-
});
|
|
79
|
-
return result;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
describe("useRbacState", () => {
|
|
83
|
-
describe("Scenario: Initialize state from fetched data", () => {
|
|
84
|
-
it("should initialize with features and roles", () => {
|
|
85
|
-
const result = initHook();
|
|
86
|
-
|
|
87
|
-
expect(result.current.original).not.toBeNull();
|
|
88
|
-
expect(result.current.original!.features).toHaveLength(1);
|
|
89
|
-
expect(result.current.original!.features[0].name).toBe("CRM");
|
|
90
|
-
expect(result.current.isDirty).toBe(false);
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
describe("Scenario: Set module default permission", () => {
|
|
95
|
-
it("should update default permission and mark as dirty", () => {
|
|
96
|
-
const result = initHook();
|
|
97
|
-
|
|
98
|
-
act(() => {
|
|
99
|
-
result.current.setModuleDefaultPermission("mod-1", "delete", true);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
expect(result.current.getModuleDefaultPermission("mod-1", "delete")).toBe(true);
|
|
103
|
-
expect(result.current.isDirty).toBe(true);
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe("Scenario: Set role-specific permission override", () => {
|
|
108
|
-
it("should set role permission for a specific module and action", () => {
|
|
109
|
-
const result = initHook();
|
|
110
|
-
|
|
111
|
-
act(() => {
|
|
112
|
-
result.current.setRolePermission("role-1", "mod-1", "create", false);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
expect(result.current.getRolePermission("role-1", "mod-1", "create")).toBe(false);
|
|
116
|
-
expect(result.current.isDirty).toBe(true);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("should support string path values for role permissions", () => {
|
|
120
|
-
const result = initHook();
|
|
121
|
-
|
|
122
|
-
act(() => {
|
|
123
|
-
result.current.setRolePermission("role-2", "mod-1", "update", "owner");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
expect(result.current.getRolePermission("role-2", "mod-1", "update")).toBe("owner");
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
describe("Scenario: Clear role permission (inherit from default)", () => {
|
|
131
|
-
it("should clear a role permission so it inherits from default", () => {
|
|
132
|
-
const result = initHook();
|
|
133
|
-
|
|
134
|
-
act(() => {
|
|
135
|
-
result.current.setRolePermission("role-1", "mod-1", "create", false);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
act(() => {
|
|
139
|
-
result.current.clearRolePermission("role-1", "mod-1", "create");
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
expect(result.current.getRolePermission("role-1", "mod-1", "create")).toBeNull();
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
describe("Scenario: Reset to initial state", () => {
|
|
147
|
-
it("should reset all changes and clear dirty flag", () => {
|
|
148
|
-
const result = initHook();
|
|
149
|
-
|
|
150
|
-
act(() => {
|
|
151
|
-
result.current.setModuleDefaultPermission("mod-1", "delete", true);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
expect(result.current.isDirty).toBe(true);
|
|
155
|
-
|
|
156
|
-
act(() => {
|
|
157
|
-
result.current.reset();
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
expect(result.current.isDirty).toBe(false);
|
|
161
|
-
expect(result.current.getModuleDefaultPermission("mod-1", "delete")).toBe(false);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
describe("Scenario: Get effective configuration for migration", () => {
|
|
166
|
-
it("should return complete configuration with all changes applied", () => {
|
|
167
|
-
const result = initHook();
|
|
168
|
-
|
|
169
|
-
act(() => {
|
|
170
|
-
result.current.setRolePermission("role-1", "mod-1", "delete", true);
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const config = result.current.getEffectiveConfiguration();
|
|
174
|
-
expect(config).not.toBeNull();
|
|
175
|
-
expect(config!.features).toHaveLength(1);
|
|
176
|
-
expect(config!.roles).toHaveLength(2);
|
|
177
|
-
expect(config!.rolePermissionsMap).toBeInstanceOf(Map);
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
});
|