diff --git a/src/stores/acctMgmt.ts b/src/stores/acctMgmt.ts new file mode 100644 index 0000000..e9e70ca --- /dev/null +++ b/src/stores/acctMgmt.ts @@ -0,0 +1,278 @@ +import { defineStore } from 'pinia'; +import apiError from '@/module/apiError'; +import { JUST_CREATE_ACCOUNT_HOT_DURATION_MINS } from '@/constants/constants'; + +interface User { + username: string; + detail: Record; + name?: string; + is_admin?: boolean; + is_sso?: boolean; + isDeleteHovered?: boolean; + isRowHovered?: boolean; + isEditHovered?: boolean; + isDetailHovered?: boolean; +} + +interface EditDetail { + newUsername?: string; + username?: string; + password: string; + name: string; + is_active: boolean; +} + +export default defineStore('acctMgmtStore', { + state: () => ({ + allUserAccoutList: [] as User[], + isAcctMenuOpen: false, + currentViewingUser: { + username: '', + detail: {}, + } as User, + response: { + deleteAccount: null, + }, + isOneAccountJustCreate: false, //如果有一個帳號剛剛建立,則會在列表上的第一列顯示這個帳號,並且右置一個徽章 + justCreateUsername: '', // unique username + shouldUpdateList: false, // 控制是否該刷新列表 + }), + getters: {}, + actions: { + /** + * Set related boolean to true + */ + openAcctMenu(){ + this.isAcctMenuOpen = true; + }, + /** + * Set related boolean to false + */ + closeAcctMenu(){ + this.isAcctMenuOpen = false; + }, + toggleIsAcctMenuOpen() { + this.isAcctMenuOpen = !this.isAcctMenuOpen; + }, + /** + * For convenience, set current viewing data according to unique user ID. + * @param {string} username + */ + setCurrentViewingUser(username: string) { + const userFind = this.allUserAccoutList.find(user => user.username === username); + this.currentViewingUser = userFind ? userFind : { username: '', detail: {} }; + }, + /** + * We have this method because we want to handle create new modal case. + */ + clearCurrentViewingUser() { + this.currentViewingUser = { + username: '', + detail: {}, + name: '', + is_admin: false, + is_sso: false, + }; + }, + /** + * Get all user accounts + */ + async getAllUserAccounts() { + const apiGetUserList = `/api/users`; + try { + const response = await this.$axios.get(apiGetUserList); + const customizedResponseData = await this.customizeAllUserList(response.data); + this.allUserAccoutList = customizedResponseData; + } catch(error) { + apiError(error, 'Failed to get all users.'); + } + }, + /** + * For example, add isHovered field + */ + async customizeAllUserList(rawResponseData: User[]): Promise { + return rawResponseData.map(user => ({ + ...user, // 保留後端傳來的欄位 + isDeleteHovered: false, // 針對前端顯示而額外增加的欄位 + isRowHovered: false, + isEditHovered: false, + })); + }, + /** + * Create new user in database. + * @param {object} userToCreate + * userToCreate { + * "username": "string", + * "password": "string", + * "name": "string", + * "is_admin": boolean, + * "is_active": boolean, + * } + */ + async createNewAccount(userToCreate: User) { + const apiCreateAccount = `/api/users`; + + try { + const response = await this.$axios.post(apiCreateAccount, userToCreate); + if (response.status === 200) { + this.isOneAccountJustCreate = true; + this.justCreateUsername = userToCreate.username; + setTimeout(this.resetJustCreateFlag, JUST_CREATE_ACCOUNT_HOT_DURATION_MINS * 1000 * 60); + } + } catch(error) { + apiError(error, 'Failed to add a new account.'); + } + }, + /** + * Delete an account from database. + * @param {string} userToDelete this value is unique in database. + * @returns the result of whether the deletion is success or not. + */ + async deleteAccount(userToDelete: string): Promise { + const apiDelete = `/api/users/${userToDelete}`; + + try { + const response = await this.$axios.delete(apiDelete); + return response.status === 200; + } catch(error) { + apiError(error, 'Failed to delete the account.'); + return false; + } + }, + /** + * Edit an account. + * @param {string} userToEdit this value is unique in database. + * @param {object} editDetail + */ + async editAccount(userToEdit: string, editDetail: EditDetail): Promise { + const apiEdit = `/api/users/${userToEdit}`; + + try { + const response = await this.$axios.put(apiEdit, { + username: editDetail.newUsername ? editDetail.newUsername : editDetail.username, + password: editDetail.password, + name: editDetail.name, + is_active: editDetail.is_active, + }); + return response.status === 200; + } catch(error) { + apiError(error, 'Failed to edit the account.'); + return false; + } + }, + /** Add a role to the user in database. + * @param {string} usernameToEdit + * @param {string} roleCode + */ + async addRoleToUser(usernameToEdit: string, roleCode: string): Promise { + const apiAddRole = `/api/users/${usernameToEdit}/roles/${roleCode}`; + + try { + const response = await this.$axios.put(apiAddRole); + return response.status === 200; + } catch(error) { + apiError(error, 'Failed to add role to the account.'); + return false; + } + }, + /** Delete a role from the user in database. + * @param {string} usernameToEdit + * @param {string} roleCode + */ + async deleteRoleToUser(usernameToEdit: string, roleCode: string): Promise { + const apiDeleteRole = `/api/users/${usernameToEdit}/roles/${roleCode}`; + + try { + const response = await this.$axios.delete(apiDeleteRole); + return response.status === 200; + } catch(error) { + apiError(error, 'Failed to delete a role frome the account.'); + return false; + } + }, + /** + * Get user detail by unique username. + * @param {string} uniqueUsername + */ + async getUserDetail(uniqueUsername: string): Promise { + const apiUserDetail = `/api/users/${uniqueUsername}`; + + try { + const response = await this.$axios.get(apiUserDetail); + this.currentViewingUser = { + ...this.currentViewingUser, + detail: response.data, + }; + return response.status === 200; + } catch(error) { + //不需要跳出錯誤,因為如果是錯誤反而是好事,表示帳號是獨一的 + return false; + } + }, + /** + * According to mouseover or mouseleave status, change the bool value. + * @param {string} username + * @param {boolean} isDeleteHovered + */ + changeIsDeleteHoveredByUser(username: string, isDeleteHovered: boolean) { + const userToChange = this.allUserAccoutList.find(user => user.username === username); + if (userToChange) { + userToChange.isDeleteHovered = isDeleteHovered; + } + }, + /** + * According to mouseover or mouseleave status, change the bool value. + * @param {string} username + * @param {boolean} isRowHovered + */ + changeIsRowHoveredByUser(username: string, isRowHovered: boolean) { + const userToChange = this.allUserAccoutList.find(user => user.username === username); + if (userToChange) { + userToChange.isRowHovered = isRowHovered; + } + }, + /** + * According to mouseover or mouseleave status, change the bool value. + * @param {string} username + * @param {boolean} isEditHovered + */ + changeIsEditHoveredByUser(username: string, isEditHovered: boolean) { + const userToChange = this.allUserAccoutList.find(user => user.username === username); + if (userToChange) { + userToChange.isEditHovered = isEditHovered; + } + }, + /** + * According to mouseover or mouseleave status, change the bool value. + * @param {string} username + * @param {boolean} isEditHovered + */ + changeIsDetailHoveredByUser(username: string, isDetailHovered: boolean) { + const userToChange = this.allUserAccoutList.find(user => user.username === username); + if (userToChange) { + userToChange.isDetailHovered = isDetailHovered; + } + }, + /** + * Reset isOneAccountJustCreate to false, causing the badge to disappear. + */ + resetJustCreateFlag(){ + this.isOneAccountJustCreate = false; + }, + /** Set the value of shouldUpdateList variable. + * @param {boolean} shouldUpdateBoolean + */ + setShouldUpdateList(shouldUpdateBoolean: boolean) { + this.shouldUpdateList = shouldUpdateBoolean; + }, + /** Only update one single account in the pinia state. + * @param {object} userDataToReplace + */ + updateSingleAccountPiniaState(userDataToReplace: User) { + const userIndex = this.allUserAccoutList.findIndex(user => user.username === userDataToReplace.username); + if (userIndex !== -1) { + this.allUserAccoutList[userIndex] = { ...this.allUserAccoutList[userIndex], ...userDataToReplace }; + } + } + }, +});