diff --git a/frontend/src/api.ts b/frontend/src/api.ts index d5965da6100864e0ef0960842eafa1cbb0e0c8e0..029722cdb187e99772eba76551ceb185fda09243 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -1,6 +1,7 @@ import axios from 'axios' +import {Credentials} from "@/store/modules/authentication"; -function addFieldsToUrl ({url, fields = []}) { +function addFieldsToUrl ({url, fields = []}: {url: string, fields?: string[]}) { return fields.length > 0 ? url + '?fields=pk,' + fields : url } @@ -22,27 +23,27 @@ let ax = axios.create({ } } -export async function registerTutor (credentials) { +export async function registerTutor (credentials: Credentials) { return ax.post('/api/tutor/register/', credentials) } -export async function fetchJWT (credentials) { - const token = (await ax.post('/api/get-token/', credentials)).data.token +export async function fetchJWT (credentials: Credentials): Promise<string> { + const token: string = (await ax.post('/api/get-token/', credentials)).data.token ax.defaults.headers['Authorization'] = `JWT ${token}` return token } -export async function refreshJWT (token) { - const newToken = (await ax.post('/api/refresh-token/', {token})).data.token +export async function refreshJWT (token: string): Promise<string> { + const newToken: string = (await ax.post('/api/refresh-token/', {token})).data.token ax.defaults.headers['Authorization'] = `JWT ${newToken}` return newToken } -export async function fetchJWTTimeDelta () { +export async function fetchJWTTimeDelta (): Promise<number> { return (await ax.get('/api/jwt-time-delta/')).data.timeDelta } -export async function fetchUserRole () { +export async function fetchUserRole (): Promise<string> { return (await ax.get('/api/user-role/')).data.role } @@ -54,11 +55,11 @@ export async function fetchStudentSubmissions () { return (await ax.get('/api/student-submissions/')).data } -export async function fetchSubmissionFeedbackTests ({pk}) { +export async function fetchSubmissionFeedbackTests ({pk}: {pk: string}) { return (await ax.get(`/api/submission/${pk}/`)).data } -export async function fetchAllStudents (fields = []) { +export async function fetchAllStudents (fields: string[] = []) { const url = addFieldsToUrl({ url: '/api/student/', fields @@ -66,7 +67,8 @@ export async function fetchAllStudents (fields = []) { return (await ax.get(url)).data } -export async function fetchStudent ({pk, fields = []}) { +export async function fetchStudent ({pk, fields = []}: + {pk: string, fields?: string[]}) { const url = addFieldsToUrl({ url: `/api/student/${pk}/`, fields @@ -74,7 +76,7 @@ export async function fetchStudent ({pk, fields = []}) { return (await ax.get(url)).data } -export async function fetchAllTutors (fields = []) { +export async function fetchAllTutors (fields: string[] = []) { const url = addFieldsToUrl({ url: '/api/tutor/', fields @@ -86,16 +88,16 @@ export async function fetchSubscriptions () { return (await ax.get('/api/subscription/')).data } -export async function deactivateSubscription ({pk}) { +export async function deactivateSubscription ({pk}: {pk: string}) { const url = `/api/subscription/${pk}/` return (await ax.delete(url)).data } -export async function fetchSubscription (subscriptionPk) { +export async function fetchSubscription (subscriptionPk: string) { return (await ax.get(`/api/subscription/${subscriptionPk}/`)).data } -export async function fetchAllFeedback (fields = []) { +export async function fetchAllFeedback (fields: string[] = []) { const url = addFieldsToUrl({ url: '/api/feedback/', fields @@ -108,7 +110,8 @@ export async function fetchFeedback ({ofSubmission}) { return (await ax.get(url)).data } -export async function fetchExamType ({examPk, fields = []}) { +export async function fetchExamType ({examPk, fields = []}: + {examPk: string, fields?: string[]}) { const url = addFieldsToUrl({ url: `/api/examtype/${examPk !== undefined ? examPk + '/' : ''}`, fields}) @@ -189,7 +192,7 @@ export async function deactivateAllStudentAccess () { return ax.post('/api/student/deactivate/') } -export async function changePassword (userPk, data) { +export async function changePassword (userPk: string, data) { return ax.patch(`/api/user/${userPk}/change_password/`, data) } @@ -197,7 +200,7 @@ export async function getOwnUser () { return (await ax.get('/api/user/me/')).data } -export async function changeActiveForUser (userPk, active) { +export async function changeActiveForUser (userPk: string, active: boolean) { return (await ax.patch(`/api/user/${userPk}/change_active/`, {'is_active': active})).data } diff --git a/frontend/src/components/feedback_list/FeedbackSearchOptions.vue b/frontend/src/components/feedback_list/FeedbackSearchOptions.vue index 03e7d84f650080e083e1c9bfd2a11dba814c10dc..c5ec661f53854323718c3f67843e79355b9c4756 100644 --- a/frontend/src/components/feedback_list/FeedbackSearchOptions.vue +++ b/frontend/src/components/feedback_list/FeedbackSearchOptions.vue @@ -88,32 +88,26 @@ export default { items: [ { name: 'showFinal', - path: 'showFinal', mutation: feedbackSearchOptsMut.SET_SHOW_FINAL }, { name: 'searchOtherUserComments', - path: 'searchOtherUserComments', mutation: feedbackSearchOptsMut.SET_SEARCH_OTHER_USER_COMMENTS }, { name: 'caseSensitive', - path: 'caseSensitive', mutation: feedbackSearchOptsMut.SET_CASE_SENSITIVE }, { name: 'useRegex', - path: 'useRegex', mutation: feedbackSearchOptsMut.SET_USE_REGEX }, { name: 'filterByTutors', - path: 'filterByTutors', mutation: feedbackSearchOptsMut.SET_FILTER_BY_TUTORS }, { name: 'filterByStage', - path: 'filterByStage', mutation: feedbackSearchOptsMut.SET_FILTER_BY_STAGE } diff --git a/frontend/src/components/submission_notes/RouteChangeConfirmation.vue b/frontend/src/components/submission_notes/RouteChangeConfirmation.vue index ce5a4b26ded02686037e41fa744996fae411e8a8..362980ee6d7a14ad660374a0f8f25a67d6424e48 100644 --- a/frontend/src/components/submission_notes/RouteChangeConfirmation.vue +++ b/frontend/src/components/submission_notes/RouteChangeConfirmation.vue @@ -41,10 +41,8 @@ export default { watch: { nextRoute (newVal, oldVal) { if (newVal !== oldVal && this.$store.getters['submissionNotes/workInProgress']) { - console.log('here') this.dialog = true } else { - console.log('there') this.nextRoute() } } diff --git a/frontend/src/store/modules/authentication.ts b/frontend/src/store/modules/authentication.ts index ce8bbb045e98a85a10ec70e979ce41f116b25b11..b0101d5d3dc8faa74228139919d5eca2250b955e 100644 --- a/frontend/src/store/modules/authentication.ts +++ b/frontend/src/store/modules/authentication.ts @@ -1,9 +1,28 @@ import * as api from '@/api' import gradySays from '../grady_speak' +import {ActionContext, Module} from "vuex"; -function initialState () { +export interface Credentials { + username: string, + password: string +} + +interface AuthState { + token: string, + lastTokenRefreshTry: number, + refreshingToken: boolean, + jwtTimeDelta: number, + message: string, + user: { + pk: string, + username: string, + role: string, + is_admin: boolean + } +} +function initialState (): AuthState { return { - token: sessionStorage.getItem('token'), + token: sessionStorage.getItem('token') || '', lastTokenRefreshTry: Date.now(), refreshingToken: false, jwtTimeDelta: 0, @@ -12,7 +31,7 @@ function initialState () { pk: '', username: '', role: '', - is_admin: '' + is_admin: false } } } @@ -27,53 +46,53 @@ export const authMut = Object.freeze({ SET_REFRESHING_TOKEN: 'SET_REFRESHING_TOKEN' }) -const authentication = { +const authentication: Module<AuthState, any> = { state: initialState(), getters: { gradySpeak: () => { return gradySays[Math.floor(Math.random() * gradySays.length)] }, - isStudent: state => { + isStudent: (state: AuthState) => { return state.user.role === 'Student' }, - isTutor: state => { + isTutor: (state: AuthState)=> { return state.user.role === 'Tutor' }, - isReviewer: state => { + isReviewer: (state: AuthState)=> { return state.user.role === 'Reviewer' }, - isTutorOrReviewer: (state, getters) => { + isTutorOrReviewer: (state: AuthState, getters) => { return getters.isTutor || getters.isReviewer }, - isLoggedIn: state => !!state.token + isLoggedIn: (state: AuthState) => !!state.token }, mutations: { - [authMut.SET_MESSAGE] (state, message) { + [authMut.SET_MESSAGE] (state: AuthState, message: string) { state.message = message }, - [authMut.SET_JWT_TOKEN] (state, token) { + [authMut.SET_JWT_TOKEN] (state: AuthState, token: string) { sessionStorage.setItem('token', token) state.token = token }, - [authMut.SET_JWT_TIME_DELTA] (state, timeDelta) { + [authMut.SET_JWT_TIME_DELTA] (state: AuthState, timeDelta: number) { state.jwtTimeDelta = timeDelta }, - [authMut.SET_USER] (state, user) { + [authMut.SET_USER] (state: AuthState, user) { state.user = user }, - [authMut.SET_REFRESHING_TOKEN] (state, refreshing) { + [authMut.SET_REFRESHING_TOKEN] (state: AuthState, refreshing: boolean) { state.refreshingToken = refreshing }, - [authMut.SET_LAST_TOKEN_REFRESH_TRY] (state) { + [authMut.SET_LAST_TOKEN_REFRESH_TRY] (state: AuthState) { state.lastTokenRefreshTry = Date.now() }, - [authMut.RESET_STATE] (state) { + [authMut.RESET_STATE] (state: AuthState) { sessionStorage.setItem('token', '') Object.assign(state, initialState()) } }, actions: { - async getJWT (context, credentials) { + async getJWT (context: ActionContext<AuthState, any>, credentials: Credentials) { try { const token = await api.fetchJWT(credentials) context.commit(authMut.SET_JWT_TOKEN, token) diff --git a/frontend/src/util/helpers.ts b/frontend/src/util/helpers.ts index a582d317c68566142c6babbd593476a511756321..73e8fc494ab7352061c72fd6e696831b58973e6e 100644 --- a/frontend/src/util/helpers.ts +++ b/frontend/src/util/helpers.ts @@ -1,11 +1,12 @@ +import vueInstance from '@/main.ts' -export function nameSpacer (namespace) { - return function (commitType) { +export function nameSpacer (namespace: string) { + return function (commitType: string) { return namespace + commitType } } -export function getObjectValueByPath (obj, path) { +export function getObjectValueByPath (obj: any, path: string): any { // credit: http://stackoverflow.com/questions/6491463/accessing-nested-javascript-objects-with-string-key#comment55278413_6491621 if (!path || path.constructor !== String) return path = path.replace(/\[(\w+)\]/g, '.$1') // convert indexes to properties @@ -22,6 +23,11 @@ export function getObjectValueByPath (obj, path) { return obj } +interface GetSetPair { + get: () => any, + set: (val: object) => void +} + /** * Use this method to generate a computed property accessing the store for a Vue instance. * The get method will return the value at this.$store.state.<path>. @@ -31,17 +37,29 @@ export function getObjectValueByPath (obj, path) { * @param namespace to prepend the mutation type with * @returns {*} */ -export function createComputedGetterSetter ({path, mutation, namespace}) { +export function createComputedGetterSetter ( + {path, mutation, namespace}: + {path: string, mutation: string, namespace:string}): GetSetPair { return { - get () { - return getObjectValueByPath(this.$store.state, path) + get (): any { + return getObjectValueByPath(vueInstance.$store.state, path) }, - set (val) { - this.$store.commit(`${namespace ? namespace + '/' : ''}${mutation}`, val) + set (val: object): void { + vueInstance.$store.commit(`${namespace ? namespace + '/' : ''}${mutation}`, val) } } } +interface StateMapperItem { + name: string, + mutation: string, + path?: string +} + +interface MappedState { + [key: string]: GetSetPair +} + /** * Returns an object of generated computed getter/setter pairs. * Can be used to quickly bind a stores state and corresponding setters to a vue component @@ -49,12 +67,14 @@ export function createComputedGetterSetter ({path, mutation, namespace}) { * @param pathPrefix if set, all items path will be prepended by the path prefix * @param items array that contains objects {name, path, mutation} */ -export function mapStateToComputedGetterSetter ({namespace = '', pathPrefix = '', items = []}) { - return items.reduce((acc, curr) => { +export function mapStateToComputedGetterSetter ( + {namespace = '', pathPrefix = '', items = []}: + {namespace: string, pathPrefix: string, items: StateMapperItem[]}): MappedState { + return items.reduce((acc: MappedState, curr) => { // if no path is give, use name const itemPath = curr.path || curr.name const path = pathPrefix ? `${pathPrefix}.${itemPath}` : itemPath - acc[curr.name] = createComputedGetterSetter({...curr, path, namespace}) + acc[curr.name] = createComputedGetterSetter({mutation: curr.mutation, path, namespace}) return acc }, {}) } @@ -67,13 +87,13 @@ export function cartesian (a, b, ...c) { } // flatten an array -export function flatten (list) { +export function flatten (list: any[]): any[] { return list.reduce( (a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [] ) } -export function objectifyArray (arr, key = 'pk') { +export function objectifyArray<T> (arr: T[], key = 'pk'): {[key: string]: T} { return arr.reduce((acc, curr) => { acc[curr[key]] = curr return acc diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index c19146b1b3e969da65d682034ed2a4d7ab81487e..c800f3ea8e347d2dbec47be5b999bd78f2e119d3 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "target": "esnext", "module": "esnext", - "strict": true, + "strict": false, "jsx": "preserve", "importHelpers": true, "moduleResolution": "node", @@ -16,7 +16,7 @@ ] }, "lib": [ - "es2015", + "es2017", "dom", "dom.iterable", "scripthost" diff --git a/frontend/vue.config.js b/frontend/vue.config.js index 29c09438239fdf4ccbc1d99b034bce7922b8792a..d321253997a1fc75cc8b2ed0b9aabd624ded3303 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -5,7 +5,8 @@ const projectRoot = path.resolve(__dirname) module.exports = { assetsDir: 'static', devServer: { - allowedHosts: ['localhost'] + allowedHosts: ['localhost'], + host: 'localhost' }, configureWebpack: { resolve: {