1+ import { type ProjectDetail } from "@squonk/data-manager-client" ;
12import { useGetProjects } from "@squonk/data-manager-client/project" ;
23
34import { useRouter } from "next/router" ;
@@ -6,11 +7,114 @@ import { PROJECT_LOCAL_STORAGE_KEY, writeToLocalStorage } from "../utils/next/lo
67import { useDMAuthorizationStatus } from "./useIsAuthorized" ;
78import { useKeycloakUser } from "./useKeycloakUser" ;
89
10+ // --- Composable role-checking API ---
11+ export type ProjectRole = "administrator" | "creator" | "editor" | "observer" ;
12+
13+ export const hasProjectRole = (
14+ project : ProjectDetail | null ,
15+ username : string | undefined ,
16+ roles : ProjectRole | ProjectRole [ ] ,
17+ ) : boolean => {
18+ if ( ! project || ! username ) {
19+ return false ;
20+ }
21+ const roleList = Array . isArray ( roles ) ? roles : [ roles ] ;
22+ return roleList . some ( ( role ) => {
23+ switch ( role ) {
24+ case "creator" :
25+ return project . creator === username ;
26+ case "editor" :
27+ return Array . isArray ( project . editors ) && project . editors . includes ( username ) ;
28+ case "administrator" :
29+ return Array . isArray ( project . administrators ) && project . administrators . includes ( username ) ;
30+ case "observer" :
31+ return Array . isArray ( project . observers ) && project . observers . includes ( username ) ;
32+ default :
33+ return false ;
34+ }
35+ } ) ;
36+ } ;
37+
38+ /**
39+ * Hook to check if a user has any of the specified roles on a project by ID.
40+ * @param projectId Project ID
41+ * @param roles Single role or array of roles
42+ * @param username Optional username (defaults to current user)
43+ */
44+ export const useHasProjectRole = (
45+ projectId : string | undefined ,
46+ roles : ProjectRole | ProjectRole [ ] ,
47+ username ?: string ,
48+ ) : boolean => {
49+ const { user } = useKeycloakUser ( ) ;
50+ const project = useProjectFromId ( projectId ?? "" ) ;
51+ const checkUser = username ?? user . username ;
52+ return hasProjectRole ( project , checkUser , roles ) ;
53+ } ;
54+
55+ /**
56+ * Hook to check if a user has any of the specified roles on the current project.
57+ * @param roles Single role or array of roles
58+ * @param username Optional username (defaults to current user)
59+ */
60+ export const useHasProjectRoleOnCurrentProject = (
61+ roles : ProjectRole | ProjectRole [ ] ,
62+ username ?: string ,
63+ ) : boolean => {
64+ const { user } = useKeycloakUser ( ) ;
65+ const project = useCurrentProject ( ) ;
66+ const checkUser = username ?? user . username ;
67+ return hasProjectRole ( project , checkUser , roles ) ;
68+ } ;
69+ export const isProjectCreator = (
70+ project : ProjectDetail | null ,
71+ username : string | undefined ,
72+ ) : boolean => {
73+ return ! ! project && ! ! username && project . creator === username ;
74+ } ;
75+
976export type ProjectId = string | undefined ;
1077export type ProjectLocalStoragePayload = { projectId : ProjectId ; version : number } ;
1178
1279export const projectPayload = ( projectId : ProjectId ) => ( { version : 1 , projectId } ) ;
1380
81+ // --- Pure role-checking utilities ---
82+
83+ export const isProjectEditor = (
84+ project : ProjectDetail | null ,
85+ username : string | undefined ,
86+ ) : boolean => {
87+ return (
88+ ! ! project && ! ! username && Array . isArray ( project . editors ) && project . editors . includes ( username )
89+ ) ;
90+ } ;
91+
92+ export const isProjectAdministrator = (
93+ project : ProjectDetail | null ,
94+ username : string | undefined ,
95+ ) : boolean => {
96+ return (
97+ ! ! project &&
98+ ! ! username &&
99+ Array . isArray ( project . administrators ) &&
100+ project . administrators . includes ( username )
101+ ) ;
102+ } ;
103+
104+ export const isProjectObserver = (
105+ project : ProjectDetail | null ,
106+ username : string | undefined ,
107+ ) : boolean => {
108+ return (
109+ ! ! project &&
110+ ! ! username &&
111+ Array . isArray ( project . observers ) &&
112+ project . observers . includes ( username )
113+ ) ;
114+ } ;
115+
116+ // --- Hooks for getting current project and project ID ---
117+
14118/**
15119 * @returns The selected projectId from the project key of the query parameters
16120 */
@@ -55,43 +159,96 @@ export const useCurrentProjectId = () => {
55159/**
56160 * @returns The project associated with the project-id in the current url query parameters
57161 */
58- export const useCurrentProject = ( ) => {
162+ export const useCurrentProject = ( ) : ProjectDetail | null => {
59163 const isDMAuthorized = useDMAuthorizationStatus ( ) ;
60164 const { projectId } = useCurrentProjectId ( ) ;
61165 const { data } = useGetProjects ( undefined , { query : { enabled : ! ! isDMAuthorized } } ) ;
62166 const projects = data ?. projects ;
63-
64167 return projects ?. find ( ( project ) => project . project_id === projectId ) ?? null ;
65168} ;
66169
67170/**
68171 * @param projectId Id of the project
69172 * @returns The project object matching the ID if it exists
70173 */
71- export const useProjectFromId = ( projectId : string ) => {
174+ export const useProjectFromId = ( projectId : string ) : ProjectDetail | null => {
72175 const { data } = useGetProjects ( ) ;
73-
74176 const projects = data ?. projects ;
75-
76- return projects ?. find ( ( project ) => project . project_id === projectId ) ;
177+ return projects ?. find ( ( project ) => project . project_id === projectId ) ?? null ;
77178} ;
78179
79- export const useIsUserAdminOrEditorOfCurrentProject = ( ) => {
180+ // --- Internal shared hook for role checks ---
181+ const useProjectRoleCheck = (
182+ getProject : ( ) => ProjectDetail | null ,
183+ roleCheck : ( project : ProjectDetail | null , username : string | undefined ) => boolean ,
184+ username ?: string ,
185+ ) : boolean => {
80186 const { user } = useKeycloakUser ( ) ;
81- const project = useCurrentProject ( ) ;
187+ const project = getProject ( ) ;
188+ const checkUser = username ?? user . username ;
189+ return roleCheck ( project , checkUser ) ;
190+ } ;
82191
83- return (
84- ! ! user . username &&
85- ( ! ! project ?. editors . includes ( user . username ) ||
86- ! ! project ?. administrators . includes ( user . username ) )
192+ // --- Hooks for role checks by project ID ---
193+
194+ /**
195+ * Check if a user (or current user) is admin or editor of the project with the given ID
196+ */
197+ export const useIsAdminOrEditorOfProject = ( projectId : string | undefined , username ?: string ) =>
198+ useProjectRoleCheck (
199+ ( ) => useProjectFromId ( projectId ?? "" ) ,
200+ ( project , user ) => isProjectEditor ( project , user ) || isProjectAdministrator ( project , user ) ,
201+ username ,
87202 ) ;
88- } ;
89203
90- export const useIsEditorOfCurrentProject = ( ) => {
91- const currentProject = useCurrentProject ( ) ;
204+ /**
205+ * Check if a user (or current user) is admin or editor of the current project
206+ */
207+ export const useIsUserAdminOrEditorOfCurrentProject = ( username ?: string ) =>
208+ useProjectRoleCheck (
209+ useCurrentProject ,
210+ ( project , user ) => isProjectEditor ( project , user ) || isProjectAdministrator ( project , user ) ,
211+ username ,
212+ ) ;
92213
93- const { user } = useKeycloakUser ( ) ;
94- const isEditor = ! ! user . username && currentProject ?. editors . includes ( user . username ) ;
214+ /**
215+ * Check if a user (or current user) is editor of the current project
216+ */
217+ export const useIsEditorOfCurrentProject = ( username ?: string ) =>
218+ useProjectRoleCheck ( useCurrentProject , isProjectEditor , username ) ;
95219
96- return isEditor ;
97- } ;
220+ /**
221+ * Check if a user (or current user) is creator of the project with the given ID
222+ */
223+ export const useIsCreatorOfProject = ( projectId : string | undefined , username ?: string ) =>
224+ useProjectRoleCheck ( ( ) => useProjectFromId ( projectId ?? "" ) , isProjectCreator , username ) ;
225+
226+ /**
227+ * Check if a user (or current user) is observer of the project with the given ID
228+ */
229+ export const useIsObserverOfProject = ( projectId : string | undefined , username ?: string ) =>
230+ useProjectRoleCheck ( ( ) => useProjectFromId ( projectId ?? "" ) , isProjectObserver , username ) ;
231+
232+ /**
233+ * Check if a user (or current user) is administrator of the project with the given ID
234+ */
235+ export const useIsAdministratorOfProject = ( projectId : string | undefined , username ?: string ) =>
236+ useProjectRoleCheck ( ( ) => useProjectFromId ( projectId ?? "" ) , isProjectAdministrator , username ) ;
237+
238+ /**
239+ * Check if a user (or current user) is creator of the current project
240+ */
241+ export const useIsCreatorOfCurrentProject = ( username ?: string ) =>
242+ useProjectRoleCheck ( useCurrentProject , isProjectCreator , username ) ;
243+
244+ /**
245+ * Check if a user (or current user) is observer of the current project
246+ */
247+ export const useIsObserverOfCurrentProject = ( username ?: string ) =>
248+ useProjectRoleCheck ( useCurrentProject , isProjectObserver , username ) ;
249+
250+ /**
251+ * Check if a user (or current user) is administrator of the current project
252+ */
253+ export const useIsAdministratorOfCurrentProject = ( username ?: string ) =>
254+ useProjectRoleCheck ( useCurrentProject , isProjectAdministrator , username ) ;
0 commit comments