diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3e28a1685c64ba88020c82285948b5c495db2663..d302ff0a128d57a68124d2cb0cf9c90212063e97 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -39,7 +39,7 @@ test_pytest:
   <<: *test_definition_virtualenv
   stage: test
   services:
-    - postgres:9.5
+    - postgres:9.6
   script:
     - pytest --cov --ds=grady.settings.test core/tests
   artifacts:
diff --git a/frontend/@types/v-clipboard/index.d.ts b/frontend/@types/v-clipboard/index.d.ts
index f2dd4459b9daac2e8b5cffd0b18fd0fca3741346..54bf07180256d6c40fc30d44b346cb01ebdf61eb 100644
--- a/frontend/@types/v-clipboard/index.d.ts
+++ b/frontend/@types/v-clipboard/index.d.ts
@@ -1 +1 @@
-declare module 'v-clipboard';
\ No newline at end of file
+declare module 'v-clipboard'
diff --git a/frontend/src/components/submission_notes/RouteChangeConfirmation.vue b/frontend/src/components/submission_notes/RouteChangeConfirmation.vue
index 362980ee6d7a14ad660374a0f8f25a67d6424e48..9abcee3be3c7f7bb42a39c934596ff7338647448 100644
--- a/frontend/src/components/submission_notes/RouteChangeConfirmation.vue
+++ b/frontend/src/components/submission_notes/RouteChangeConfirmation.vue
@@ -1,9 +1,9 @@
 <template>
     <v-dialog
       v-model="dialog"
-      max-width="fit-content"
+      max-width="30%"
     >
-      <v-card>
+      <v-card class="text-xs-center">
         <v-card-title class="title">
           Are you sure?
         </v-card-title>
@@ -19,6 +19,8 @@
 </template>
 
 <script>
