feat(course): reset student course progress (#671)
* feat(course): add reset student course progress action Give course admins and tutors a destructive reset-progress flow on the student people detail page. The backend clears learner progress for one (courseId, memberId) pair in a single transaction while keeping the groupmember enrolled, and logs an audit summary. The dashboard shows a confirmation dialog with impact counts from analytics before deleting. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(i18n): add localized reset progress strings for all locales Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(course): scope reset progress to course data only Stop deleting org-level community questions and answers during course progress reset. Fix course newsfeed deletion order so comments on a student's posts are removed before the posts themselves. Restrict reset to student members, move analytics loading to the person detail layout, and rename impact copy to course newsfeed only. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> * fix(course): address Greptile review on reset progress Make progressImpact opt-in via includeProgressImpact query param so analytics requests avoid extra DB work unless the reset dialog needs it. Count only student-authored course newsfeed posts and comments in the impact summary to avoid double-counting replies on the student's posts. Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Rotimi Best <rotimi-best@users.noreply.github.com>
This commit is contained in:
co-authored by
Rotimi Best
Cursor Agent
parent
e43d8a2444
commit
a695c97eda
@@ -1,3 +1,4 @@
|
||||
import { ROLE } from '@cio/utils/constants';
|
||||
import { AppError, ErrorCodes } from '@api/utils/errors';
|
||||
import {
|
||||
addCourseMember,
|
||||
@@ -7,8 +8,8 @@ import {
|
||||
getCourseTeachers,
|
||||
updateCourseMember
|
||||
} from '@cio/db/queries/course/people';
|
||||
import { resetStudentCourseProgress } from '@cio/db/queries/course/reset-progress';
|
||||
|
||||
import { ROLE } from '@cio/utils/constants';
|
||||
import type { TAddCourseMembers } from '@cio/utils/validation/course/people';
|
||||
import type { TGroupmember } from '@cio/db/types';
|
||||
import { getDashboardBaseUrl } from '@cio/core/config/dashboard-url';
|
||||
@@ -316,3 +317,43 @@ export async function deleteMember(courseId: string, memberId: string) {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all learner progress for a course member while keeping them enrolled.
|
||||
*/
|
||||
export async function resetMemberCourseProgress(courseId: string, memberId: string, actorProfileId: string) {
|
||||
try {
|
||||
const member = await getCourseMember(courseId, memberId);
|
||||
if (!member || !member.profileId) {
|
||||
throw new AppError('Course member not found', ErrorCodes.NOT_FOUND, 404);
|
||||
}
|
||||
|
||||
if (member.roleId !== ROLE.STUDENT) {
|
||||
throw new AppError('Only student progress can be reset', ErrorCodes.VALIDATION_ERROR, 400);
|
||||
}
|
||||
|
||||
const summary = await resetStudentCourseProgress({
|
||||
courseId,
|
||||
groupMemberId: memberId,
|
||||
profileId: member.profileId
|
||||
});
|
||||
|
||||
console.info('resetMemberCourseProgress', {
|
||||
actorProfileId,
|
||||
courseId,
|
||||
memberId,
|
||||
summary
|
||||
});
|
||||
|
||||
return summary;
|
||||
} catch (error) {
|
||||
if (error instanceof AppError) {
|
||||
throw error;
|
||||
}
|
||||
throw new AppError(
|
||||
error instanceof Error ? error.message : 'Failed to reset course member progress',
|
||||
ErrorCodes.INTERNAL_ERROR,
|
||||
500
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user