Skip to content
Snippets Groups Projects
users.ts 5.81 KiB
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)!);
    },
  },
});