+import { SubmissionNotes } from '@/store/modules/submission-notes'
+
 export default {
   name: 'route-change-confirmation',
   props: {
@@ -40,7 +42,7 @@ export default {
   },
   watch: {
     nextRoute (newVal, oldVal) {
-      if (newVal !== oldVal && this.$store.getters['submissionNotes/workInProgress']) {
+      if (newVal !== oldVal && SubmissionNotes.workInProgress) {
         this.dialog = true
       } else {
         this.nextRoute()
diff --git a/frontend/src/components/submission_notes/SubmissionCorrection.vue b/frontend/src/components/submission_notes/SubmissionCorrection.vue
index 5339159c8724371c92cb58fbe859c3571c37f766..20378ceed5ac55a06586978e28860d287bd2c863 100644
--- a/frontend/src/components/submission_notes/SubmissionCorrection.vue
+++ b/frontend/src/components/submission_notes/SubmissionCorrection.vue
@@ -63,9 +63,10 @@ import AnnotatedSubmissionTopToolbar from '@/components/submission_notes/toolbar
 import AnnotatedSubmissionBottomToolbar from '@/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar'
 import BaseAnnotatedSubmission from '@/components/submission_notes/base/BaseAnnotatedSubmission'
 import SubmissionLine from '@/components/submission_notes/base/SubmissionLine'
-import {subNotesMut, subNotesNamespace} from '@/store/modules/submission-notes'
+import {SubmissionNotes} from '@/store/modules/submission-notes'
 import RouteChangeConfirmation from '@/components/submission_notes/RouteChangeConfirmation'
 import {Authentication} from '@/store/modules/authentication'
+import { actions } from '@/store/actions'
 
 export default {
   components: {
@@ -96,22 +97,20 @@ export default {
     }
   },
   computed: {
-    ...mapState({
-      showEditorOnLine: state => state.submissionNotes.ui.showEditorOnLine,
-      selectedComment: state => state.submissionNotes.ui.selectedCommentOnLine,
-      origFeedback: state => state.submissionNotes.origFeedback.feedbackLines,
-      updatedFeedback: state => state.submissionNotes.updatedFeedback.feedbackLines,
-      showFeedback: state => state.submissionNotes.ui.showFeedback
-    }),
-    ...mapGetters([
-      'workInProgress'
-    ]),
+    showEditorOnLine () { return SubmissionNotes.state.ui.showEditorOnLine },
+    selectedComment () { return SubmissionNotes.state.ui.selectedCommentOnLine },
+    origFeedback () { return SubmissionNotes.state.origFeedback.feedbackLines },
+    updatedFeedback () { return SubmissionNotes.state.updatedFeedback.feedbackLines },
+    showFeedback () { return SubmissionNotes.state.ui.showFeedback },
+
+    workInProgress () { return SubmissionNotes.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']
+      return SubmissionNotes.submission
     },
     submissionObj () {
       return this.assignment ? this.assignment.submission : this.submissionWithoutAssignment
@@ -122,15 +121,15 @@ export default {
   },
   methods: {
     toggleEditorOnLine (lineNo, comment = '') {
-      this.$store.commit(subNotesNamespace(subNotesMut.TOGGLE_EDITOR_ON_LINE), {lineNo, comment})
+      SubmissionNotes.TOGGLE_EDITOR_ON_LINE({lineNo, comment})
     },
     submitFeedback ({isFinal}) {
       this.loading = true
-      this.$store.dispatch(subNotesNamespace('submitFeedback'), {
+      SubmissionNotes.submitFeedback({
         isFinal: isFinal
       }).then(feedback => {
-        this.$store.commit(subNotesNamespace(subNotesMut.RESET_UPDATED_FEEDBACK))
-        this.$store.commit(subNotesNamespace(subNotesMut.SET_ORIG_FEEDBACK), feedback)
+        SubmissionNotes.RESET_UPDATED_FEEDBACK()
+        SubmissionNotes.SET_ORIG_FEEDBACK(feedback)
         this.$emit('feedbackCreated')
       }).catch(err => {
         this.$notify({
@@ -145,16 +144,16 @@ export default {
     shortPollOrigFeedback () {
       this.feedbackShortPollInterval = setInterval(() => {
         if (this.feedbackObj && this.feedbackObj.ofSubmission) {
-          this.$store.dispatch('getFeedback', {ofSubmission: this.feedbackObj.ofSubmission}).then(feedback => {
-            this.$store.commit(subNotesNamespace(subNotesMut.SET_ORIG_FEEDBACK), feedback)
+          actions.getFeedback({ofSubmission: this.feedbackObj.ofSubmission}).then(feedback => {
+            SubmissionNotes.SET_ORIG_FEEDBACK(feedback)
           })
         }
       }, 5e3)
     },
     init () {
-      this.$store.commit(subNotesNamespace(subNotesMut.RESET_STATE))
-      this.$store.commit(subNotesNamespace(subNotesMut.SET_SUBMISSION), this.submissionObj)
-      this.$store.commit(subNotesNamespace(subNotesMut.SET_ORIG_FEEDBACK), this.feedbackObj)
+      SubmissionNotes.RESET_STATE()
+      SubmissionNotes.SET_SUBMISSION(this.submissionObj)
+      SubmissionNotes.SET_ORIG_FEEDBACK(this.feedbackObj)
     }
   },
   watch: {
diff --git a/frontend/src/components/submission_notes/base/CommentForm.vue b/frontend/src/components/submission_notes/base/CommentForm.vue
index 603f4398ff7b973165c873b1812c844dbc50a699..a75e704b2a5e904b20d1e12092b44128a67aabc4 100644
--- a/frontend/src/components/submission_notes/base/CommentForm.vue
+++ b/frontend/src/components/submission_notes/base/CommentForm.vue
@@ -19,7 +19,7 @@
 </template>
 
 <script>
-import {subNotesMut, subNotesNamespace} from '@/store/modules/submission-notes'
+import {SubmissionNotes} from '@/store/modules/submission-notes'
 
 export default {
   name: 'comment-form',
@@ -48,7 +48,7 @@ export default {
       this.$emit('collapseFeedbackForm')
     },
     submitFeedback () {
-      this.$store.commit(subNotesNamespace(subNotesMut.UPDATE_FEEDBACK_LINE), {
+      SubmissionNotes.UPDATE_FEEDBACK_LINE({
         lineNo: this.lineNo,
         comment: {
           text: this.currentFeedback
diff --git a/frontend/src/components/submission_notes/base/FeedbackComment.vue b/frontend/src/components/submission_notes/base/FeedbackComment.vue
index 29dd835708ecd5e1d01e5bb39b822e4eb327d0e6..a800177667e1c1bb50d1036afdb8af4487118d95 100644
--- a/frontend/src/components/submission_notes/base/FeedbackComment.vue
+++ b/frontend/src/components/submission_notes/base/FeedbackComment.vue
@@ -38,7 +38,7 @@
 <script>
 import {mapState} from 'vuex'
 import {UI} from '@/store/modules/ui'
-import {subNotesMut, subNotesNamespace} from '@/store/modules/submission-notes'
+import {SubmissionNotes} from '@/store/modules/submission-notes'
 
 export default {
   name: 'feedback-comment',
@@ -77,9 +77,7 @@ export default {
     }
   },
   computed: {
-    ...mapState({
-      markedForDeletion: state => state.submissionNotes.commentsMarkedForDeletion
-    }),
+    markedForDeletion () { return SubmissionNotes.state.commentsMarkedForDeletion },
     parsedCreated () {
       if (this.created) {
         return new Date(this.created).toLocaleString()
@@ -101,12 +99,12 @@ export default {
     toggleDeleteComment () {
       if (this.pk) {
         if (!this.markedForDeletion.hasOwnProperty(this.pk)) {
-          this.$store.commit(subNotesNamespace(subNotesMut.MARK_COMMENT_FOR_DELETION), {pk: this.pk})
+          SubmissionNotes.MARK_COMMENT_FOR_DELETION({pk: this.pk})
         } else {
-          this.$store.commit(subNotesNamespace(subNotesMut.UN_MARK_COMMENT_FOR_DELETION), {pk: this.pk})
+          SubmissionNotes.UN_MARK_COMMENT_FOR_DELETION({pk: this.pk})
         }
       }
-      this.$store.commit(subNotesNamespace(subNotesMut.DELETE_FEEDBACK_LINE), this.lineNo)
+      SubmissionNotes.DELETE_FEEDBACK_LINE(this.lineNo)
     }
   }
 }
diff --git a/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar.vue b/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar.vue
index f759a584983b86f2a9842d9c2146c77573b08640..10f9b1ace86abad97b240742dd0f4e288600dc11 100644
--- a/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar.vue
+++ b/frontend/src/components/submission_notes/toolbars/AnnotatedSubmissionBottomToolbar.vue
@@ -54,9 +54,9 @@
 </template>
 
 <script>
-import {subNotesMut, subNotesNamespace} from '@/store/modules/submission-notes'
+import {SubmissionNotes} from '@/store/modules/submission-notes'
 import {Authentication} from '@/store/modules/authentication'
-import { Subscriptions } from '@/store/modules/subscriptions';
+import { Subscriptions } from '@/store/modules/subscriptions'
 
 export default {
   name: 'annotated-submission-bottom-toolbar',
@@ -87,14 +87,14 @@ export default {
   computed: {
     score: {
       get: function () {
-        return this.$store.getters['submissionNotes/score']
+        return SubmissionNotes.score
       },
       set: function (score) {
-        this.$store.commit(subNotesNamespace(subNotesMut.UPDATE_FEEDBACK_SCORE), Number(score))
+        SubmissionNotes.UPDATE_FEEDBACK_SCORE(Number(score))
       }
     },
     showFinalCheckbox () {
-      return !this.$store.getters['submissionNotes/isFeedbackCreation'] || Authentication.isReviewer
+      return !SubmissionNotes.isFeedbackCreation || Authentication.isReviewer
     }
   },
   watch: {
@@ -108,12 +108,12 @@ export default {
   methods: {
     initialFinalStatus () {
       if (this.$route.name === 'subscription') {
-        return !this.$store.getters['submissionNotes/isFeedbackCreation'] || Authentication.isReviewer
+        return !SubmissionNotes.isFeedbackCreation || Authentication.isReviewer
       } else {
         if (this.feedback.hasOwnProperty('isFinal')) {
           return this.feedback.isFinal
         } else {
-          return !this.$store.getters['submissionNotes/isFeedbackCreation'] || Authentication.isReviewer
+          return !SubmissionNotes.isFeedbackCreation || Authentication.isReviewer
         }
       }
     },
diff --git a/frontend/src/components/submission_notes/toolbars/ToggleFeedbackVisibilityButton.vue b/frontend/src/components/submission_notes/toolbars/ToggleFeedbackVisibilityButton.vue
index 2c29239e922f02d3eeef0a233c4a3335705e5555..57fef5ddd79cf8c8c343c7c6eaf1353e8a45874b 100644
--- a/frontend/src/components/submission_notes/toolbars/ToggleFeedbackVisibilityButton.vue
+++ b/frontend/src/components/submission_notes/toolbars/ToggleFeedbackVisibilityButton.vue
@@ -6,16 +6,15 @@
 </template>
 
 <script>
-import {subNotesMut} from '@/store/modules/submission-notes'
+import {SubmissionNotes} from '@/store/modules/submission-notes'
 import {createComputedGetterSetter} from '@/util/helpers'
 
 export default {
   name: 'toggle-feedback-visibility-button',
   computed: {
     showFeedback: createComputedGetterSetter({
-      path: 'submissionNotes.ui.showFeedback',
-      mutation: subNotesMut.SET_SHOW_FEEDBACK,
-      namespace: 'submissionNotes'
+      path: 'SubmissionNotes.ui.showFeedback',
+      mutation: SubmissionNotes.SET_SHOW_FEEDBACK
     })
   }
 }
diff --git a/frontend/src/components/subscriptions/SubscriptionCreation.vue b/frontend/src/components/subscriptions/SubscriptionCreation.vue
index b7d1561ea0de34be32ac6b89de0cc9ebf1fef1f1..4fd3abea03a6abcc54f1d3af99b25c7fb521b5ad 100644
--- a/frontend/src/components/subscriptions/SubscriptionCreation.vue
+++ b/frontend/src/components/subscriptions/SubscriptionCreation.vue
@@ -30,7 +30,7 @@
 
 <script>
 import {Authentication} from '@/store/modules/authentication'
-import { Subscriptions } from '@/store/modules/subscriptions';
+import { Subscriptions } from '@/store/modules/subscriptions'
 
 const stages = [
   {
diff --git a/frontend/src/components/subscriptions/SubscriptionForList.vue b/frontend/src/components/subscriptions/SubscriptionForList.vue
index 77c4bf121cf0bb7d7786222598979e48ef6bbe93..b85d1bf29be465927b9921079d39167f38635c68 100644
--- a/frontend/src/components/subscriptions/SubscriptionForList.vue
+++ b/frontend/src/components/subscriptions/SubscriptionForList.vue
@@ -21,7 +21,7 @@
 <script lang="ts">
 import {Vue, Component, Prop} from 'vue-property-decorator'
 import {Assignment, Subscription} from '@/models'
-import { Subscriptions } from '@/store/modules/subscriptions';
+import { Subscriptions } from '@/store/modules/subscriptions'
 
 @Component
 export default class SubscriptionForList extends Vue {
diff --git a/frontend/src/components/subscriptions/SubscriptionsForStage.vue b/frontend/src/components/subscriptions/SubscriptionsForStage.vue
index 8c044a5240c4ba5aec11ab2c21c53ec9ac1d9784..1e961cbdf5575776adecd01241263785a1b328d0 100644
--- a/frontend/src/components/subscriptions/SubscriptionsForStage.vue
+++ b/frontend/src/components/subscriptions/SubscriptionsForStage.vue
@@ -14,7 +14,7 @@
 
 <script>
 import SubscriptionForList from '@/components/subscriptions/SubscriptionForList'
-import { Subscriptions } from '@/store/modules/subscriptions';
+import { Subscriptions } from '@/store/modules/subscriptions'
 export default {
   components: {
     SubscriptionForList
diff --git a/frontend/src/pages/student/StudentSubmissionPage.vue b/frontend/src/pages/student/StudentSubmissionPage.vue
index e6928c5259bab10ddbf5dcd25af60d66f8750b8d..218a4e7eb6fb0f6356ceb3525f19bfc0a6926259 100644
--- a/frontend/src/pages/student/StudentSubmissionPage.vue
+++ b/frontend/src/pages/student/StudentSubmissionPage.vue
@@ -60,7 +60,7 @@ import BaseAnnotatedSubmission from '@/components/submission_notes/base/BaseAnno
 import SubmissionLine from '@/components/submission_notes/base/SubmissionLine'
 import FeedbackComment from '@/components/submission_notes/base/FeedbackComment'
 import {studentPageMut} from '@/store/modules/student-page'
-import {subNotesMut} from '@/store/modules/submission-notes'
+import {SubmissionNotes} from '@/store/modules/submission-notes'
 import ToggleFeedbackVisibilityButton from '@/components/submission_notes/toolbars/ToggleFeedbackVisibilityButton'
 import SubmissionTests from '@/components/SubmissionTests'
 import NonFinalFeedbackAlert from '@/components/student/NonFinalFeedbackAlert'
@@ -80,24 +80,22 @@ export default {
     id: function () {
       return this.$route.params.id
     },
-    ...mapGetters('submissionNotes', [
-      'submission'
-    ]),
+    submission () { return SubmissionNotes.submission },
     ...mapState({
       submissionType (state) { return state.studentPage.submissionData[this.id].type },
       feedback (state) {
         return state.studentPage.submissionData[this.$route.params.id].feedback
       },
       showFeedback: function (state) {
-        return state.submissionNotes.ui.showFeedback
+        return SubmissionNotes.state.ui.showFeedback
       }
     })
   },
   methods: {
     onRouteMountOrUpdate (routeId) {
       this.$store.commit(studentPageMut.SET_VISITED, { index: routeId, visited: true })
-      this.$store.commit('submissionNotes/' + subNotesMut.SET_SUBMISSION,
-        this.$store.state.studentPage.submissionData[this.id])
+      // TODO this seems fishy and there probably is the student page bug in here
+      SubmissionNotes.SET_SUBMISSION(this.$store.state.studentPage.submissionData[this.id])
     }
   },
   mounted () {
diff --git a/frontend/src/store/actions.ts b/frontend/src/store/actions.ts
index 31250dade5fa3a4c19a212433bf4feaf5320fa15..95a43c0387e043fa923b385011682f980d5b6678 100644
--- a/frontend/src/store/actions.ts
+++ b/frontend/src/store/actions.ts
@@ -1,39 +1,39 @@
-import { ActionContext } from "vuex";
-import { BareActionContext, getStoreBuilder } from "vuex-typex";
+import { ActionContext } from 'vuex'
+import { BareActionContext, getStoreBuilder } from 'vuex-typex'
 
-import { mutations as mut } from "./mutations";
-import { Authentication } from "@/store/modules/authentication";
-import { subNotesMut } from "@/store/modules/submission-notes";
-import * as api from "@/api";
-import router from "@/router/index";
-import { RootState } from "@/store/store";
-import { FeedbackTable } from '@/store/modules/feedback_list/feedback-table';
-import { Subscriptions } from '@/store/modules/subscriptions';
+import { mutations as mut } from './mutations'
+import { Authentication } from '@/store/modules/authentication'
+import { SubmissionNotes } from '@/store/modules/submission-notes'
+import * as api from '@/api'
+import router from '@/router/index'
+import { RootState } from '@/store/store'
+import { FeedbackTable } from '@/store/modules/feedback_list/feedback-table'
+import { Subscriptions } from '@/store/modules/subscriptions'
 
 async function getExamTypes(context: BareActionContext<RootState, RootState>) {
-  const examTypes = await api.fetchExamTypes({});
-  mut.SET_EXAM_TYPES(examTypes);
+  const examTypes = await api.fetchExamTypes({})
+  mut.SET_EXAM_TYPES(examTypes)
 }
 async function updateSubmissionTypes(
   context: BareActionContext<RootState, RootState>,
   fields: Array<string> = []
 ) {
-  const submissionTypes = await api.fetchSubmissionTypes(fields);
+  const submissionTypes = await api.fetchSubmissionTypes(fields)
   submissionTypes.forEach(type => {
-    mut.UPDATE_SUBMISSION_TYPE(type);
-  });
+    mut.UPDATE_SUBMISSION_TYPE(type)
+  })
 }
 async function getStudents(
   context: BareActionContext<RootState, RootState>,
-  opt: { studentPks: Array<string>; fields: Array<string> } = {
+  opt: { studentPks: Array<string>, fields: Array<string> } = {
     studentPks: [],
     fields: []
   }
 ) {
   if (opt.studentPks.length === 0) {
-    const students = await api.fetchAllStudents();
-    mut.SET_STUDENTS(students);
-    return students;
+    const students = await api.fetchAllStudents()
+    mut.SET_STUDENTS(students)
+    return students
   } else {
     const students = await Promise.all(
       opt.studentPks.map((pk: string) =>
@@ -42,33 +42,33 @@ async function getStudents(
           fields: opt.fields
         })
       )
-    );
-    students.forEach(student => mut.SET_STUDENT(student));
-    return students;
+    )
+    students.forEach(student => mut.SET_STUDENT(student))
+    return students
   }
 }
 async function getTutors() {
-  const tutors = await api.fetchAllTutors();
-  mut.SET_TUTORS(tutors);
+  const tutors = await api.fetchAllTutors()
+  mut.SET_TUTORS(tutors)
 }
 async function getFeedback(
   context: BareActionContext<RootState, RootState>,
   fetchFeedbackArg: { ofSubmission: string }
 ) {
-  const feedback = await api.fetchFeedback(fetchFeedbackArg);
-  mut.SET_FEEDBACK(feedback);
-  return feedback;
+  const feedback = await api.fetchFeedback(fetchFeedbackArg)
+  mut.SET_FEEDBACK(feedback)
+  return feedback
 }
 async function getSubmissionFeedbackTest(
   context: BareActionContext<RootState, RootState>,
   submissionPkObj: { pk: string }
 ) {
-  const submission = await api.fetchSubmissionFeedbackTests(submissionPkObj);
-  mut.SET_SUBMISSION(submission);
+  const submission = await api.fetchSubmissionFeedbackTests(submissionPkObj)
+  mut.SET_SUBMISSION(submission)
 }
 async function getStatistics() {
-  const statistics = await api.fetchStatistics();
-  mut.SET_STATISTICS(statistics);
+  const statistics = await api.fetchStatistics()
+  mut.SET_STATISTICS(statistics)
 }
 function logout(
   context: BareActionContext<RootState, RootState>,
@@ -78,23 +78,23 @@ function logout(
   // TODO there has to be a better way
   const { commit, getters, state } = <ActionContext<RootState, RootState>>(
     context
-  );
+  )
   if (Authentication.isStudent) {
     // change active to false when user logs out
     // TODO this should belong in auth module
-    api.changeActiveForUser(Authentication.state.user.pk, false);
+    api.changeActiveForUser(Authentication.state.user.pk, false)
   }
-  mut.RESET_STATE();
+  mut.RESET_STATE()
   FeedbackTable.RESET_STATE()
   Authentication.RESET_STATE()
   Subscriptions.RESET_STATE()
-  commit("submissionNotes/" + subNotesMut.RESET_STATE);
-  Authentication.SET_MESSAGE(message);
-  router.push({ name: "login" });
-  router.go(0);
+  SubmissionNotes.RESET_STATE()
+  Authentication.SET_MESSAGE(message)
+  router.push({ name: "login" })
+  router.go(0)
 }
 
-const mb = getStoreBuilder<RootState>();
+const mb = getStoreBuilder<RootState>()
 
 export const actions = {
   updateSubmissionTypes: mb.dispatch(updateSubmissionTypes),
@@ -105,4 +105,4 @@ export const actions = {
   getSubmissionFeedbackTest: mb.dispatch(getSubmissionFeedbackTest),
   getStatistics: mb.dispatch(getStatistics),
   logout: mb.dispatch(logout)
-};
+}
diff --git a/frontend/src/store/modules/submission-notes.ts b/frontend/src/store/modules/submission-notes.ts
index 734acc868778a39bbc3efe80abe967ede7647931..4dfd9d2997e4a7a64475475d257c92a5a9cec4a3 100644
--- a/frontend/src/store/modules/submission-notes.ts
+++ b/frontend/src/store/modules/submission-notes.ts
@@ -5,24 +5,11 @@ import {nameSpacer} from '@/util/helpers'
 import {Feedback, FeedbackComment, Submission, SubmissionNoType} from '@/models'
 import {RootState} from '@/store/store'
 import {Module} from 'vuex'
+import { getStoreBuilder, BareActionContext } from 'vuex-typex'
 
 export const subNotesNamespace = nameSpacer('submissionNotes/')
 
-export const subNotesMut = Object.freeze({
-  SET_SUBMISSION: 'SET_SUBMISSION',
-  SET_ORIG_FEEDBACK: 'SET_ORIG_FEEDBACK',
-  SET_SHOW_FEEDBACK: 'SET_SHOW_FEEDBACK',
-  UPDATE_FEEDBACK_LINE: 'UPDATE_FEEDBACK_LINE',
-  UPDATE_FEEDBACK_SCORE: 'UPDATE_FEEDBACK_SCORE',
-  DELETE_FEEDBACK_LINE: 'DELETE_FEEDBACK_LINE',
-  TOGGLE_EDITOR_ON_LINE: 'TOGGLE_EDITOR_ON_LINE',
-  MARK_COMMENT_FOR_DELETION: 'MARK_COMMENT_FOR_DELETION',
-  UN_MARK_COMMENT_FOR_DELETION: 'UN_MARK_COMMENT_FOR_DELETION',
-  RESET_UPDATED_FEEDBACK: 'RESET_UPDATED_FEEDBACK',
-  RESET_STATE: 'RESET_STATE'
-})
-
-interface SubmissionNotesState {
+export interface SubmissionNotesState {
   submission: SubmissionNoType
   ui: {
       showEditorOnLine: {[lineNo: number]: boolean}
@@ -64,114 +51,133 @@ function initialState (): SubmissionNotesState {
   }
 }
 
-const submissionNotes: Module<SubmissionNotesState, RootState> = {
-  namespaced: true,
-  state: initialState(),
-  getters: {
-    submissionType: (state, getters, rootState) => {
-      return rootState.submissionTypes[state.submission.type]
-    },
-    // highlight the submission the reduce the string
-    // submission.text into an object where the keys are the
-    // line indexes starting at one and the values the corresponding submission line
-    // this makes iterating over the submission much more pleasant
-    submission: (state, getters) => {
-      const language = getters.submissionType
-        ? getters.submissionType.programmingLanguage
-        : 'c'
-      const highlighted = hljs.highlight(language, state.submission.text || '', true).value
-      return highlighted.split('\n').reduce((acc: {[k: number]: string}, cur, index) => {
-        acc[index + 1] = cur
-        return acc
-      }, {})
-    },
-    score: state => {
-      return state.updatedFeedback.score !== undefined ? state.updatedFeedback.score : state.origFeedback.score
-    },
-    workInProgress: state => {
-      const openEditor = Object.values(state.ui.showEditorOnLine).reduce((acc, curr) => acc || curr, false)
-      const feedbackWritten = Object.entries(state.updatedFeedback.feedbackLines || {}).length > 0
-      return openEditor || feedbackWritten
-    },
-    isFeedbackCreation: state => {
-      return !state.origFeedback['feedbackStageForUser'] ||
-        state.origFeedback['feedbackStageForUser'] === 'feedback-creation'
-    }
-  },
-  mutations: {
-    [subNotesMut.SET_SUBMISSION]: function (state, submission: SubmissionNoType) {
-      state.submission = submission
-    },
-    [subNotesMut.SET_ORIG_FEEDBACK]: function (state, feedback: Feedback) {
-      if (feedback) {
-        state.origFeedback = feedback
-        state.hasOrigFeedback = true
-      }
-    },
-    [subNotesMut.SET_SHOW_FEEDBACK]: function (state, val: boolean) {
-      state.ui.showFeedback = val
-    },
-    [subNotesMut.UPDATE_FEEDBACK_LINE]: function (state, feedback: {lineNo: number, comment: Partial<FeedbackComment>}) {
-      // explicit .toString() on lineNo is necessary because of Vue.set typings
-      if (state.updatedFeedback.feedbackLines) {
-          Vue.set(state.updatedFeedback.feedbackLines, feedback.lineNo.toString(), feedback.comment)
-      }
-    },
-    [subNotesMut.UPDATE_FEEDBACK_SCORE]: function (state, score) {
-      state.updatedFeedback.score = score
-    },
-    [subNotesMut.DELETE_FEEDBACK_LINE]: function (state, lineNo) {
-      if (state.updatedFeedback.feedbackLines) {
-          Vue.delete(state.updatedFeedback.feedbackLines, lineNo)
-      }
-    },
-    [subNotesMut.TOGGLE_EDITOR_ON_LINE]: function (state, {lineNo, comment}) {
-      Vue.set(state.ui.selectedCommentOnLine, lineNo, comment)
-      Vue.set(state.ui.showEditorOnLine, lineNo, !state.ui.showEditorOnLine[lineNo])
-    },
-    [subNotesMut.MARK_COMMENT_FOR_DELETION]: function (state, comment) {
-      Vue.set(state.commentsMarkedForDeletion, comment.pk, comment)
-    },
-    [subNotesMut.UN_MARK_COMMENT_FOR_DELETION]: function (state, comment) {
-      Vue.delete(state.commentsMarkedForDeletion, comment.pk)
-    },
-    [subNotesMut.RESET_UPDATED_FEEDBACK]: function (state) {
-      state.updatedFeedback = initialState().updatedFeedback
-    },
-    [subNotesMut.RESET_STATE]: function (state) {
-      Object.assign(state, initialState())
-    }
-  },
-  actions: {
-    deleteComments: async function ({state}) {
-      return Promise.all(
-        Object.values(state.commentsMarkedForDeletion).map(comment => {
-          return api.deleteComment(comment)
-        })
-      )
-    },
-    submitFeedback: async function ({state, dispatch, getters}, {isFinal = false}) {
-      let feedback: Partial<Feedback> = {
-        isFinal: isFinal,
-        ofSubmission: state.submission.pk
-      }
-      if (Object.keys(state.updatedFeedback.feedbackLines || {}).length > 0) {
-        feedback.feedbackLines = state.updatedFeedback.feedbackLines
-      }
-      if (state.origFeedback.score === undefined && state.updatedFeedback.score === undefined) {
-        throw new Error('You need to give a score.')
-      } else if (state.updatedFeedback.score !== undefined) {
-        feedback.score = state.updatedFeedback.score
-      }
-      await dispatch('deleteComments')
-      if (!state.hasOrigFeedback) {
-        return api.submitFeedbackForAssignment({feedback})
-      } else {
-        feedback.pk = state.origFeedback.pk
-        return api.submitUpdatedFeedback(<{feedback: Feedback}> {feedback})
-      }
-    }
+const mb = getStoreBuilder<RootState>().module('SubmissionNotes', initialState())
+
+const stateGetter = mb.state()
+
+const submissionTypeGetter = mb.read(function submissionType (state, getters, rootState) {
+  return rootState.submissionTypes[state.submission.type]
+})
+// highlight the submission the reduce the string
+// submission.text into an object where the keys are the
+// line indexes starting at one and the values the corresponding submission line
+// this makes iterating over the submission much more pleasant
+const submissionGetter = mb.read(function submission (state, getters) {
+  const language = getters.submissionType
+    ? getters.submissionType.programmingLanguage
+    : 'c'
+  const highlighted = hljs.highlight(language, state.submission.text || '', true).value
+  return highlighted.split('\n').reduce((acc: {[k: number]: string}, cur, index) => {
+    acc[index + 1] = cur
+    return acc
+  }, {})
+})
+const scoreGetter = mb.read(function score (state) {
+  return state.updatedFeedback.score !== undefined ? state.updatedFeedback.score : state.origFeedback.score
+})
+const workInProgressGetter = mb.read(function workInProgress (state) {
+  const openEditor = Object.values(state.ui.showEditorOnLine).reduce((acc, curr) => acc || curr, false)
+  const feedbackWritten = Object.entries(state.updatedFeedback.feedbackLines || {}).length > 0
+  return openEditor || feedbackWritten
+})
+const isFeedbackCreationGetter = mb.read(function isFeedbackCreation (state) {
+  return !state.origFeedback['feedbackStageForUser'] ||
+    state.origFeedback['feedbackStageForUser'] === 'feedback-creation'
+})
+
+function SET_SUBMISSION (state: SubmissionNotesState, submission: SubmissionNoType) {
+  state.submission = submission
+}
+function SET_ORIG_FEEDBACK (state: SubmissionNotesState, feedback: Feedback) {
+  if (feedback) {
+    state.origFeedback = feedback
+    state.hasOrigFeedback = true
+  }
+}
+function SET_SHOW_FEEDBACK (state: SubmissionNotesState, val: boolean) {
+  state.ui.showFeedback = val
+}
+function UPDATE_FEEDBACK_LINE (state: SubmissionNotesState, feedback: {lineNo: number, comment: Partial<FeedbackComment>}) {
+  // explicit .toString() on lineNo is necessary because of Vue.set typings
+  if (state.updatedFeedback.feedbackLines) {
+      Vue.set(state.updatedFeedback.feedbackLines, feedback.lineNo.toString(), feedback.comment)
+  }
+}
+function UPDATE_FEEDBACK_SCORE (state: SubmissionNotesState, score: number) {
+  state.updatedFeedback.score = score
+}
+function DELETE_FEEDBACK_LINE (state: SubmissionNotesState, lineNo: number) {
+  if (state.updatedFeedback.feedbackLines) {
+    // Vue dosn't like objects that are indexed with numbers...
+    Vue.delete(state.updatedFeedback.feedbackLines, <string><any>lineNo)
+  }
+}
+function TOGGLE_EDITOR_ON_LINE (state: SubmissionNotesState, {lineNo, comment}: {lineNo: number, comment: FeedbackComment}) {
+  Vue.set(state.ui.selectedCommentOnLine, <string><any>lineNo, comment)
+  Vue.set(state.ui.showEditorOnLine, <string><any> lineNo, !state.ui.showEditorOnLine[lineNo])
+}
+function MARK_COMMENT_FOR_DELETION (state: SubmissionNotesState, comment: FeedbackComment) {
+  Vue.set(state.commentsMarkedForDeletion, comment.pk, comment)
+}
+function UN_MARK_COMMENT_FOR_DELETION (state: SubmissionNotesState, comment: FeedbackComment) {
+  Vue.delete(state.commentsMarkedForDeletion, comment.pk)
+}
+function RESET_UPDATED_FEEDBACK (state: SubmissionNotesState) {
+  state.updatedFeedback = initialState().updatedFeedback
+}
+function RESET_STATE (state: SubmissionNotesState) {
+  Object.assign(state, initialState())
+}
+
+async function deleteComments ({state}: BareActionContext<SubmissionNotesState, RootState>) {
+  return Promise.all(
+    Object.values(state.commentsMarkedForDeletion).map(comment => {
+      return api.deleteComment(comment)
+    })
+  )
+}
+async function submitFeedback ({state}: BareActionContext<SubmissionNotesState, RootState>, {isFinal = false}) {
+  let feedback: Partial<Feedback> = {
+    isFinal: isFinal,
+    ofSubmission: state.submission.pk
+  }
+  if (Object.keys(state.updatedFeedback.feedbackLines || {}).length > 0) {
+    feedback.feedbackLines = state.updatedFeedback.feedbackLines
+  }
+  if (state.origFeedback.score === undefined && state.updatedFeedback.score === undefined) {
+    throw new Error('You need to give a score.')
+  } else if (state.updatedFeedback.score !== undefined) {
+    feedback.score = state.updatedFeedback.score
+  }
+  await SubmissionNotes.deleteComments()
+  if (!state.hasOrigFeedback) {
+    return api.submitFeedbackForAssignment({feedback})
+  } else {
+    feedback.pk = state.origFeedback.pk
+    return api.submitUpdatedFeedback(<{feedback: Feedback}> {feedback})
   }
 }
 
-export default submissionNotes
+export const SubmissionNotes = {
+  get state () { return stateGetter() },
+  get submissionType () { return submissionTypeGetter() },
+  get submission () { return submissionGetter() },
+  get score () { return scoreGetter() },
+  get workInProgress () { return workInProgressGetter() },
+  get isFeedbackCreation () { return isFeedbackCreationGetter() },
+
+  SET_SUBMISSION: mb.commit(SET_SUBMISSION),
+  SET_ORIG_FEEDBACK: mb.commit(SET_ORIG_FEEDBACK),
+  SET_SHOW_FEEDBACK: mb.commit(SET_SHOW_FEEDBACK),
+  UPDATE_FEEDBACK_LINE: mb.commit(UPDATE_FEEDBACK_LINE),
+  UPDATE_FEEDBACK_SCORE: mb.commit(UPDATE_FEEDBACK_SCORE),
+  DELETE_FEEDBACK_LINE: mb.commit(DELETE_FEEDBACK_LINE),
+  TOGGLE_EDITOR_ON_LINE: mb.commit(TOGGLE_EDITOR_ON_LINE),
+  MARK_COMMENT_FOR_DELETION: mb.commit(MARK_COMMENT_FOR_DELETION),
+  UN_MARK_COMMENT_FOR_DELETION: mb.commit(UN_MARK_COMMENT_FOR_DELETION),
+  RESET_UPDATED_FEEDBACK: mb.commit(RESET_UPDATED_FEEDBACK),
+  RESET_STATE: mb.commit(RESET_STATE),
+
+  deleteComments: mb.dispatch(deleteComments),
+  submitFeedback: mb.dispatch(submitFeedback)
+}
diff --git a/frontend/src/store/store.ts b/frontend/src/store/store.ts
index c208af27350ae75c80c442856a5a43fb902645bc..f5c32aa8055c0046440a6cc58fcda4e41fc2cc5b 100644
--- a/frontend/src/store/store.ts
+++ b/frontend/src/store/store.ts
@@ -4,13 +4,13 @@ import createPersistedState from 'vuex-persistedstate'
 import {getStoreBuilder} from 'vuex-typex'
 
 import studentPage from './modules/student-page'
-import submissionNotes from './modules/submission-notes'
 
 import './modules/ui'
 import './modules/authentication'
 import './modules/feedback_list/feedback-search-options'
 import './modules/feedback_list/feedback-table'
 import './modules/subscriptions'
+import './modules/submission-notes'
 
 import './mutations'
 import './actions'
@@ -22,6 +22,7 @@ import {AuthState} from './modules/authentication'
 import {FeedbackSearchOptionsState, FeedbackSearchOptions} from './modules/feedback_list/feedback-search-options'
 import {Subscriptions, SubscriptionsState} from './modules/subscriptions'
 import {FeedbackTableState, FeedbackTable} from './modules/feedback_list/feedback-table'
+import { SubmissionNotesState } from './modules/submission-notes'
 
 import {lastInteraction} from '@/store/plugins/lastInteractionPlugin'
 import {
@@ -51,7 +52,8 @@ export interface RootState extends RootInitialState{
   Authentication: AuthState,
   FeedbackSearchOptions: FeedbackSearchOptionsState,
   FeedbackTable: FeedbackTableState,
-  Subscriptions: SubscriptionsState
+  Subscriptions: SubscriptionsState,
+  SubmissionNotes: SubmissionNotesState
 }
 
 export function initialState (): RootInitialState {
@@ -78,8 +80,7 @@ export const persistedStateKey = 'grady'
 const store = getStoreBuilder<RootState>().vuexStore({
   strict: process.env.NODE_ENV === 'development',
   modules: {
-    studentPage,
-    submissionNotes
+    studentPage
   },
   plugins: [
     createPersistedState({
diff --git a/frontend/src/util/helpers.ts b/frontend/src/util/helpers.ts
index 8d8fe65cd457ba1dfd64a123c41fce51c59d14a8..dae1e7e97a61d633ac55d8b2ab02da7e1feb959b 100644
--- a/frontend/src/util/helpers.ts
+++ b/frontend/src/util/helpers.ts
@@ -1,6 +1,6 @@
-import {AxiosError} from "axios";
-import {Dispatch} from "vuex";
-import {MutationHandler} from "vuex-typex";
+import {AxiosError} from "axios"
+import {Dispatch} from "vuex"
+import {MutationHandler} from "vuex-typex"
 
 export function nameSpacer (namespace: string) {
   return function (commitType: string) {