import { defineStore } from 'pinia'; import { api } from '../internal'; import { isAxiosError, useMainStore } from '.'; export function fixUser(u?: FG.User) { return !u ? u : Object.assign(u, { birthday: u.birthday ? new Date(u.birthday) : undefined }); } /** * Check if state is outdated / dirty * Value is considered outdated after 15 minutes * @param updated Time of last updated (in milliseconds see Date.now()) * @returns True if outdated, false otherwise */ function isDirty(updated: number) { return Date.now() - updated > 15 * 60 * 1000; } export const useUserStore = defineStore({ id: 'users', state: () => ({ roles: [] as FG.Role[], permissions: [] as FG.Permission[], userSettings: {} as FG.UserSettings, // list of all users, include deleted ones, use `users` getter for list of active ones _users: [] as FG.User[], // Internal flags for deciding if lists need to force-loaded _dirty_users: 0, _dirty_roles: 0, }), getters: { users(state) { const u = state._users.filter((u) => !u.deleted); u.sort((a, b) => { const a_lastname = a.lastname.toLowerCase(); const b_lastname = b.lastname.toLowerCase(); const a_firstname = a.firstname.toLowerCase(); const b_firstname = b.firstname.toLowerCase(); if (a_lastname === b_lastname) { return a_firstname < b_firstname ? -1 : 1; } return a_lastname < b_lastname ? -1 : 1; }); return u; }, }, actions: { /** Simply filter all users by ID */ findUser(userid: string) { return this._users.find((user) => user.userid === userid); }, /** Retrieve user by ID * @param userid ID of user to retrieve * @param force If set to true the user is loaded from backend even when a local copy is available * @returns Retrieved user (Promise) or raise an error * @throws Probably an AxiosError if loading failed */ async getUser(userid: string, force = false) { const idx = this._users.findIndex((user) => user.userid === userid); if (force || idx === -1 || isDirty(this._dirty_users)) { try { const { data } = await api.get(`/users/${userid}`); fixUser(data); if (idx === -1) this._users.push(data); else this._users[idx] = data; return data; } catch (error) { // Ignore 404, throw all other if (!isAxiosError(error, 404)) throw error; } } else { return this._users[idx]; } }, /** Retrieve list of all users * @param force If set to true a fresh users list is loaded from backend even when a local copy is available * @returns Array of retrieved users (Promise) * @throws Probably an AxiosError if loading failed */ async getUsers(force = false) { if (force || isDirty(this._dirty_users)) { const { data } = await api.get('/users'); data.forEach(fixUser); this._users = data; this._dirty_users = Date.now(); } return this._users; }, /** Save modifications of user on backend * @param user Modified user to save * @throws Probably an AxiosError if request failed (404 = Invalid userid, 400 = Invalid data) */ async updateUser(user: FG.User) { await api.put(`/users/${user.userid}`, user); // Modifcation accepted by backend // Save modifications back to our users list const idx = this._users.findIndex((u) => u.userid === user.userid); if (idx > -1) this._users[idx] = user; // If user was current user, save modifications back to the main store const mainStore = useMainStore(); if (user.userid === mainStore.user?.userid) mainStore.user = user; }, /** Register a new user * @param user User to register (id not set) * @returns The registered user (id set) * @throws Probably an AxiosError if request failed */ async createUser(user: FG.User) { const { data } = await api.post('/users', user); this._users.push(fixUser(data)); return data; }, /** Delete an user * Throws if failed and resolves void if succeed * * @param user User or ID of user to delete * @throws Probably an AxiosError if request failed */ async deleteUser(user: FG.User | string) { if (typeof user === 'object') user = user.userid; await api.delete(`/users/${user}`); this._users = this._users.filter((u) => u.userid != user); }, /** Upload an avatar for an user * Throws if failed and resolves void if succeed * * @param user User or ID of user * @param file Avatar file to upload * @throws Probably an AxiosError if request failed */ async uploadAvatar(user: FG.User | string, file: string | File) { if (typeof user === 'object') user = user.userid; const formData = new FormData(); formData.append('file', file); await api.post(`/users/${user}/avatar`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); }, /** Delete avatar of an user * @param user User or ID of user * @throws Probably an AxiosError if request failed */ async deleteAvatar(user: FG.User | string) { if (typeof user === 'object') user = user.userid; await api.delete(`/users/${user}/avatar`); }, /** Retrieve list of all permissions * @param force If set to true a fresh list is loaded from backend even when a local copy is available * @returns Array of retrieved permissions (Promise) * @throws Probably an AxiosError if request failed */ async getPermissions(force = false) { if (force || this.permissions.length === 0) { const { data } = await api.get('/roles/permissions'); this.permissions = data; } return this.permissions; }, /** Retrieve list of all roles * @param force If set to true a fresh list is loaded from backend even when a local copy is available * @returns Array of retrieved roles (Promise) * @throws Probably an AxiosError if request failed */ async getRoles(force = false) { if (force || isDirty(this._dirty_roles)) { const { data } = await api.get('/roles'); this.roles = data; this._dirty_roles = Date.now(); } return this.roles; }, /** Save modifications of role on the backend * @param role role to save * @throws Probably an AxiosError if request failed */ async updateRole(role: FG.Role) { await api.put(`/roles/${role.id}`, role); const idx = this.roles.findIndex((r) => r.id === role.id); if (idx != -1) this.roles[idx] = role; else this._dirty_roles = 0; }, /** Create a new role * @param role Role to create (ID not set) * @returns Created role (ID set) * @throws Probably an AxiosError if request failed */ async newRole(role: FG.Role) { const { data } = await api.post('/roles', role); this.roles.push(data); return data; }, /** Delete a role * @param role Role or ID of role to delete * @throws Probably an AxiosError if request failed (409 if role still in use) */ async deleteRole(role: FG.Role | number) { if (typeof role === 'object') role = role.id; await api.delete(`/roles/${role}`); this.roles = this.roles.filter((r) => r.id !== role); }, /** Get Settings for display name mode * @param force If set to true a fresh list is loaded from backend even when a local copy is available * @throws Probably an AxiosError if request failed * @returns Settings for display name mode */ async getDisplayNameModeSetting(force = false): Promise { const mainStore = useMainStore(); if (force) { const { data } = await api.get<{ data: string }>( `users/${mainStore.currentUser.userid}/setting/display_name_mode` ); this.userSettings['display_name'] = data.data; } return this.userSettings['display_name']; }, /** Set Settings for display name mode * @param mode New display name mode * @throws Probably an AxiosError if request failed * @returns Settings for display name mode */ async setDisplayNameModeSetting(mode: string): Promise { const mainStore = useMainStore(); await api.put(`users/${mainStore.currentUser.userid}/setting/display_name_mode`, { data: mode, }); this.userSettings['display_name'] = mode; return mode; }, }, });