Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions common/courses/states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,17 @@ import {
ConversationInfos,
markConversationAsReadOnlyForPupils,
markConversationAsWriteable,
removeParticipantFromCourseChat,
sendSystemMessage,
updateConversation,
} from '../chat';
import systemMessages from '../chat/localization';
import { cancelAppointment } from '../appointment/cancel';
import { User, userForStudent } from '../user';
import { addGroupAppointmentsOrganizer } from '../appointment/participants';
import { addGroupAppointmentsOrganizer, removeGroupAppointmentsOrganizer } from '../appointment/participants';
import { sendPupilCoursePromotion, sendSubcourseCancelNotifications } from './notifications';
import * as Notification from '../../common/notification';
import { deleteAchievementsForSubcourse } from '../../common/achievement/delete';
import { deleteAchievementsForSubcourse, deleteCourseAchievementsForStudents } from '../../common/achievement/delete';
import { ValidationError } from 'apollo-server-express';
import { getContextForGroupAppointmentReminder } from '../appointment/util';
import { isSubcourseSilent } from './util';
Expand Down Expand Up @@ -225,9 +226,9 @@ export async function canCancel(subcourse: Subcourse): Promise<Decision> {
return { allowed: true };
}

export async function cancelSubcourse(user: User, subcourse: Subcourse) {
export async function cancelSubcourse(user: User, subcourse: Subcourse, force?: boolean) {
const can = await canCancel(subcourse);
if (!can.allowed) {
if (!can.allowed && !force) {
throw new Error(`Cannot cancel Subcourse(${subcourse.id}), reason: ${can.reason}`);
}

Expand Down Expand Up @@ -328,3 +329,19 @@ export async function addSubcourseInstructor(user: User | null, subcourse: Subco
});
logger.info(`Student (${newInstructor.id}) was added as an instructor to Subcourse(${subcourse.id}) by User(${user?.userID})`);
}

export async function deleteSubcourseInstructor(user: User | null, subcourse: Subcourse, instructorToBeRemoved: Student) {
const subcourseId = subcourse.id;
const studentId = instructorToBeRemoved.id;
const instructorUser = userForStudent(instructorToBeRemoved);
await prisma.subcourse_instructors_student.delete({ where: { subcourseId_studentId: { subcourseId, studentId } } });
await removeGroupAppointmentsOrganizer(subcourseId, instructorUser.userID, instructorUser.email);
if (subcourse.conversationId) {
await removeParticipantFromCourseChat(instructorUser, subcourse.conversationId);
}
logger.info(`Student(${studentId}) was deleted from Subcourse(${subcourseId}) by User(${user?.userID})`);

await deleteCourseAchievementsForStudents(subcourseId, [instructorUser.userID]);

return true;
}
62 changes: 18 additions & 44 deletions common/student/activation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { isZoomFeatureActive } from '../zoom/util';
import { userForStudent } from '../user';
import { CertificateState } from '../certificate';
import { removeAllPushSubcriptions } from '../notification/channels/push';
import { cancelSubcourse, deleteSubcourseInstructor, subcourseOver } from '../courses/states';

export async function deactivateStudent(
student: Student,
Expand All @@ -27,7 +28,12 @@ export async function deactivateStudent(

await Notification.cancelRemindersFor(userForStudent(student));
// Setting 'active' to false will not send out any notifications during deactivation
student.active = false;
const updatedStudent = await prisma.student.update({
data: { active: false },
where: { id: student.id },
});

await removeAllPushSubcriptions(userForStudent(student));

// Dissolve matches for the student.
const matches = await prisma.match.findMany({
Expand All @@ -46,66 +52,34 @@ export async function deactivateStudent(
data: { state: CertificateState.manual },
});

//Delete course records for the student.
const courses = await prisma.course.findMany({
// Cancel subcourses
const subcourses = await prisma.subcourse.findMany({
where: {
course_instructors_student: {
cancelled: false,
subcourse_instructors_student: {
some: {
studentId: student.id,
},
},
},
include: {
course_instructors_student: true,
subcourse_instructors_student: true,
},
});

for (let i = 0; i < courses.length; i++) {
if (courses[i].course_instructors_student.length > 1) {
await prisma.course.update({
where: {
id: courses[i].id,
},
data: {
course_instructors_student: {
deleteMany: {
studentId: student.id,
},
},
},
});
} else {
await prisma.course.update({
where: {
id: courses[i].id,
},
data: {
subcourse: {
updateMany: {
where: {},
data: {
cancelled: true,
},
},
},
courseState: course_coursestate_enum.cancelled,
},
});
// TODO Notify participants
for (const subcourse of subcourses) {
// There are multiple instructors, so just remove the student from the subcourse
if (subcourse.subcourse_instructors_student.length > 1) {
await deleteSubcourseInstructor(userForStudent(student), subcourse, student);
} else if (!(await subcourseOver(subcourse))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the course is over, we also do not want to remove the instructor if there are multiple instructors?

await cancelSubcourse(userForStudent(student), subcourse, true);
}
}

if (isZoomFeatureActive() && student.zoomUserId) {
await deleteZoomUser(student);
}

await removeAllPushSubcriptions(userForStudent(student));

const updatedStudent = await prisma.student.update({
data: { active: false },
where: { id: student.id },
});

await logTransaction('deActivate', userForStudent(student), { newStatus: false, deactivationReason: reason });

return updatedStudent;
Expand Down
19 changes: 9 additions & 10 deletions graphql/subcourse/mutations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,14 @@ import {
leaveSubcourseWaitinglist,
mentorLeaveSubcourse,
} from '../../common/courses/participants';
import { addSubcourseInstructor, cancelSubcourse, deleteSubcourse, editSubcourse, publishSubcourse } from '../../common/courses/states';
import {
addSubcourseInstructor,
cancelSubcourse,
deleteSubcourse,
deleteSubcourseInstructor,
editSubcourse,
publishSubcourse,
} from '../../common/courses/states';
import { getLogger } from '../../common/logger/logger';
import { prisma } from '../../common/prisma';
import { userForPupil, userForStudent } from '../../common/user';
Expand Down Expand Up @@ -155,15 +162,7 @@ export class MutateSubcourseResolver {
const subcourse = await getSubcourse(subcourseId);
await hasAccess(context, 'Subcourse', subcourse);
const instructorToBeRemoved = await getStudent(studentId);
const instructorUser = userForStudent(instructorToBeRemoved);
await prisma.subcourse_instructors_student.delete({ where: { subcourseId_studentId: { subcourseId, studentId } } });
await removeGroupAppointmentsOrganizer(subcourseId, instructorUser.userID, instructorUser.email);
if (subcourse.conversationId) {
await removeParticipantFromCourseChat(instructorUser, subcourse.conversationId);
}
logger.info(`Student(${studentId}) was deleted from Subcourse(${subcourseId}) by User(${context.user.userID})`);

await deleteCourseAchievementsForStudents(subcourseId, [instructorUser.userID]);
await deleteSubcourseInstructor(context.user, subcourse, instructorToBeRemoved);

return true;
}
Expand Down