import { defineStore } from "pinia"; import type { UserOutExtended, UserOut, UserIn } from "@/client"; import { OpenAPI, UserService, RoleEnum } from "@/client"; import { useWorkflowExecutionStore } from "@/stores/workflowExecutions"; import { useBucketStore } from "@/stores/buckets"; import { useWorkflowStore } from "@/stores/workflows"; import { useS3KeyStore } from "@/stores/s3keys"; import { useS3ObjectStore } from "@/stores/s3objects"; import { clear as dbclear } from "idb-keyval"; import { useNameStore } from "@/stores/names"; type DecodedToken = { exp: number; iss: string; sub: string; }; function parseJwt(token: string): DecodedToken { const base64Url = token.split(".")[1]; const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/"); const jsonPayload = decodeURIComponent( window .atob(base64) .split("") .map(function (c) { return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2); }) .join(""), ); return JSON.parse(jsonPayload) as DecodedToken; } export const useUserStore = defineStore({ id: "user", state: () => ({ token: null, decodedToken: null, user: null, }) as { token: string | null; decodedToken: DecodedToken | null; user: UserOutExtended | null; }, getters: { roles(): string[] { return this.user?.roles?.map((role) => role.toString()) ?? []; }, authenticated: (state) => state.token != null, currentUID(): string { return this.user?.uid ?? this.decodedToken?.["sub"] ?? ""; }, foreignUser: (state) => (state.user?.roles ?? []).length == 0, normalUser: (state) => state.user?.roles.includes(RoleEnum.USER) ?? false, rewiewer: (state) => state.user?.roles.includes(RoleEnum.REVIEWER) ?? false, workflowDev: (state) => state.user?.roles.includes(RoleEnum.DEVELOPER) ?? false, resourceMaintainer: (state) => state.user?.roles.includes(RoleEnum.DB_MAINTAINER) ?? false, admin: (state) => state.user?.roles.includes(RoleEnum.ADMINISTRATOR) ?? false, }, actions: { setToken(token: string | null) { if (token != null) { this.token = token; this.decodedToken = parseJwt(token); OpenAPI.TOKEN = token; UserService.userGetLoggedInUser() .then((user) => { this.updateUser(user); }) .catch(() => { this.token = null; }); } else { this.logout(); } }, updateUser(user: UserOutExtended) { this.user = user; useNameStore().addNameToMapping(user.uid, user.display_name); }, logout() { window._paq.push(["resetUserId"]); OpenAPI.TOKEN = undefined; this.$reset(); localStorage.clear(); dbclear(); useWorkflowExecutionStore().$reset(); useBucketStore().$reset(); useWorkflowStore().$reset(); useS3KeyStore().$reset(); useS3ObjectStore().$reset(); }, fetchUsers( searchString?: string, filterRoles?: RoleEnum[], ): Promise<UserOutExtended[]> { return UserService.userListUsers(searchString, filterRoles).then( (users) => { const nameStore = useNameStore(); for (const user of users) { nameStore.addNameToMapping(user.uid, user.display_name); } return users; }, ); }, updateUserRoles(uid: string, roles: RoleEnum[]): Promise<UserOutExtended> { return UserService.userUpdateRoles(uid, { roles: roles, }); }, inviteUser(userIn: UserIn): Promise<UserOutExtended> { return UserService.userCreateUser(userIn).then((user) => { useNameStore().addNameToMapping(user.uid, user.display_name); return user; }); }, resendInvitationEmail(uid: string): Promise<UserOutExtended> { return UserService.userResendInvitation(uid).then((user) => { useNameStore().addNameToMapping(user.uid, user.display_name); return user; }); }, searchUser(searchString: string): Promise<UserOut[]> { return UserService.userSearchUsers(searchString).then((users) => { const nameStore = useNameStore(); for (const user of users) { nameStore.addNameToMapping(user.uid, user.display_name); } return users; }); }, async fetchUsernames(uids: string[]): Promise<string[]> { const nameStore = useNameStore(); const filteredIds = uids .filter((uid) => !nameStore.getName(uid)) // filter already present UIDs .filter( // filter unique UIDs (modeId, index, array) => array.findIndex((val) => val === modeId) === index, ); // If all uids are already in the store, then return them if (filteredIds.length === 0) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return uids.map((uid) => nameStore.getName(uid)!); } const missingIds: string[] = []; const storedNames = filteredIds.map((uid) => localStorage.getItem(uid)); for (const index in storedNames) { // if uid was not cached, mark it to fetch it from backend if (storedNames[index] == null) { missingIds.push(filteredIds[index]); } else { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion nameStore.addNameToMapping(filteredIds[index], storedNames[index]!); } } // fetch missing users from backend const fetchedUsers = await Promise.all( missingIds.map((uid) => UserService.userGetUser(uid)), ); // Put users in store for (const user of fetchedUsers) { nameStore.addNameToMapping(user.uid, user.display_name); } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return uids.map((uid) => nameStore.getName(uid)!); }, }, });