Skip to content

Commit 79b240d

Browse files
committed
fix permissions, tests use instructor, refactor session store
1 parent bbab79f commit 79b240d

File tree

15 files changed

+141
-143
lines changed

15 files changed

+141
-143
lines changed

conf/permissions.dist.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,9 @@ db_permissions:
3535

3636
Course:
3737
getCourses:
38-
allowed_roles: ['*']
38+
authenticated: true
3939
getCourse:
40-
allowed_roles: ['*']
40+
authenticated: true
4141
updateCourse:
4242
admin_required: true
4343
addCourse:

src/common/models/courses.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,12 +106,12 @@ export class Course extends Model {
106106
*/
107107

108108
export interface ParseableUserCourse {
109-
course_id?: number;
110-
user_id?: number;
111-
course_name?: string;
109+
course_id: number;
110+
user_id: number;
111+
course_name: string;
112112
username?: string;
113113
visible?: boolean;
114-
role?: string;
114+
role: string;
115115
course_dates?: ParseableCourseDates;
116116
}
117117
export class UserCourse extends Model {
@@ -126,6 +126,13 @@ export class UserCourse extends Model {
126126
static ALL_FIELDS = ['course_id', 'course_name', 'visible', 'course_dates',
127127
'user_id', 'username', 'role'];
128128

129+
static DEFAULT_VALUES = {
130+
course_id: 0,
131+
user_id: 0,
132+
course_name: 'DEFAULT_USER_COURSE',
133+
role: 'unknown',
134+
};
135+
129136
get all_field_names(): string[] {
130137
return UserCourse.ALL_FIELDS;
131138
}
@@ -134,7 +141,7 @@ export class UserCourse extends Model {
134141
return ['course_dates'];
135142
}
136143

137-
constructor(params: ParseableUserCourse = {}) {
144+
constructor(params: ParseableUserCourse = UserCourse.DEFAULT_VALUES) {
138145
super();
139146
this.set(params);
140147
}
@@ -171,7 +178,8 @@ export class UserCourse extends Model {
171178
set role(value: string) { this._role = value; }
172179

173180
clone(): UserCourse {
174-
return new UserCourse(this.toObject());
181+
// typescript does not recognize the getters as keys when converting with .toObject()
182+
return new UserCourse(this.toObject() as unknown as ParseableUserCourse);
175183
}
176184

177185
isValid(): boolean {

src/components/common/Login.vue

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,23 +56,21 @@ const login = async () => {
5656
};
5757
const session_info = await checkPassword(username_info);
5858
59-
if (!session_info.logged_in) {
59+
if (!session_info.logged_in || !session_info.user.user_id) {
6060
message.value = i18n.t('authentication.failure');
6161
} else {
6262
// success
6363
session.updateSessionInfo(session_info);
6464
6565
// permissions require access to user courses and respective roles
66-
await session.fetchUserCourses(session_info.user.user_id);
66+
await session.fetchUserCourses();
6767
await permission_store.fetchRoles();
6868
await permission_store.fetchRoutePermissions();
6969
70-
if (session.user.user_id == undefined || session.user.user_id == 0) return;
71-
7270
let forward = localStorage.getItem('afterLogin');
7371
forward ||= (session_info.user.is_admin) ?
7472
'/admin' :
75-
`/users/${session.user.user_id}/courses`;
73+
`/users/${session_info.user.user_id}/courses`;
7674
localStorage.removeItem('afterLogin');
7775
void router.push(forward);
7876
}

src/components/common/UserCourses.vue

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,41 +41,30 @@ import { logger } from 'src/boot/logger';
4141
const session_store = useSessionStore();
4242
const router = useRouter();
4343
44-
const user_courses = computed(() => session_store.user_courses);
45-
4644
const user = computed(() => session_store.user);
4745
4846
// This is used to simplify the UI.
4947
const course_types = computed(() => [
50-
{ name: 'Student', courses: user_courses.value.filter(c => c.role == 'student') },
51-
{ name: 'Instructor', courses: session_store.instructor_user_courses }
48+
{ name: 'Student', courses: session_store.user_courses.filter(c => c.role === 'student') },
49+
{ name: 'Instructor', courses: session_store.user_courses.filter(c => c.role === 'instructor') }
5250
]);
5351
54-
const switchCourse = async (course_id?: number) => {
55-
if (course_id == undefined || course_id === 0) {
52+
const switchCourse = (course_id?: number) => {
53+
if (!course_id) {
5654
logger.error('[UserCourses/switchCourse]: the course_id is 0 or undefined.');
55+
return;
5756
}
58-
const student_course = session_store.student_user_courses.find(c => c.course_id === course_id);
59-
const instructor_course = session_store.instructor_user_courses.find(c => c.course_id === course_id);
60-
if (student_course) {
61-
session_store.setCourse({
62-
course_name: student_course.course_name ?? 'unknown',
63-
course_id: student_course.course_id ?? 0,
64-
role: 'student'
65-
});
66-
await router.push({
57+
session_store.setCourse(course_id);
58+
59+
if (session_store.course.role === 'student') {
60+
void router.push({
6761
name: 'StudentDashboard',
68-
params: { course_id: student_course.course_id }
69-
});
70-
} else if (instructor_course) {
71-
session_store.setCourse({
72-
course_name: instructor_course.course_name ?? 'unknown',
73-
course_id: instructor_course.course_id ?? 0,
74-
role: 'instructor'
62+
params: { course_id }
7563
});
76-
await router.push({
64+
} else if (session_store.course.role === 'instructor') {
65+
void router.push({
7766
name: 'InstructorDashboard',
78-
params: { course_id: instructor_course.course_id }
67+
params: { course_id }
7968
});
8069
}
8170
};

src/components/student/Student.vue

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,7 @@ const loadStudentSets = async () => {
3333
};
3434
3535
const course_id = parseRouteCourseID(route);
36-
const course = session_store.user_courses.find(c => c.course_id === course_id);
37-
if (course) {
38-
session_store.setCourse({
39-
course_id: course_id,
40-
course_name: course.course_name ?? 'unknown'
41-
});
42-
} else {
43-
logger.warn(`Can't find ${course_id} in ${session_store.user_courses
44-
.map((c) => c.course_id).join(', ')}`);
45-
}
36+
session_store.setCourse(course_id);
4637
await loadStudentSets();
4738
4839
watch(() => session_store.course.course_id, async () => {

src/layouts/MenuBar.vue

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
<q-list>
3030
<template v-for="course in user_courses" :key="course.course_id">
3131
<q-item clickable v-close-popup
32-
@click="changeCourse(course.course_id, course.course_name)">
32+
@click="changeCourse(course.course_id)">
3333
<q-item-section>
3434
<q-item-label>{{course.course_name}}</q-item-label>
3535
</q-item-section>
@@ -87,30 +87,17 @@ const full_name = computed(() => session.full_name);
8787
const user_courses = computed(() =>
8888
session.user_courses.filter(course => course.course_name !== current_course_name.value));
8989
90-
const changeCourse = (course_id?: number, course_name?: string) => {
91-
logger.debug(`[MenuBar/changeCourse]: changing the course to ${course_name ?? 'unknown'}`);
92-
const new_course = session.user_courses.find(course => course.course_name === course_name);
93-
const new_course_id = new_course?.course_id ?? 0;
94-
if (!new_course || new_course_id == 0) return;
95-
const role = new_course?.role ?? 'unknown';
96-
97-
if (role == 'unknown') {
98-
logger.error(['MenuBar/changeCourse: the role is not defined']);
99-
}
90+
const changeCourse = (course_id: number) => {
91+
logger.debug(`[MenuBar/changeCourse]: changing the course to #${course_id}`);
92+
session.setCourse(course_id);
10093
10194
// This sets the path to the instructor or student dashboard.
10295
// This only works currently for roles of student/instructor. We'll need to think about
10396
// the UI for other roles.
104-
105-
if (new_course != undefined) {
106-
router.push(`/courses/${new_course_id}/${role}`).then(() => {
107-
session.setCourse({
108-
course_name: new_course.course_name ?? 'unknown',
109-
course_id: new_course_id
110-
});
111-
}).catch(() => {
112-
logger.error('[MenuBar/changeCourse]: Error occurred.');
113-
});
97+
if (!session.course.role || session.course.role == 'unknown') {
98+
logger.error(`[MenuBar/changeCourse]: the role is not defined for course #${course_id}`);
99+
} else {
100+
void router.push(`/courses/${course_id}/${session.course.role}`);
114101
}
115102
};
116103
@@ -121,6 +108,6 @@ const availableLocales = computed(() =>
121108
const logout = async () => {
122109
await endSession();
123110
void session.logout();
124-
void router.push('/login');
111+
void router.push({ name: 'login' });
125112
};
126113
</script>

src/layouts/MenuSidebar.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,23 @@
1313
import { defineComponent, ref, computed } from 'vue';
1414
import { instructor_views, admin_views, student_views, ViewInfo } from 'src/common/views';
1515
import { useRouter, useRoute } from 'vue-router';
16+
import { useSessionStore } from 'src/stores/session';
1617
1718
export default defineComponent({
1819
name: 'MenuSidebar',
1920
setup() {
2021
const route = useRoute();
2122
const router = useRouter();
23+
const session = useSessionStore();
2224
const sidebar_open = ref<boolean>(false);
2325
return {
2426
sidebar_open,
2527
views: computed(() =>
2628
/^\/admin/.exec(route.path)
2729
? admin_views
28-
: /^\/courses\/\d+\/instructor/.exec(route.path)
30+
: session.course.role === 'instructor'
2931
? instructor_views
30-
: /^\/courses\/\d+\/student/.exec(route.path)
32+
: session.course.role === 'student'
3133
? student_views
3234
: []
3335
),

src/pages/instructor/Instructor.vue

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,12 @@ const problem_sets = useProblemSetStore();
3030
const route = useRoute();
3131
3232
const course_id = parseRouteCourseID(route);
33-
const course = session.user_courses.find(c => c.course_id === course_id);
34-
35-
if (course) {
36-
session.setCourse({
37-
course_id,
38-
course_name: course.course_name ?? 'unknown'
39-
});
40-
} else {
41-
logger.warn(`Can't find ${course_id} in ${session.user_courses
42-
.map((c) => c.course_id).join(', ')}`);
33+
34+
// allow the route to update the session's 'selected' course
35+
if (course_id !== session.course.course_id) {
36+
session.setCourse(course_id);
4337
}
38+
4439
await users.fetchGlobalCourseUsers(course_id);
4540
await users.fetchCourseUsers(course_id);
4641
await problem_sets.fetchProblemSets(course_id);

src/stores/session.ts

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,6 @@ export const useSessionStore = defineStore('session', {
4747
getters: {
4848
full_name: (state): string => `${state.user?.first_name ?? ''} ${state.user?.last_name ?? ''}`,
4949
getUser: (state): User => new User(state.user),
50-
student_user_courses: (state) => state.user_courses.filter(c => c.role === 'student'),
51-
instructor_user_courses: (state) => state.user_courses.filter(c => c.role === 'instructor'),
5250
},
5351
actions: {
5452
updateExpiry(expiry: number): void {
@@ -68,16 +66,35 @@ export const useSessionStore = defineStore('session', {
6866
this.user = new User({ username: 'logged_out' }).toObject();
6967
}
7068
},
71-
setCourse(course: CourseInfo): void {
72-
this.course = course;
73-
this.course.role = this.user_courses.find((c) => c.course_id === course.course_id)?.role || '';
69+
setCourse(course_id: number): void {
70+
if (course_id === this.course.course_id) return;
71+
const new_course = this.user_courses.find((c) => c.course_id === course_id);
72+
if (!new_course) {
73+
logger.error(`[session_store] Attempted to select course #${course_id} -- not found!`);
74+
this.course = {
75+
course_id: 0,
76+
course_name: '',
77+
role: 'unknown'
78+
};
79+
return;
80+
}
81+
// in order to be reactive, replace the entire `this.course` object
82+
this.course = {
83+
course_id,
84+
course_name: new_course.course_name,
85+
role: new_course.role
86+
};
7487
},
7588
/**
7689
* fetch all User Courses for a given user.
7790
* @param {number} user_id
7891
*/
79-
async fetchUserCourses(user_id: number): Promise<void> {
80-
const response = await api.get(`users/${user_id}/courses`);
92+
async fetchUserCourses(): Promise<void> {
93+
if (!this.user.user_id) throw {
94+
message: 'No user has been authenticated',
95+
} as ResponseError;
96+
97+
const response = await api.get(`users/${this.user.user_id}/courses`);
8198
if (response.status === 200) {
8299
this.user_courses = response.data as ParseableUserCourse[];
83100
} else {

src/stores/set_problems.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,13 @@ export const useSetProblemStore = defineStore('set_problems', {
106106
* Fetch all set problems with given course_id.
107107
*/
108108
async fetchSetProblems(course_id: number): Promise<void> {
109-
const response = await api.get(`courses/${course_id}/problems`);
110-
const all_problems = response.data as Array<ParseableProblem>;
111-
this.set_problems = all_problems.map((prob) => parseProblem(prob, 'Set') as SetProblem);
109+
const response = await api.get(`courses/${course_id}/problems`)
110+
.then((response) => {
111+
// TODO: throw exceptions via axios hook instead
112+
if (response.status !== 200) throw (response.data as Error);
113+
return response.data as Array<ParseableProblem>;
114+
});
115+
if (response) this.set_problems = response.map((prob) => parseProblem(prob, 'Set') as SetProblem);
112116
},
113117

114118
/**
@@ -118,7 +122,11 @@ export const useSetProblemStore = defineStore('set_problems', {
118122
async addSetProblem(problem: LibraryProblem, set_id: number): Promise<SetProblem> {
119123
if (!problem.isValid()) await invalidError(problem, 'The added problem is invalid');
120124

121-
const course_id = useSessionStore().course.course_id;
125+
const session_store = useSessionStore();
126+
127+
const course_id = session_store.course.course_id;
128+
if (!course_id) logger.error('[addSetProblem] cannot add problem when session course is not set');
129+
122130
const prob = new SetProblem({
123131
problem_params: problem.location_params,
124132
set_id: set_id
@@ -127,7 +135,8 @@ export const useSetProblemStore = defineStore('set_problems', {
127135
// delete the render params. Not in the database.
128136
delete prob.render_params;
129137
const response = await api.post(`/courses/${course_id}/sets/${set_id}/problems`, prob).
130-
catch((e: Error) => logger.error(`[addSetProblem] ${JSON.stringify(prob)} failed with ${e.message}`));
138+
catch((e: Error) =>
139+
logger.error(`[addSetProblem] ${JSON.stringify(prob)} failed with ${e.message}`));
131140

132141
const new_problem = new SetProblem(response.data as ParseableSetProblem);
133142
this.set_problems.push(new_problem);

0 commit comments

Comments
 (0)