diff --git a/frontend/src/components/AutoLogout.vue b/frontend/src/components/AutoLogout.vue index 3c29cf12caafb3c05f6213771a7f1543fccf08c5..b7677ee1773e5114bb0435f7fff7ae4dd0868414 100644 --- a/frontend/src/components/AutoLogout.vue +++ b/frontend/src/components/AutoLogout.vue @@ -28,6 +28,8 @@ <script> import {mapState} from 'vuex' +import {Authentication} from '@/store/modules/authentication' +import {actions} from '@/store/actions' export default { name: 'auto-logout', @@ -41,19 +43,17 @@ export default { ...mapState([ 'lastAppInteraction' ]), - ...mapState({ - lastTokenRefreshTry: state => state.authentication.lastTokenRefreshTry, - refreshingToken: state => state.authentication.refreshingToken, - jwtTimeDelta: state => state.authentication.jwtTimeDelta - }) + lastTokenRefreshTry () { return Authentication.state.lastTokenRefreshTry }, + refreshingToken () { return Authentication.state.refreshingToken }, + jwtTimeDelta () { return Authentication.state.jwtTimeDelta } }, methods: { logout () { this.logoutDialog = false - this.$store.dispatch('logout') + actions.logout() }, continueWork () { - this.$store.dispatch('refreshJWT') + Authentication.refreshJWT() this.logoutDialog = false } }, @@ -64,7 +64,7 @@ export default { // refresh jwt if it's older than 20% of his maximum age if (this.$route.name !== 'login' && timeSinceLastRefresh > timeDelta * 0.2 && !this.refreshingToken) { - this.$store.dispatch('refreshJWT') + Authentication.refreshJWT() } } }, @@ -75,7 +75,7 @@ export default { if (this.$route.name !== 'login' && this.$store.getters.isLoggedIn) { if (Date.now() > this.lastTokenRefreshTry + this.jwtTimeDelta) { this.logoutDialog = false - this.$store.dispatch('logout', "You've been logged out due to inactivity.") + actions.logout("You've been logged out due to inactivity.") } else if (Date.now() + timeToLogOutDialog > this.lastTokenRefreshTry + this.jwtTimeDelta) { this.logoutDialog = true } diff --git a/frontend/src/components/BaseLayout.vue b/frontend/src/components/BaseLayout.vue index b02083b3b0c8423a6270fc8309d920fff25d4cc6..db4e169c438d857c22f8e972251ce9f5b21e1a14 100644 --- a/frontend/src/components/BaseLayout.vue +++ b/frontend/src/components/BaseLayout.vue @@ -88,18 +88,15 @@ import { mapGetters, mapState } from 'vuex' import {UI} from '@/store/modules/ui' import { mapStateToComputedGetterSetter } from '@/util/helpers' import UserOptions from '@/components/UserOptions' +import {Authentication} from '@/store/modules/authentication' export default { name: 'base-layout', components: {UserOptions}, computed: { - ...mapGetters([ - 'gradySpeak' - ]), - ...mapState({ - username: state => state.authentication.user.username, - userRole: state => state.authentication.user.role - }), + username () { return Authentication.state.user.username }, + userRole () { return Authentication.state.user.role }, + gradySpeak () { return Authentication.gradySpeak }, ...mapStateToComputedGetterSetter({ pathPrefix: 'UI', items: [ diff --git a/frontend/src/components/CorrectionStatistics.vue b/frontend/src/components/CorrectionStatistics.vue index a3bf9eadecab5a7c89bedf730633fc090fc11023..ecd499f279ae6eac1a06a137185fa8e46d2b58db 100644 --- a/frontend/src/components/CorrectionStatistics.vue +++ b/frontend/src/components/CorrectionStatistics.vue @@ -32,6 +32,8 @@ </template> <script> +import { actions } from '@/store/actions' + export default { name: 'correction-statistics', data () { @@ -45,7 +47,7 @@ export default { } }, created () { - this.$store.dispatch('getStatistics').then(() => { this.loaded = true }) + actions.getStatistics().then(() => { this.loaded = true }) } } </script> diff --git a/frontend/src/components/PasswordChangeDialog.vue b/frontend/src/components/PasswordChangeDialog.vue index 03e4758ad3a960ec56961a18869d16d2749fb6cf..8ddc07bcbc365295699e6f720dec7308b058d6f1 100644 --- a/frontend/src/components/PasswordChangeDialog.vue +++ b/frontend/src/components/PasswordChangeDialog.vue @@ -37,6 +37,7 @@ <script> import {mapState} from 'vuex' import { changePassword } from '@/api' +import { Authentication } from '@/store/modules/authentication' export default { name: 'PasswordChangeDialog', @@ -49,9 +50,7 @@ export default { } }, computed: { - ...mapState({ - userPk: state => state.authentication.user.pk - }), + userPk () { return Authentication.state.user.pk }, equalNewPasswords () { return this.newPassword === this.newPasswordRepeated }, diff --git a/frontend/src/components/UserOptions.vue b/frontend/src/components/UserOptions.vue index 96fffc4c4be56e513700bd219e5cf78951547d6f..5f6677bee0c882b977553382928a679c1bd74f82 100644 --- a/frontend/src/components/UserOptions.vue +++ b/frontend/src/components/UserOptions.vue @@ -17,6 +17,7 @@ <script> import PasswordChangeDialog from '@/components/PasswordChangeDialog' +import {Authentication} from '@/store/modules/authentication' export default { name: 'UserOptions', components: {PasswordChangeDialog}, @@ -27,7 +28,7 @@ export default { { display: 'Change password', action: () => { this.displayComponent = PasswordChangeDialog }, - condition: () => !this.$store.getters.isStudent + condition: () => !Authentication.isStudent } ] } diff --git a/frontend/src/components/feedback_list/FeedbackSearchOptions.vue b/frontend/src/components/feedback_list/FeedbackSearchOptions.vue index c2db7e9c529c990e16bb92ba81146698dd817c94..3eb5514e6d0ccf8ff8019a8cc4289f8089d3182a 100644 --- a/frontend/src/components/feedback_list/FeedbackSearchOptions.vue +++ b/frontend/src/components/feedback_list/FeedbackSearchOptions.vue @@ -64,6 +64,7 @@ import {mapState, mapGetters} from 'vuex' import {namespace, feedbackSearchOptsMut} from '@/store/modules/feedback_list/feedback-search-options' import {mapStateToComputedGetterSetter} from '@/util/helpers' +import {Authentication} from '@/store/modules/authentication' export default { name: 'feedback-search-options', @@ -76,9 +77,7 @@ export default { ...mapState([ 'tutors' ]), - ...mapGetters([ - 'isReviewer' - ]), + isReviewer () { return Authentication.isReviewer }, tutorNames () { return this.tutors.map(tutor => tutor.username) }, diff --git a/frontend/src/components/submission_notes/SubmissionCorrection.vue b/frontend/src/components/submission_notes/SubmissionCorrection.vue index a23d41110e15d588b49a25b7366d9fac09fa9a54..5339159c8724371c92cb58fbe859c3571c37f766 100644 --- a/frontend/src/components/submission_notes/SubmissionCorrection.vue +++ b/frontend/src/components/submission_notes/SubmissionCorrection.vue @@ -65,6 +65,7 @@ import BaseAnnotatedSubmission from '@/components/submission_notes/base/BaseAnno import SubmissionLine from '@/components/submission_notes/base/SubmissionLine' import {subNotesMut, subNotesNamespace} from '@/store/modules/submission-notes' import RouteChangeConfirmation from '@/components/submission_notes/RouteChangeConfirmation' +import {Authentication} from '@/store/modules/authentication' export default { components: { @@ -96,7 +97,6 @@ export default { }, computed: { ...mapState({ - user: state => state.authentication.user.username, showEditorOnLine: state => state.submissionNotes.ui.showEditorOnLine, selectedComment: state => state.submissionNotes.ui.selectedCommentOnLine, origFeedback: state => state.submissionNotes.origFeedback.feedbackLines, @@ -104,11 +104,12 @@ export default { showFeedback: state => state.submissionNotes.ui.showFeedback }), ...mapGetters([ - 'isStudent', - 'isTutor', - 'isReviewer', 'workInProgress' ]), + isStudent () { return Authentication.isStudent }, + isTutor () { return Authentication.isTutor }, + isReviewer () { return Authentication.isReviewer }, + user () { return Authentication.state.user.username }, submission () { return this.$store.getters['submissionNotes/submission'] }, diff --git a/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar.vue b/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar.vue index c26aea454c0cb479e4b3f5bff2f4ee1d531fdf68..49c9af1b22a15a7592bc8970e770fb6f43946da3 100644 --- a/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar.vue +++ b/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar.vue @@ -55,6 +55,7 @@ <script> import {subNotesMut, subNotesNamespace} from '@/store/modules/submission-notes' +import {Authentication} from '@/store/modules/authentication' export default { name: 'annotated-submission-bottom-toolbar', @@ -92,7 +93,7 @@ export default { } }, showFinalCheckbox () { - return !this.$store.getters['submissionNotes/isFeedbackCreation'] || this.$store.getters.isReviewer + return !this.$store.getters['submissionNotes/isFeedbackCreation'] || Authentication.isReviewer } }, watch: { @@ -106,12 +107,12 @@ export default { methods: { initialFinalStatus () { if (this.$route.name === 'subscription') { - return !this.$store.getters['submissionNotes/isFeedbackCreation'] || this.$store.getters.isReviewer + return !this.$store.getters['submissionNotes/isFeedbackCreation'] || Authentication.isReviewer } else { if (this.feedback.hasOwnProperty('isFinal')) { return this.feedback.isFinal } else { - return !this.$store.getters['submissionNotes/isFeedbackCreation'] || this.$store.getters.isReviewer + return !this.$store.getters['submissionNotes/isFeedbackCreation'] || Authentication.isReviewer } } }, diff --git a/frontend/src/components/subscriptions/SubscriptionCreation.vue b/frontend/src/components/subscriptions/SubscriptionCreation.vue index 8dabfedd401bc82bbe7dc9164a6f32a2510acfeb..e41d00abfa0c3e454596ed6cc27c4dd300e126fa 100644 --- a/frontend/src/components/subscriptions/SubscriptionCreation.vue +++ b/frontend/src/components/subscriptions/SubscriptionCreation.vue @@ -29,6 +29,8 @@ </template> <script> +import {Authentication} from '@/store/modules/authentication' + const stages = [ { text: 'Initial Feedback', @@ -68,7 +70,7 @@ export default { computed: { possibleStages () { let possibleStages = [...stages] - if (this.$store.getters.isReviewer) { + if (Authentication.isReviewer) { possibleStages.push({ text: 'Conflict resolution', stage: 'feedback-conflict-resolution' diff --git a/frontend/src/components/subscriptions/SubscriptionList.vue b/frontend/src/components/subscriptions/SubscriptionList.vue index d62b05a93266d1e036fc6646cfda881966ff1aa1..c31e578caf276d06924d5792d2ee6c28b00ec41c 100644 --- a/frontend/src/components/subscriptions/SubscriptionList.vue +++ b/frontend/src/components/subscriptions/SubscriptionList.vue @@ -30,6 +30,7 @@ <script> import {mapGetters, mapActions, mapState} from 'vuex' import {UI} from '@/store/modules/ui' +import {actions} from '@/store/actions' import SubscriptionCreation from '@/components/subscriptions/SubscriptionCreation' import SubscriptionForList from '@/components/subscriptions/SubscriptionForList' import SubscriptionsForStage from '@/components/subscriptions/SubscriptionsForStage' @@ -64,9 +65,7 @@ export default { }, methods: { ...mapActions([ - 'updateSubmissionTypes', 'getCurrentAssignment', - 'getExamTypes', 'subscribeToAll', 'cleanAssignmentsFromSubscriptions' ]), @@ -78,7 +77,7 @@ export default { } }, created () { - const typesAndSubscriptions = [this.updateSubmissionTypes(), this.getSubscriptions()] + const typesAndSubscriptions = [actions.updateSubmissionTypes(), this.getSubscriptions()] Promise.all(typesAndSubscriptions).then(() => { this.subscribeToAll() this.cleanAssignmentsFromSubscriptions() diff --git a/frontend/src/main.ts b/frontend/src/main.ts index ad81436ec767936f2e0d2118a82e08287f7d99f8..774733cd7bf8ebf172f899b8a9b817ad3ad845ae 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,7 +1,7 @@ import Vue from 'vue' +import store from './store/store' import App from './App.vue' import router from './router/index' -import store from './store/store' import Vuetify from 'vuetify' import Notifications from 'vue-notification' import Clipboard from 'v-clipboard' diff --git a/frontend/src/pages/LayoutSelector.vue b/frontend/src/pages/LayoutSelector.vue index 5175019934de88b1907137ac19c6989e4958c9c9..8303d03618859101ee067aa859cdf82f6f1766be 100644 --- a/frontend/src/pages/LayoutSelector.vue +++ b/frontend/src/pages/LayoutSelector.vue @@ -12,6 +12,7 @@ import {mapGetters} from 'vuex' import TutorLayout from '@/pages/tutor/TutorLayout' import StudentLayout from '@/pages/student/StudentLayout' import ReviewerLayout from '@/pages/reviewer/ReviewerLayout' +import {Authentication} from '@/store/modules/authentication' export default { components: { @@ -20,11 +21,9 @@ export default { TutorLayout}, name: 'layout-selector', computed: { - ...mapGetters([ - 'isStudent', - 'isTutor', - 'isReviewer' - ]), + isStudent () { return Authentication.isStudent }, + isTutor () { return Authentication.isTutor }, + isReviewer () { return Authentication.isReviewer }, layout () { if (this.isStudent) { return 'student-layout' diff --git a/frontend/src/pages/Login.vue b/frontend/src/pages/Login.vue index 0988645e495fe611959f06788d662156c367236b..f73e92788ecaeb1a146f3b5e1214357758b2ea89 100644 --- a/frontend/src/pages/Login.vue +++ b/frontend/src/pages/Login.vue @@ -41,7 +41,7 @@ <script> import {mapActions, mapState} from 'vuex' import RegisterDialog from '@/components/RegisterDialog' -import { authMut } from '@/store/modules/authentication' +import {Authentication as Auth} from '@/store/modules/authentication' export default { components: {RegisterDialog}, @@ -57,10 +57,8 @@ export default { } }, computed: { - ...mapState({ - msg: state => state.authentication.message, - userRole: state => state.authentication.user.role - }), + msg () { return Auth.state.message }, + userRole () { return Auth.state.user.role }, production () { return process.env.NODE_ENV === 'production' }, @@ -69,18 +67,13 @@ export default { } }, methods: { - ...mapActions([ - 'getJWT', - 'getUser', - 'getJWTTimeDelta' - ]), submit () { this.loading = true - this.getJWT(this.credentials).then(() => { - this.getUser().then(() => { + Auth.getJWT(this.credentials).then(() => { + Auth.getUser().then(() => { this.$router.push({name: 'home'}) }) - this.getJWTTimeDelta() + Auth.getJWTTimeDelta() this.loading = false }).catch(() => { this.loading = false }) }, @@ -88,7 +81,7 @@ export default { this.registerDialog = false this.credentials.username = credentials.username this.credentials.password = credentials.password - this.$store.commit(authMut.SET_MESSAGE, 'Your account is being activated. Please wait.') + Auth.SET_MESSAGE('Your account is being activated. Please wait.') } } } diff --git a/frontend/src/pages/StartPageSelector.vue b/frontend/src/pages/StartPageSelector.vue index 97a06a4436ff9fb0e89a1d1487be1bdf06503bc7..35bceedd41bca637a3d268dcc05491ed25abdbb8 100644 --- a/frontend/src/pages/StartPageSelector.vue +++ b/frontend/src/pages/StartPageSelector.vue @@ -8,6 +8,7 @@ import {mapGetters} from 'vuex' import TutorStartPage from '@/pages/tutor/TutorStartPage' import StudentPage from '@/pages/student/StudentPage' import ReviewerStartPage from '@/pages/reviewer/ReviewerStartPage' +import {Authentication} from '@/store/modules/authentication' export default { name: 'start-page-selector', components: { @@ -16,11 +17,9 @@ export default { TutorStartPage }, computed: { - ...mapGetters([ - 'isStudent', - 'isTutor', - 'isReviewer' - ]), + isStudent () { return Authentication.isStudent }, + isTutor () { return Authentication.isTutor }, + isReviewer () { return Authentication.isReviewer }, startPage () { if (this.isStudent) { return 'student-page' diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 25b1aeee5b8f2fa7288587de7e8c2e923231c0d9..d2d70bfc5d459d84bc0a96ade6964c0c44e041c6 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -15,7 +15,7 @@ import FeedbackHistoryPage from '@/pages/base/FeedbackHistoryPage.vue' import FeedbackTable from '@/components/feedback_list/FeedbackTable.vue' import FeedbackListHelpCard from '@/components/feedback_list/FeedbackListHelpCard.vue' import VueInstance from '@/main' -import store from '@/store/store' +import {Authentication} from '@/store/modules/authentication' Vue.use(Router) @@ -31,7 +31,7 @@ function denyAccess (next: rerouteFunc, redirect: Route) { } let tutorOrReviewerOnly: NavigationGuard = function (to, from, next) { - if (store.getters.isTutorOrReviewer) { + if (Authentication.isTutorOrReviewer) { next() } else { denyAccess(next, from) @@ -39,7 +39,7 @@ let tutorOrReviewerOnly: NavigationGuard = function (to, from, next) { } let reviewerOnly: NavigationGuard = function (to, from, next) { - if (store.getters.isReviewer) { + if (Authentication.isReviewer) { next() } else { denyAccess(next, from) @@ -47,7 +47,7 @@ let reviewerOnly: NavigationGuard = function (to, from, next) { } let studentOnly: NavigationGuard = function (to, from, next) { - if (store.getters.isStudent) { + if (Authentication.isStudent) { next() } else { next(false) @@ -55,7 +55,7 @@ let studentOnly: NavigationGuard = function (to, from, next) { } let checkLoggedIn: NavigationGuard = function (to, from, next) { - if (store.getters.isLoggedIn) { + if (Authentication.isLoggedIn) { next() } else { next('/login/') diff --git a/frontend/src/store/actions.ts b/frontend/src/store/actions.ts index e1fecc60798cc1beac4bfc2cd97177db4d0fa6d6..14a5889c9ef6c164f64daff137c9b28b4fa84126 100644 --- a/frontend/src/store/actions.ts +++ b/frontend/src/store/actions.ts @@ -2,7 +2,7 @@ import { ActionContext } from "vuex"; import { BareActionContext, getStoreBuilder } from "vuex-typex"; import { mutations as mut } from "./mutations"; -import { authMut } from "@/store/modules/authentication"; +import { Authentication } from "@/store/modules/authentication"; import { subNotesMut } from "@/store/modules/submission-notes"; import * as api from "@/api"; import router from "@/router/index"; @@ -77,14 +77,14 @@ function logout( const { commit, getters, state } = <ActionContext<RootState, RootState>>( context ); - if (getters.isStudent) { + if (Authentication.isStudent) { // change active to false when user logs out // TODO this should belong in auth module - api.changeActiveForUser((state as any).authentication.user.pk, false); + api.changeActiveForUser(Authentication.state.user.pk, false); } mut.RESET_STATE(); commit("submissionNotes/" + subNotesMut.RESET_STATE); - commit(authMut.SET_MESSAGE, message); + Authentication.SET_MESSAGE(message); router.push({ name: "login" }); router.go(0); } @@ -92,8 +92,8 @@ function logout( const mb = getStoreBuilder<RootState>(); export const actions = { - updateSubmissionTypes: mb.dispatch(getExamTypes), - getExamTypes: mb.dispatch(updateSubmissionTypes), + updateSubmissionTypes: mb.dispatch(updateSubmissionTypes), + getExamTypes: mb.dispatch(getExamTypes), getStudents: mb.dispatch(getStudents), getTutors: mb.dispatch(getTutors), getFeedback: mb.dispatch(getFeedback), diff --git a/frontend/src/store/getters.ts b/frontend/src/store/getters.ts index 6d4aafded1ef2b08b0cb013ea40397db819499bd..09fbeafeeba820fa536af933771258a13e6292f6 100644 --- a/frontend/src/store/getters.ts +++ b/frontend/src/store/getters.ts @@ -3,6 +3,8 @@ import {getStoreBuilder} from 'vuex-typex' const mb = getStoreBuilder<RootState>() +const stateGetter = mb.state() + const correctedGetter = mb.read(function corrected (state) { return state.statistics.submissionTypeProgress.every(progress => { return progress.feedbackFinal === progress.submissionCount @@ -20,6 +22,7 @@ const submissionTypeGetter = mb.read(function submissionType (state) { }) export const getters = { + get state () { return stateGetter() }, get corrected () { return correctedGetter() }, get submission () { return submissionGetter() }, get submissionType () { return submissionTypeGetter() } diff --git a/frontend/src/store/modules/authentication.ts b/frontend/src/store/modules/authentication.ts index 34cb5c88c9a5c35ccc167b93268ca4dfb013b17a..79e330d98b05d655381808a6214ce3dec4426779 100644 --- a/frontend/src/store/modules/authentication.ts +++ b/frontend/src/store/modules/authentication.ts @@ -1,14 +1,15 @@ import * as api from '@/api' import gradySays from '../grady_speak' -import {ActionContext, Module} from 'vuex' +import {BareActionContext, getStoreBuilder} from 'vuex-typex' import {UserAccount} from '@/models' +import {RootState} from '@/store/store' export interface Credentials { username: string, password: string } -interface AuthState { +export interface AuthState { token: string, lastTokenRefreshTry: number, refreshingToken: boolean, @@ -31,109 +32,119 @@ function initialState (): AuthState { } } -export const authMut = Object.freeze({ - SET_MESSAGE: 'SET_MESSAGE', - SET_JWT_TOKEN: 'SET_JWT_TOKEN', - SET_JWT_TIME_DELTA: 'SET_JWT_TIME_DELTA', - SET_USER: 'SET_USER', - SET_LAST_TOKEN_REFRESH_TRY: 'SET_LAST_TOKEN_REFRESH_TRY', - RESET_STATE: 'RESET_STATE', - SET_REFRESHING_TOKEN: 'SET_REFRESHING_TOKEN' +const mb = getStoreBuilder<RootState>().module('Authentication', initialState()) + +const stateGetter = mb.state() + +const gradySpeakGetter = mb.read(function gradySpeak () { + return gradySays[Math.floor(Math.random() * gradySays.length)] +}) +const isStudentGetter = mb.read(function isStudent (state: AuthState) { + return state.user.role === UserAccount.RoleEnum.Student +}) +const isTutorGetter = mb.read(function isTutor (state: AuthState) { + return state.user.role === UserAccount.RoleEnum.Tutor +}) +const isReviewerGetter = mb.read(function isReviewer (state: AuthState) { + return state.user.role === UserAccount.RoleEnum.Reviewer +}) +const isTutorOrReviewerGetter = mb.read(function isTutorOrReviewer (state: AuthState, getters) { + return getters.isTutor || getters.isReviewer +}) +const isLoggedInGetter = mb.read(function isLoggedIn (state: AuthState) { + return !!state.token }) -const authentication: Module<AuthState, any> = { - state: initialState(), - getters: { - gradySpeak: () => { - return gradySays[Math.floor(Math.random() * gradySays.length)] - }, - isStudent: (state: AuthState) => { - return state.user.role === UserAccount.RoleEnum.Student - }, - isTutor: (state: AuthState) => { - return state.user.role === UserAccount.RoleEnum.Tutor - }, - isReviewer: (state: AuthState) => { - return state.user.role === UserAccount.RoleEnum.Reviewer - }, - isTutorOrReviewer: (state: AuthState, getters) => { - return getters.isTutor || getters.isReviewer - }, - isLoggedIn: (state: AuthState) => !!state.token - }, - mutations: { - [authMut.SET_MESSAGE] (state: AuthState, message: string) { - state.message = message - }, - [authMut.SET_JWT_TOKEN] (state: AuthState, token: string) { - sessionStorage.setItem('token', token) - state.token = token - }, - [authMut.SET_JWT_TIME_DELTA] (state: AuthState, timeDelta: number) { - state.jwtTimeDelta = timeDelta - }, - [authMut.SET_USER] (state: AuthState, user) { - state.user = user - }, - [authMut.SET_REFRESHING_TOKEN] (state: AuthState, refreshing: boolean) { - state.refreshingToken = refreshing - }, - [authMut.SET_LAST_TOKEN_REFRESH_TRY] (state: AuthState) { - state.lastTokenRefreshTry = Date.now() - }, - [authMut.RESET_STATE] (state: AuthState) { - sessionStorage.setItem('token', '') - Object.assign(state, initialState()) - } - }, - actions: { - async getJWT (context: ActionContext<AuthState, any>, credentials: Credentials) { - try { - const token = await api.fetchJWT(credentials) - context.commit(authMut.SET_JWT_TOKEN, token.token) - } catch (error) { - let errorMsg - if (!error.response) { - errorMsg = 'Cannot reach server.' - } else if (error.response.status === 400) { - errorMsg = 'Unable to log in with provided credentials.' - } else if (error.response.status === 429) { - errorMsg = error.response.data.detail - } - context.commit(authMut.SET_MESSAGE, errorMsg) - throw errorMsg - } finally { - context.commit(authMut.SET_LAST_TOKEN_REFRESH_TRY) - } - }, - async refreshJWT ({state, commit}) { - commit(authMut.SET_REFRESHING_TOKEN, true) - try { - const token = await api.refreshJWT(state.token) - commit(authMut.SET_JWT_TOKEN, token.token) - } finally { - commit(authMut.SET_REFRESHING_TOKEN, false) - commit(authMut.SET_LAST_TOKEN_REFRESH_TRY) - } - }, - async getUser ({commit}) { - try { - const user = await api.getOwnUser() - commit(authMut.SET_USER, user) - } catch (err) { - commit(authMut.SET_MESSAGE, 'Unable to fetch user.') - } - }, - async getJWTTimeDelta ({commit}) { - try { - const delta = await api.fetchJWTTimeDelta() - // multiply by 1000 to convert to ms - commit(authMut.SET_JWT_TIME_DELTA, delta * 1000) - } catch (err) { - console.log(err) - } +function SET_MESSAGE (state: AuthState, message: string) { + state.message = message +} +function SET_JWT_TOKEN (state: AuthState, token: string) { + sessionStorage.setItem('token', token) + state.token = token +} +function SET_JWT_TIME_DELTA (state: AuthState, timeDelta: number) { + state.jwtTimeDelta = timeDelta +} +function SET_USER (state: AuthState, user: UserAccount) { + state.user = user +} +function SET_REFRESHING_TOKEN (state: AuthState, refreshing: boolean) { + state.refreshingToken = refreshing +} +function SET_LAST_TOKEN_REFRESH_TRY (state: AuthState) { + state.lastTokenRefreshTry = Date.now() +} +function RESET_STATE (state: AuthState) { + sessionStorage.setItem('token', '') + Object.assign(state, initialState()) +} + +async function getJWT (context: BareActionContext<AuthState, RootState>, credentials: Credentials) { + try { + const token = await api.fetchJWT(credentials) + Authentication.SET_JWT_TOKEN(token.token) + } catch (error) { + let errorMsg + if (!error.response) { + errorMsg = 'Cannot reach server.' + } else if (error.response.status === 400) { + errorMsg = 'Unable to log in with provided credentials.' + } else if (error.response.status === 429) { + errorMsg = error.response.data.detail } + Authentication.SET_MESSAGE(errorMsg) + throw errorMsg + } finally { + Authentication.SET_LAST_TOKEN_REFRESH_TRY() + } +} +async function refreshJWT ({state}: BareActionContext<AuthState, RootState>) { + Authentication.SET_REFRESHING_TOKEN(true) + try { + const token = await api.refreshJWT(state.token) + Authentication.SET_JWT_TOKEN(token.token) + } finally { + Authentication.SET_REFRESHING_TOKEN(false) + Authentication.SET_LAST_TOKEN_REFRESH_TRY() + } +} +async function getUser () { + try { + const user = await api.getOwnUser() + Authentication.SET_USER(user) + } catch (err) { + Authentication.SET_MESSAGE('Unable to fetch user.') + } +} +async function getJWTTimeDelta () { + try { + const delta = await api.fetchJWTTimeDelta() + // multiply by 1000 to convert to ms + Authentication.SET_JWT_TIME_DELTA(delta * 1000) + } catch (err) { + console.log(err) } } -export default authentication +export const Authentication = { + get state () { return stateGetter() }, + get gradySpeak () { return gradySpeakGetter() }, + get isStudent () { return isStudentGetter() }, + get isTutor () { return isTutorGetter() }, + get isReviewer () { return isReviewerGetter() }, + get isTutorOrReviewer () { return isTutorOrReviewerGetter() }, + get isLoggedIn () { return isLoggedInGetter() }, + + SET_MESSAGE: mb.commit(SET_MESSAGE), + SET_JWT_TOKEN: mb.commit(SET_JWT_TOKEN), + SET_JWT_TIME_DELTA: mb.commit(SET_JWT_TIME_DELTA), + SET_USER: mb.commit(SET_USER), + SET_REFRESHING_TOKEN: mb.commit(SET_REFRESHING_TOKEN), + SET_LAST_TOKEN_REFRESH_TRY: mb.commit(SET_LAST_TOKEN_REFRESH_TRY), + RESET_STATE: mb.commit(RESET_STATE), + + getJWT: mb.dispatch(getJWT), + refreshJWT: mb.dispatch(refreshJWT), + getUser: mb.dispatch(getUser), + getJWTTimeDelta: mb.dispatch(getJWTTimeDelta) +} diff --git a/frontend/src/store/modules/subscriptions.ts b/frontend/src/store/modules/subscriptions.ts index 63dc6aae39c7fe91547d7728043efb5afaaaa110..12d678d652dacd0fc1d695ffa11a52a56e0d5d4a 100644 --- a/frontend/src/store/modules/subscriptions.ts +++ b/frontend/src/store/modules/subscriptions.ts @@ -4,6 +4,7 @@ import {cartesian, flatten, handleError, once} from '@/util/helpers' import {Assignment, Subscription} from '@/models' import {ActionContext, Module} from 'vuex' import {RootState} from '@/store/store' +import { Authentication } from '@/store/modules/authentication'; export const subscriptionMuts = Object.freeze({ SET_SUBSCRIPTIONS: 'SET_SUBSCRIPTIONS', @@ -39,21 +40,21 @@ const subscriptionsModule: Module<SubscriptionsState, RootState> = { getters: { availableTypes (state, getters) { let types = ['random', 'submission_type'] - if (getters.isReviewer) { + if (Authentication.isReviewer) { types.push('exam') } return types }, availableStages (state, getters) { let stages = ['feedback-creation', 'feedback-validation'] - if (getters.isReviewer) { + if (Authentication.isReviewer) { stages.push('feedback-conflict-resolution') } return stages }, availableStagesReadable (state, getters) { let stages = ['create', 'validate'] - if (getters.isReviewer) { + if (Authentication.isReviewer) { stages.push('resolve') } return stages @@ -265,7 +266,7 @@ const subscriptionsModule: Module<SubscriptionsState, RootState> = { dispatch('subscribeTo', {type, stage}) }) case Subscription.QueryTypeEnum.Exam: - if (getters.isReviewer) { + if (Authentication.isReviewer) { const stageKeyCartesian = cartesian( getters.availableStages, getters.availableExamTypeQueryKeys) // @ts-ignore diff --git a/frontend/src/store/modules/ui.ts b/frontend/src/store/modules/ui.ts index 776384463438ccb8bfb091c4118d568a3e350abd..04baacf7e6228f51641321beb1d29ea6bacfc4aa 100644 --- a/frontend/src/store/modules/ui.ts +++ b/frontend/src/store/modules/ui.ts @@ -19,10 +19,8 @@ function initialState (): UIState { const mb = getStoreBuilder<RootState>().module('UI', initialState()) - const stateGetter = mb.state() - function SET_SIDEBAR_COLLAPSED (state: UIState, collapsed: boolean) { state.sideBarCollapsed = collapsed } @@ -37,7 +35,7 @@ function SET_SHOW_JUMBOTRON (state: UIState, val: boolean) { } export const UI = { - get state() { return stateGetter() }, + get state () { return stateGetter() }, SET_SIDEBAR_COLLAPSED: mb.commit(SET_SIDEBAR_COLLAPSED), SET_DARK_MODE: mb.commit(SET_DARK_MODE), diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts index e6ca81843fe422c872d6b243693148cac3df715c..c2c4b9ca300327dbc0260a37fd6f3f44745bc22e 100644 --- a/frontend/src/store/store.ts +++ b/frontend/src/store/store.ts @@ -5,17 +5,20 @@ import {getStoreBuilder} from 'vuex-typex' import studentPage from './modules/student-page' import submissionNotes from './modules/submission-notes' -import authentication from './modules/authentication' import subscriptions from './modules/subscriptions' import feedbackTable from './modules/feedback_list/feedback-table' import feedbackSearchOptions from './modules/feedback_list/feedback-search-options' +import './modules/ui' +import './modules/authentication' + import './mutations' import './actions' import './getters' -import './modules/ui' + import {UIState} from './modules/ui' +import {AuthState} from './modules/authentication' import {lastInteraction} from '@/store/plugins/lastInteractionPlugin' import { @@ -41,7 +44,8 @@ export interface RootInitialState { } export interface RootState extends RootInitialState{ - UI: UIState + UI: UIState, + Authentication: AuthState } export function initialState (): RootInitialState { @@ -68,7 +72,6 @@ export const persistedStateKey = 'grady' const store = getStoreBuilder<RootState>().vuexStore({ strict: process.env.NODE_ENV === 'development', modules: { - authentication, studentPage, submissionNotes, subscriptions, @@ -79,12 +82,12 @@ const store = getStoreBuilder<RootState>().vuexStore({ createPersistedState({ key: persistedStateKey, storage: window.sessionStorage, - // authentication.token is manually saved since using it with this plugin caused issues + // Authentication.token is manually saved since using it with this plugin caused issues // when manually reloading the page paths: Object.keys(initialState()).concat( ['UI', 'studentPage', 'submissionNotes', 'feedbackSearchOptions', 'subscriptions', - 'authentication.user', 'authentication.jwtTimeDelta', - 'authentication.tokenCreationTime']) + 'Authentication.user', 'Authentication.jwtTimeDelta', + 'Authentication.tokenCreationTime']) }), lastInteraction], // TODO is there a better way than casting the initialState ?