@playcademy/sandbox 0.3.17-beta.32 → 0.3.17-beta.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +149 -33
- package/dist/server.js +149 -33
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1330,7 +1330,7 @@ var package_default;
|
|
|
1330
1330
|
var init_package = __esm(() => {
|
|
1331
1331
|
package_default = {
|
|
1332
1332
|
name: "@playcademy/sandbox",
|
|
1333
|
-
version: "0.3.17-beta.
|
|
1333
|
+
version: "0.3.17-beta.34",
|
|
1334
1334
|
description: "Local development server for Playcademy game development",
|
|
1335
1335
|
type: "module",
|
|
1336
1336
|
exports: {
|
|
@@ -30433,6 +30433,13 @@ function resolveAdminEventTime(data) {
|
|
|
30433
30433
|
}
|
|
30434
30434
|
return toAttributionEventTime(data.date);
|
|
30435
30435
|
}
|
|
30436
|
+
function compareEnrollmentsByRecency(a, b) {
|
|
30437
|
+
const dateCompare = (b.beginDate ?? "").localeCompare(a.beginDate ?? "");
|
|
30438
|
+
if (dateCompare !== 0) {
|
|
30439
|
+
return dateCompare;
|
|
30440
|
+
}
|
|
30441
|
+
return (b.dateLastModified ?? "").localeCompare(a.dateLastModified ?? "");
|
|
30442
|
+
}
|
|
30436
30443
|
var init_timeback_admin_util = __esm(() => {
|
|
30437
30444
|
init_errors();
|
|
30438
30445
|
});
|
|
@@ -30947,6 +30954,7 @@ class TimebackAdminService {
|
|
|
30947
30954
|
}
|
|
30948
30955
|
async getStudentEnrollmentsByCourseId(client, studentId, courseIds, options) {
|
|
30949
30956
|
const enrollments = new Map;
|
|
30957
|
+
const allEnrollments = new Map;
|
|
30950
30958
|
const entries = await Promise.all(courseIds.map(async (courseId) => {
|
|
30951
30959
|
const roster = await client.oneroster.enrollments.listByCourse(courseId, {
|
|
30952
30960
|
includeInactive: options?.includeInactive,
|
|
@@ -30958,28 +30966,30 @@ class TimebackAdminService {
|
|
|
30958
30966
|
if (aActive !== bActive) {
|
|
30959
30967
|
return aActive ? -1 : 1;
|
|
30960
30968
|
}
|
|
30961
|
-
return (
|
|
30969
|
+
return compareEnrollmentsByRecency(a.enrollment, b.enrollment);
|
|
30962
30970
|
});
|
|
30963
|
-
return { courseId,
|
|
30971
|
+
return { courseId, matches };
|
|
30964
30972
|
}));
|
|
30965
|
-
for (const { courseId,
|
|
30966
|
-
|
|
30967
|
-
|
|
30968
|
-
|
|
30969
|
-
|
|
30970
|
-
|
|
30971
|
-
|
|
30972
|
-
|
|
30973
|
-
|
|
30974
|
-
|
|
30975
|
-
|
|
30976
|
-
|
|
30977
|
-
|
|
30978
|
-
|
|
30979
|
-
|
|
30973
|
+
for (const { courseId, matches } of entries) {
|
|
30974
|
+
const records = matches.map((match) => ({
|
|
30975
|
+
id: match.enrollment.sourcedId,
|
|
30976
|
+
status: match.enrollment.status ?? "active",
|
|
30977
|
+
role: match.enrollment.role ?? "student",
|
|
30978
|
+
beginDate: match.enrollment.beginDate ?? null,
|
|
30979
|
+
endDate: match.enrollment.endDate ?? null,
|
|
30980
|
+
course: {
|
|
30981
|
+
id: courseId,
|
|
30982
|
+
title: match.class?.title ?? "",
|
|
30983
|
+
subjects: null,
|
|
30984
|
+
grades: null
|
|
30985
|
+
}
|
|
30986
|
+
}));
|
|
30987
|
+
if (records.length > 0) {
|
|
30988
|
+
enrollments.set(courseId, records[0]);
|
|
30980
30989
|
}
|
|
30990
|
+
allEnrollments.set(courseId, records);
|
|
30981
30991
|
}
|
|
30982
|
-
return { enrollments };
|
|
30992
|
+
return { enrollments, allEnrollments };
|
|
30983
30993
|
}
|
|
30984
30994
|
async assertStudentEnrolledInCourse(client, studentId, courseId) {
|
|
30985
30995
|
const enrollments = await client.edubridge.enrollments.listByUser(studentId);
|
|
@@ -31066,7 +31076,7 @@ class TimebackAdminService {
|
|
|
31066
31076
|
const enrollmentId = rosterEntry.enrollment.sourcedId || null;
|
|
31067
31077
|
const summary = enrollmentId ? analyticsByEnrollmentId.get(enrollmentId) : undefined;
|
|
31068
31078
|
const analyticsUnavailable = Boolean(enrollmentId) && summary?.analyticsAvailable !== true;
|
|
31069
|
-
const name3 = rosterEntry.user ? `${rosterEntry.user.givenName} ${rosterEntry.user.familyName}`.trim() :
|
|
31079
|
+
const name3 = rosterEntry.user ? `${rosterEntry.user.givenName} ${rosterEntry.user.familyName}`.trim() || "No name specified" : "No name specified";
|
|
31070
31080
|
const inactive = rosterEntry.enrollment.status === "tobedeleted";
|
|
31071
31081
|
return {
|
|
31072
31082
|
studentId: rosterEntry.enrollment.user.sourcedId,
|
|
@@ -31111,14 +31121,15 @@ class TimebackAdminService {
|
|
|
31111
31121
|
throw new NotFoundError("Timeback integration", gameId);
|
|
31112
31122
|
}
|
|
31113
31123
|
const courseIds = new Set(integrations.map((integration) => integration.courseId));
|
|
31114
|
-
const { enrollments: enrollmentsByCourseId } = await this.getStudentEnrollmentsByCourseId(client, studentId, [...courseIds], {
|
|
31124
|
+
const { enrollments: enrollmentsByCourseId, allEnrollments: allEnrollmentsByCourseId } = await this.getStudentEnrollmentsByCourseId(client, studentId, [...courseIds], {
|
|
31115
31125
|
includeInactive: true
|
|
31116
31126
|
});
|
|
31117
31127
|
if (enrollmentsByCourseId.size === 0) {
|
|
31118
31128
|
throw new NotFoundError("Student enrollment", courseId ? `${studentId}:${courseId}` : `${studentId}:${gameId}`);
|
|
31119
31129
|
}
|
|
31130
|
+
const allEnrollmentIds = [...allEnrollmentsByCourseId.values()].flat().map((enrollment) => enrollment.id);
|
|
31120
31131
|
const studentProfile = await client.oneroster.users.get(studentId);
|
|
31121
|
-
const analyticsByEnrollmentId = await this.loadEnrollmentAnalyticsSummaries(
|
|
31132
|
+
const analyticsByEnrollmentId = await this.loadEnrollmentAnalyticsSummaries(allEnrollmentIds);
|
|
31122
31133
|
const [masterableUnitsByCourse, completionStatusByCourse] = await Promise.all([
|
|
31123
31134
|
this.getMasterableUnitsByCourse(integrations.map((integration) => integration.courseId)),
|
|
31124
31135
|
this.getCompletionStatusByCourse(client, integrations.map((integration) => integration.courseId), studentId)
|
|
@@ -31129,6 +31140,24 @@ class TimebackAdminService {
|
|
|
31129
31140
|
const masterableUnits = masterableUnitsByCourse.get(integration.courseId);
|
|
31130
31141
|
const analyticsUnavailable = Boolean(enrollment?.id) && summary?.analyticsAvailable !== true;
|
|
31131
31142
|
const inactive = enrollment?.status === "tobedeleted";
|
|
31143
|
+
const courseEnrollments = allEnrollmentsByCourseId.get(integration.courseId) ?? [];
|
|
31144
|
+
const enrollmentSummaries = courseEnrollments.length > 1 ? courseEnrollments.map((record) => {
|
|
31145
|
+
const recordSummary = analyticsByEnrollmentId.get(record.id);
|
|
31146
|
+
const recordAnalyticsUnavailable = recordSummary?.analyticsAvailable !== true;
|
|
31147
|
+
return {
|
|
31148
|
+
enrollmentId: record.id,
|
|
31149
|
+
status: record.status === "tobedeleted" ? "tobedeleted" : "active",
|
|
31150
|
+
beginDate: record.beginDate,
|
|
31151
|
+
endDate: record.endDate,
|
|
31152
|
+
analyticsUnavailable: recordAnalyticsUnavailable,
|
|
31153
|
+
totalXp: recordSummary?.totalXp ?? 0,
|
|
31154
|
+
todayXp: recordSummary?.todayXp ?? 0,
|
|
31155
|
+
activeTimeSeconds: recordSummary?.activeTimeSeconds ?? 0,
|
|
31156
|
+
masteredUnits: recordSummary?.masteredUnits ?? 0,
|
|
31157
|
+
pctCompleteApp: TimebackAdminService.computeCompletionPct(recordSummary?.masteredUnits ?? 0, masterableUnits),
|
|
31158
|
+
history: recordSummary?.history ?? []
|
|
31159
|
+
};
|
|
31160
|
+
}) : undefined;
|
|
31132
31161
|
return {
|
|
31133
31162
|
courseId: integration.courseId,
|
|
31134
31163
|
title: enrollment?.course.title || `${integration.subject} Grade ${integration.grade}`,
|
|
@@ -31144,7 +31173,8 @@ class TimebackAdminService {
|
|
|
31144
31173
|
pctCompleteApp: TimebackAdminService.computeCompletionPct(summary?.masteredUnits ?? 0, masterableUnits),
|
|
31145
31174
|
completionStatus: completionStatusByCourse.get(integration.courseId) ?? "none",
|
|
31146
31175
|
history: summary?.history ?? [],
|
|
31147
|
-
...inactive ? { inactive } : {}
|
|
31176
|
+
...inactive ? { inactive } : {},
|
|
31177
|
+
...enrollmentSummaries ? { enrollments: enrollmentSummaries } : {}
|
|
31148
31178
|
};
|
|
31149
31179
|
});
|
|
31150
31180
|
return {
|
|
@@ -31386,17 +31416,34 @@ class TimebackAdminService {
|
|
|
31386
31416
|
});
|
|
31387
31417
|
return { students: [] };
|
|
31388
31418
|
}
|
|
31389
|
-
const
|
|
31419
|
+
const fullRoster = await client.oneroster.enrollments.listByCourse(courseId, {
|
|
31390
31420
|
role: "student",
|
|
31421
|
+
includeInactive: true,
|
|
31391
31422
|
includeUsers: false
|
|
31392
31423
|
});
|
|
31393
|
-
const enrolledStudentIds = new Set(
|
|
31394
|
-
const
|
|
31395
|
-
|
|
31396
|
-
|
|
31397
|
-
|
|
31398
|
-
|
|
31399
|
-
|
|
31424
|
+
const enrolledStudentIds = new Set(fullRoster.filter((entry) => entry.enrollment.status === "active").map((entry) => entry.enrollment.user.sourcedId));
|
|
31425
|
+
const pastEnrollmentsByStudent = new Map;
|
|
31426
|
+
const inactiveEntries = fullRoster.filter((entry) => entry.enrollment.status === "tobedeleted").toSorted((a, b) => compareEnrollmentsByRecency(a.enrollment, b.enrollment));
|
|
31427
|
+
for (const entry of inactiveEntries) {
|
|
31428
|
+
const studentId = entry.enrollment.user.sourcedId;
|
|
31429
|
+
const list = pastEnrollmentsByStudent.get(studentId) ?? [];
|
|
31430
|
+
list.push({
|
|
31431
|
+
enrollmentId: entry.enrollment.sourcedId,
|
|
31432
|
+
beginDate: entry.enrollment.beginDate ?? null,
|
|
31433
|
+
endDate: entry.enrollment.endDate ?? null
|
|
31434
|
+
});
|
|
31435
|
+
pastEnrollmentsByStudent.set(studentId, list);
|
|
31436
|
+
}
|
|
31437
|
+
const students = allUsers.filter((entry) => Boolean(entry.sourcedId) && entry.roles?.some((role) => role.role === "student") === true).map((entry) => {
|
|
31438
|
+
const past = pastEnrollmentsByStudent.get(entry.sourcedId) ?? [];
|
|
31439
|
+
return {
|
|
31440
|
+
studentId: entry.sourcedId,
|
|
31441
|
+
name: `${entry.givenName || ""} ${entry.familyName || ""}`.trim() || "No name specified",
|
|
31442
|
+
email: entry.email || null,
|
|
31443
|
+
alreadyEnrolled: enrolledStudentIds.has(entry.sourcedId),
|
|
31444
|
+
...past.length > 0 ? { pastEnrollments: past } : {}
|
|
31445
|
+
};
|
|
31446
|
+
});
|
|
31400
31447
|
return { students };
|
|
31401
31448
|
}
|
|
31402
31449
|
async enrollStudent(data, user) {
|
|
@@ -31427,6 +31474,48 @@ class TimebackAdminService {
|
|
|
31427
31474
|
client.invalidateEnrollments(data.studentId);
|
|
31428
31475
|
return { status: "ok" };
|
|
31429
31476
|
}
|
|
31477
|
+
async reactivateEnrollment(data, user) {
|
|
31478
|
+
const client = this.requireClient();
|
|
31479
|
+
await this.deps.validateGameManagementAccess(user, data.gameId);
|
|
31480
|
+
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
31481
|
+
where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId))
|
|
31482
|
+
});
|
|
31483
|
+
if (!integration) {
|
|
31484
|
+
throw new NotFoundError("Timeback integration", `${data.gameId}:${data.courseId}`);
|
|
31485
|
+
}
|
|
31486
|
+
const enrollment = await client.oneroster.enrollments.get(data.enrollmentId);
|
|
31487
|
+
if (!enrollment) {
|
|
31488
|
+
throw new NotFoundError("Enrollment", data.enrollmentId);
|
|
31489
|
+
}
|
|
31490
|
+
if (enrollment.user.sourcedId !== data.studentId) {
|
|
31491
|
+
throw new ValidationError("Enrollment does not belong to the specified student");
|
|
31492
|
+
}
|
|
31493
|
+
if (enrollment.status === "active") {
|
|
31494
|
+
throw new ValidationError("Enrollment is already active");
|
|
31495
|
+
}
|
|
31496
|
+
const { allEnrollments } = await this.getStudentEnrollmentsByCourseId(client, data.studentId, [data.courseId], { includeInactive: true });
|
|
31497
|
+
const courseEnrollmentIds = new Set((allEnrollments.get(data.courseId) ?? []).map((e) => e.id));
|
|
31498
|
+
if (!courseEnrollmentIds.has(data.enrollmentId)) {
|
|
31499
|
+
throw new ValidationError("Enrollment does not belong to the specified course");
|
|
31500
|
+
}
|
|
31501
|
+
const activeEnrollments = await client.edubridge.enrollments.listByUser(data.studentId);
|
|
31502
|
+
if (activeEnrollments.some((e) => e.course.id === data.courseId)) {
|
|
31503
|
+
throw new ValidationError("Student already has an active enrollment for this course. Unenroll from the current enrollment before reactivating a past one.");
|
|
31504
|
+
}
|
|
31505
|
+
await client.oneroster.enrollments.update(data.enrollmentId, {
|
|
31506
|
+
role: enrollment.role,
|
|
31507
|
+
primary: enrollment.primary,
|
|
31508
|
+
beginDate: enrollment.beginDate,
|
|
31509
|
+
endDate: enrollment.endDate,
|
|
31510
|
+
user: enrollment.user,
|
|
31511
|
+
class: enrollment.class,
|
|
31512
|
+
school: enrollment.school,
|
|
31513
|
+
sourcedId: data.enrollmentId,
|
|
31514
|
+
status: "active"
|
|
31515
|
+
});
|
|
31516
|
+
client.invalidateEnrollments(data.studentId);
|
|
31517
|
+
return { status: "ok" };
|
|
31518
|
+
}
|
|
31430
31519
|
async getCompletionStatus(client, courseId, studentId) {
|
|
31431
31520
|
const ids = deriveSourcedIds(courseId);
|
|
31432
31521
|
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
@@ -36517,6 +36606,10 @@ function createOneRosterNamespace(client) {
|
|
|
36517
36606
|
}
|
|
36518
36607
|
},
|
|
36519
36608
|
enrollments: {
|
|
36609
|
+
get: async (sourcedId) => {
|
|
36610
|
+
const response = await client["request"](`${ONEROSTER_ENDPOINTS5.enrollments}/${sourcedId}`, "GET");
|
|
36611
|
+
return response.enrollment;
|
|
36612
|
+
},
|
|
36520
36613
|
listByClass: async (classSourcedId, options) => {
|
|
36521
36614
|
const queryParams = new URLSearchParams;
|
|
36522
36615
|
const filters = [`class.sourcedId='${escapeFilterValue2(classSourcedId)}'`];
|
|
@@ -36594,6 +36687,11 @@ function createOneRosterNamespace(client) {
|
|
|
36594
36687
|
}
|
|
36595
36688
|
},
|
|
36596
36689
|
create: async (data) => client["request"](ONEROSTER_ENDPOINTS5.enrollments, "POST", { enrollment: data }),
|
|
36690
|
+
update: async (sourcedId, data) => {
|
|
36691
|
+
await client["request"](`${ONEROSTER_ENDPOINTS5.enrollments}/${sourcedId}`, "PUT", {
|
|
36692
|
+
enrollment: data
|
|
36693
|
+
});
|
|
36694
|
+
},
|
|
36597
36695
|
delete: async (sourcedId) => {
|
|
36598
36696
|
await client["request"](`${ONEROSTER_ENDPOINTS5.enrollments}/${sourcedId}`, "DELETE");
|
|
36599
36697
|
}
|
|
@@ -95107,7 +95205,7 @@ function isValidAdminAttributionDate(value) {
|
|
|
95107
95205
|
const date4 = new Date(Date.UTC(year3, month - 1, day, 12, 0, 0));
|
|
95108
95206
|
return date4.getUTCFullYear() === year3 && date4.getUTCMonth() + 1 === month && date4.getUTCDate() === day;
|
|
95109
95207
|
}
|
|
95110
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS6, TimebackGradeSchema, TimebackSubjectSchema, UpdateTimebackXpRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, AdvanceCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ToggleCourseCompletionRequestSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, InsertAssessmentTestSchema, CreateAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
95208
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS6, TimebackGradeSchema, TimebackSubjectSchema, UpdateTimebackXpRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, AdvanceCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ToggleCourseCompletionRequestSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, InsertAssessmentTestSchema, CreateAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
95111
95209
|
var init_schemas11 = __esm(() => {
|
|
95112
95210
|
init_drizzle_zod();
|
|
95113
95211
|
init_esm();
|
|
@@ -95294,6 +95392,12 @@ var init_schemas11 = __esm(() => {
|
|
|
95294
95392
|
courseId: exports_external.string().min(1),
|
|
95295
95393
|
studentId: exports_external.string().min(1)
|
|
95296
95394
|
});
|
|
95395
|
+
ReactivateEnrollmentRequestSchema = exports_external.object({
|
|
95396
|
+
gameId: exports_external.string().uuid(),
|
|
95397
|
+
courseId: exports_external.string().min(1),
|
|
95398
|
+
studentId: exports_external.string().min(1),
|
|
95399
|
+
enrollmentId: exports_external.string().min(1)
|
|
95400
|
+
});
|
|
95297
95401
|
InsertAssessmentTestSchema = createInsertSchema(gameTimebackAssessmentTests).omit({
|
|
95298
95402
|
id: true,
|
|
95299
95403
|
createdAt: true
|
|
@@ -97473,7 +97577,7 @@ var init_sprite_controller = __esm(() => {
|
|
|
97473
97577
|
});
|
|
97474
97578
|
|
|
97475
97579
|
// ../api-core/src/controllers/timeback.controller.ts
|
|
97476
|
-
var logger65, getTodayXp, getTotalXp, updateTodayXp, getXpHistory, populateStudent, getUser, getUserById, setupIntegration, getIntegrations, verifyIntegration, getConfig2, deleteIntegrations, endActivity, heartbeat, advanceCourse, getStudentXp, getRoster, getStudentOverview, getStudentActivity, getActivityDetail, grantXp, adjustTime, adjustMastery, toggleCompletion, searchStudents, enrollStudent, unenrollStudent, listAssessments, createAssessment, deleteAssessment, reorderAssessments, reorderQuestions, activateAssessment, deactivateAssessment, listQuestions, createQuestion, updateQuestion, deleteQuestion, getAssessmentBankStatus, destroyAssessmentBank, timeback2;
|
|
97580
|
+
var logger65, getTodayXp, getTotalXp, updateTodayXp, getXpHistory, populateStudent, getUser, getUserById, setupIntegration, getIntegrations, verifyIntegration, getConfig2, deleteIntegrations, endActivity, heartbeat, advanceCourse, getStudentXp, getRoster, getStudentOverview, getStudentActivity, getActivityDetail, grantXp, adjustTime, adjustMastery, toggleCompletion, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, deleteAssessment, reorderAssessments, reorderQuestions, activateAssessment, deactivateAssessment, listQuestions, createQuestion, updateQuestion, deleteQuestion, getAssessmentBankStatus, destroyAssessmentBank, timeback2;
|
|
97477
97581
|
var init_timeback_controller = __esm(() => {
|
|
97478
97582
|
init_esm();
|
|
97479
97583
|
init_schemas_index();
|
|
@@ -97920,6 +98024,17 @@ var init_timeback_controller = __esm(() => {
|
|
|
97920
98024
|
});
|
|
97921
98025
|
return ctx.services.timebackAdmin.unenrollStudent(body2, ctx.user);
|
|
97922
98026
|
});
|
|
98027
|
+
reactivateEnrollment = requireGameManagementAccess(async (ctx) => {
|
|
98028
|
+
const body2 = await parseRequestBody(ctx.request, ReactivateEnrollmentRequestSchema);
|
|
98029
|
+
logger65.debug("Reactivating enrollment", {
|
|
98030
|
+
requesterId: ctx.user.id,
|
|
98031
|
+
gameId: body2.gameId,
|
|
98032
|
+
courseId: body2.courseId,
|
|
98033
|
+
studentId: body2.studentId,
|
|
98034
|
+
enrollmentId: body2.enrollmentId
|
|
98035
|
+
});
|
|
98036
|
+
return ctx.services.timebackAdmin.reactivateEnrollment(body2, ctx.user);
|
|
98037
|
+
});
|
|
97923
98038
|
listAssessments = requireGameManagementAccess(async (ctx) => {
|
|
97924
98039
|
const { gameId, courseId } = ctx.params;
|
|
97925
98040
|
if (!gameId || !courseId) {
|
|
@@ -98069,6 +98184,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
98069
98184
|
searchStudents,
|
|
98070
98185
|
enrollStudent,
|
|
98071
98186
|
unenrollStudent,
|
|
98187
|
+
reactivateEnrollment,
|
|
98072
98188
|
listAssessments,
|
|
98073
98189
|
createAssessment,
|
|
98074
98190
|
deleteAssessment,
|
package/dist/server.js
CHANGED
|
@@ -1329,7 +1329,7 @@ var package_default;
|
|
|
1329
1329
|
var init_package = __esm(() => {
|
|
1330
1330
|
package_default = {
|
|
1331
1331
|
name: "@playcademy/sandbox",
|
|
1332
|
-
version: "0.3.17-beta.
|
|
1332
|
+
version: "0.3.17-beta.34",
|
|
1333
1333
|
description: "Local development server for Playcademy game development",
|
|
1334
1334
|
type: "module",
|
|
1335
1335
|
exports: {
|
|
@@ -30432,6 +30432,13 @@ function resolveAdminEventTime(data) {
|
|
|
30432
30432
|
}
|
|
30433
30433
|
return toAttributionEventTime(data.date);
|
|
30434
30434
|
}
|
|
30435
|
+
function compareEnrollmentsByRecency(a, b) {
|
|
30436
|
+
const dateCompare = (b.beginDate ?? "").localeCompare(a.beginDate ?? "");
|
|
30437
|
+
if (dateCompare !== 0) {
|
|
30438
|
+
return dateCompare;
|
|
30439
|
+
}
|
|
30440
|
+
return (b.dateLastModified ?? "").localeCompare(a.dateLastModified ?? "");
|
|
30441
|
+
}
|
|
30435
30442
|
var init_timeback_admin_util = __esm(() => {
|
|
30436
30443
|
init_errors();
|
|
30437
30444
|
});
|
|
@@ -30946,6 +30953,7 @@ class TimebackAdminService {
|
|
|
30946
30953
|
}
|
|
30947
30954
|
async getStudentEnrollmentsByCourseId(client, studentId, courseIds, options) {
|
|
30948
30955
|
const enrollments = new Map;
|
|
30956
|
+
const allEnrollments = new Map;
|
|
30949
30957
|
const entries = await Promise.all(courseIds.map(async (courseId) => {
|
|
30950
30958
|
const roster = await client.oneroster.enrollments.listByCourse(courseId, {
|
|
30951
30959
|
includeInactive: options?.includeInactive,
|
|
@@ -30957,28 +30965,30 @@ class TimebackAdminService {
|
|
|
30957
30965
|
if (aActive !== bActive) {
|
|
30958
30966
|
return aActive ? -1 : 1;
|
|
30959
30967
|
}
|
|
30960
|
-
return (
|
|
30968
|
+
return compareEnrollmentsByRecency(a.enrollment, b.enrollment);
|
|
30961
30969
|
});
|
|
30962
|
-
return { courseId,
|
|
30970
|
+
return { courseId, matches };
|
|
30963
30971
|
}));
|
|
30964
|
-
for (const { courseId,
|
|
30965
|
-
|
|
30966
|
-
|
|
30967
|
-
|
|
30968
|
-
|
|
30969
|
-
|
|
30970
|
-
|
|
30971
|
-
|
|
30972
|
-
|
|
30973
|
-
|
|
30974
|
-
|
|
30975
|
-
|
|
30976
|
-
|
|
30977
|
-
|
|
30978
|
-
|
|
30972
|
+
for (const { courseId, matches } of entries) {
|
|
30973
|
+
const records = matches.map((match) => ({
|
|
30974
|
+
id: match.enrollment.sourcedId,
|
|
30975
|
+
status: match.enrollment.status ?? "active",
|
|
30976
|
+
role: match.enrollment.role ?? "student",
|
|
30977
|
+
beginDate: match.enrollment.beginDate ?? null,
|
|
30978
|
+
endDate: match.enrollment.endDate ?? null,
|
|
30979
|
+
course: {
|
|
30980
|
+
id: courseId,
|
|
30981
|
+
title: match.class?.title ?? "",
|
|
30982
|
+
subjects: null,
|
|
30983
|
+
grades: null
|
|
30984
|
+
}
|
|
30985
|
+
}));
|
|
30986
|
+
if (records.length > 0) {
|
|
30987
|
+
enrollments.set(courseId, records[0]);
|
|
30979
30988
|
}
|
|
30989
|
+
allEnrollments.set(courseId, records);
|
|
30980
30990
|
}
|
|
30981
|
-
return { enrollments };
|
|
30991
|
+
return { enrollments, allEnrollments };
|
|
30982
30992
|
}
|
|
30983
30993
|
async assertStudentEnrolledInCourse(client, studentId, courseId) {
|
|
30984
30994
|
const enrollments = await client.edubridge.enrollments.listByUser(studentId);
|
|
@@ -31065,7 +31075,7 @@ class TimebackAdminService {
|
|
|
31065
31075
|
const enrollmentId = rosterEntry.enrollment.sourcedId || null;
|
|
31066
31076
|
const summary = enrollmentId ? analyticsByEnrollmentId.get(enrollmentId) : undefined;
|
|
31067
31077
|
const analyticsUnavailable = Boolean(enrollmentId) && summary?.analyticsAvailable !== true;
|
|
31068
|
-
const name3 = rosterEntry.user ? `${rosterEntry.user.givenName} ${rosterEntry.user.familyName}`.trim() :
|
|
31078
|
+
const name3 = rosterEntry.user ? `${rosterEntry.user.givenName} ${rosterEntry.user.familyName}`.trim() || "No name specified" : "No name specified";
|
|
31069
31079
|
const inactive = rosterEntry.enrollment.status === "tobedeleted";
|
|
31070
31080
|
return {
|
|
31071
31081
|
studentId: rosterEntry.enrollment.user.sourcedId,
|
|
@@ -31110,14 +31120,15 @@ class TimebackAdminService {
|
|
|
31110
31120
|
throw new NotFoundError("Timeback integration", gameId);
|
|
31111
31121
|
}
|
|
31112
31122
|
const courseIds = new Set(integrations.map((integration) => integration.courseId));
|
|
31113
|
-
const { enrollments: enrollmentsByCourseId } = await this.getStudentEnrollmentsByCourseId(client, studentId, [...courseIds], {
|
|
31123
|
+
const { enrollments: enrollmentsByCourseId, allEnrollments: allEnrollmentsByCourseId } = await this.getStudentEnrollmentsByCourseId(client, studentId, [...courseIds], {
|
|
31114
31124
|
includeInactive: true
|
|
31115
31125
|
});
|
|
31116
31126
|
if (enrollmentsByCourseId.size === 0) {
|
|
31117
31127
|
throw new NotFoundError("Student enrollment", courseId ? `${studentId}:${courseId}` : `${studentId}:${gameId}`);
|
|
31118
31128
|
}
|
|
31129
|
+
const allEnrollmentIds = [...allEnrollmentsByCourseId.values()].flat().map((enrollment) => enrollment.id);
|
|
31119
31130
|
const studentProfile = await client.oneroster.users.get(studentId);
|
|
31120
|
-
const analyticsByEnrollmentId = await this.loadEnrollmentAnalyticsSummaries(
|
|
31131
|
+
const analyticsByEnrollmentId = await this.loadEnrollmentAnalyticsSummaries(allEnrollmentIds);
|
|
31121
31132
|
const [masterableUnitsByCourse, completionStatusByCourse] = await Promise.all([
|
|
31122
31133
|
this.getMasterableUnitsByCourse(integrations.map((integration) => integration.courseId)),
|
|
31123
31134
|
this.getCompletionStatusByCourse(client, integrations.map((integration) => integration.courseId), studentId)
|
|
@@ -31128,6 +31139,24 @@ class TimebackAdminService {
|
|
|
31128
31139
|
const masterableUnits = masterableUnitsByCourse.get(integration.courseId);
|
|
31129
31140
|
const analyticsUnavailable = Boolean(enrollment?.id) && summary?.analyticsAvailable !== true;
|
|
31130
31141
|
const inactive = enrollment?.status === "tobedeleted";
|
|
31142
|
+
const courseEnrollments = allEnrollmentsByCourseId.get(integration.courseId) ?? [];
|
|
31143
|
+
const enrollmentSummaries = courseEnrollments.length > 1 ? courseEnrollments.map((record) => {
|
|
31144
|
+
const recordSummary = analyticsByEnrollmentId.get(record.id);
|
|
31145
|
+
const recordAnalyticsUnavailable = recordSummary?.analyticsAvailable !== true;
|
|
31146
|
+
return {
|
|
31147
|
+
enrollmentId: record.id,
|
|
31148
|
+
status: record.status === "tobedeleted" ? "tobedeleted" : "active",
|
|
31149
|
+
beginDate: record.beginDate,
|
|
31150
|
+
endDate: record.endDate,
|
|
31151
|
+
analyticsUnavailable: recordAnalyticsUnavailable,
|
|
31152
|
+
totalXp: recordSummary?.totalXp ?? 0,
|
|
31153
|
+
todayXp: recordSummary?.todayXp ?? 0,
|
|
31154
|
+
activeTimeSeconds: recordSummary?.activeTimeSeconds ?? 0,
|
|
31155
|
+
masteredUnits: recordSummary?.masteredUnits ?? 0,
|
|
31156
|
+
pctCompleteApp: TimebackAdminService.computeCompletionPct(recordSummary?.masteredUnits ?? 0, masterableUnits),
|
|
31157
|
+
history: recordSummary?.history ?? []
|
|
31158
|
+
};
|
|
31159
|
+
}) : undefined;
|
|
31131
31160
|
return {
|
|
31132
31161
|
courseId: integration.courseId,
|
|
31133
31162
|
title: enrollment?.course.title || `${integration.subject} Grade ${integration.grade}`,
|
|
@@ -31143,7 +31172,8 @@ class TimebackAdminService {
|
|
|
31143
31172
|
pctCompleteApp: TimebackAdminService.computeCompletionPct(summary?.masteredUnits ?? 0, masterableUnits),
|
|
31144
31173
|
completionStatus: completionStatusByCourse.get(integration.courseId) ?? "none",
|
|
31145
31174
|
history: summary?.history ?? [],
|
|
31146
|
-
...inactive ? { inactive } : {}
|
|
31175
|
+
...inactive ? { inactive } : {},
|
|
31176
|
+
...enrollmentSummaries ? { enrollments: enrollmentSummaries } : {}
|
|
31147
31177
|
};
|
|
31148
31178
|
});
|
|
31149
31179
|
return {
|
|
@@ -31385,17 +31415,34 @@ class TimebackAdminService {
|
|
|
31385
31415
|
});
|
|
31386
31416
|
return { students: [] };
|
|
31387
31417
|
}
|
|
31388
|
-
const
|
|
31418
|
+
const fullRoster = await client.oneroster.enrollments.listByCourse(courseId, {
|
|
31389
31419
|
role: "student",
|
|
31420
|
+
includeInactive: true,
|
|
31390
31421
|
includeUsers: false
|
|
31391
31422
|
});
|
|
31392
|
-
const enrolledStudentIds = new Set(
|
|
31393
|
-
const
|
|
31394
|
-
|
|
31395
|
-
|
|
31396
|
-
|
|
31397
|
-
|
|
31398
|
-
|
|
31423
|
+
const enrolledStudentIds = new Set(fullRoster.filter((entry) => entry.enrollment.status === "active").map((entry) => entry.enrollment.user.sourcedId));
|
|
31424
|
+
const pastEnrollmentsByStudent = new Map;
|
|
31425
|
+
const inactiveEntries = fullRoster.filter((entry) => entry.enrollment.status === "tobedeleted").toSorted((a, b) => compareEnrollmentsByRecency(a.enrollment, b.enrollment));
|
|
31426
|
+
for (const entry of inactiveEntries) {
|
|
31427
|
+
const studentId = entry.enrollment.user.sourcedId;
|
|
31428
|
+
const list = pastEnrollmentsByStudent.get(studentId) ?? [];
|
|
31429
|
+
list.push({
|
|
31430
|
+
enrollmentId: entry.enrollment.sourcedId,
|
|
31431
|
+
beginDate: entry.enrollment.beginDate ?? null,
|
|
31432
|
+
endDate: entry.enrollment.endDate ?? null
|
|
31433
|
+
});
|
|
31434
|
+
pastEnrollmentsByStudent.set(studentId, list);
|
|
31435
|
+
}
|
|
31436
|
+
const students = allUsers.filter((entry) => Boolean(entry.sourcedId) && entry.roles?.some((role) => role.role === "student") === true).map((entry) => {
|
|
31437
|
+
const past = pastEnrollmentsByStudent.get(entry.sourcedId) ?? [];
|
|
31438
|
+
return {
|
|
31439
|
+
studentId: entry.sourcedId,
|
|
31440
|
+
name: `${entry.givenName || ""} ${entry.familyName || ""}`.trim() || "No name specified",
|
|
31441
|
+
email: entry.email || null,
|
|
31442
|
+
alreadyEnrolled: enrolledStudentIds.has(entry.sourcedId),
|
|
31443
|
+
...past.length > 0 ? { pastEnrollments: past } : {}
|
|
31444
|
+
};
|
|
31445
|
+
});
|
|
31399
31446
|
return { students };
|
|
31400
31447
|
}
|
|
31401
31448
|
async enrollStudent(data, user) {
|
|
@@ -31426,6 +31473,48 @@ class TimebackAdminService {
|
|
|
31426
31473
|
client.invalidateEnrollments(data.studentId);
|
|
31427
31474
|
return { status: "ok" };
|
|
31428
31475
|
}
|
|
31476
|
+
async reactivateEnrollment(data, user) {
|
|
31477
|
+
const client = this.requireClient();
|
|
31478
|
+
await this.deps.validateGameManagementAccess(user, data.gameId);
|
|
31479
|
+
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
31480
|
+
where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId))
|
|
31481
|
+
});
|
|
31482
|
+
if (!integration) {
|
|
31483
|
+
throw new NotFoundError("Timeback integration", `${data.gameId}:${data.courseId}`);
|
|
31484
|
+
}
|
|
31485
|
+
const enrollment = await client.oneroster.enrollments.get(data.enrollmentId);
|
|
31486
|
+
if (!enrollment) {
|
|
31487
|
+
throw new NotFoundError("Enrollment", data.enrollmentId);
|
|
31488
|
+
}
|
|
31489
|
+
if (enrollment.user.sourcedId !== data.studentId) {
|
|
31490
|
+
throw new ValidationError("Enrollment does not belong to the specified student");
|
|
31491
|
+
}
|
|
31492
|
+
if (enrollment.status === "active") {
|
|
31493
|
+
throw new ValidationError("Enrollment is already active");
|
|
31494
|
+
}
|
|
31495
|
+
const { allEnrollments } = await this.getStudentEnrollmentsByCourseId(client, data.studentId, [data.courseId], { includeInactive: true });
|
|
31496
|
+
const courseEnrollmentIds = new Set((allEnrollments.get(data.courseId) ?? []).map((e) => e.id));
|
|
31497
|
+
if (!courseEnrollmentIds.has(data.enrollmentId)) {
|
|
31498
|
+
throw new ValidationError("Enrollment does not belong to the specified course");
|
|
31499
|
+
}
|
|
31500
|
+
const activeEnrollments = await client.edubridge.enrollments.listByUser(data.studentId);
|
|
31501
|
+
if (activeEnrollments.some((e) => e.course.id === data.courseId)) {
|
|
31502
|
+
throw new ValidationError("Student already has an active enrollment for this course. Unenroll from the current enrollment before reactivating a past one.");
|
|
31503
|
+
}
|
|
31504
|
+
await client.oneroster.enrollments.update(data.enrollmentId, {
|
|
31505
|
+
role: enrollment.role,
|
|
31506
|
+
primary: enrollment.primary,
|
|
31507
|
+
beginDate: enrollment.beginDate,
|
|
31508
|
+
endDate: enrollment.endDate,
|
|
31509
|
+
user: enrollment.user,
|
|
31510
|
+
class: enrollment.class,
|
|
31511
|
+
school: enrollment.school,
|
|
31512
|
+
sourcedId: data.enrollmentId,
|
|
31513
|
+
status: "active"
|
|
31514
|
+
});
|
|
31515
|
+
client.invalidateEnrollments(data.studentId);
|
|
31516
|
+
return { status: "ok" };
|
|
31517
|
+
}
|
|
31429
31518
|
async getCompletionStatus(client, courseId, studentId) {
|
|
31430
31519
|
const ids = deriveSourcedIds(courseId);
|
|
31431
31520
|
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
@@ -36516,6 +36605,10 @@ function createOneRosterNamespace(client) {
|
|
|
36516
36605
|
}
|
|
36517
36606
|
},
|
|
36518
36607
|
enrollments: {
|
|
36608
|
+
get: async (sourcedId) => {
|
|
36609
|
+
const response = await client["request"](`${ONEROSTER_ENDPOINTS5.enrollments}/${sourcedId}`, "GET");
|
|
36610
|
+
return response.enrollment;
|
|
36611
|
+
},
|
|
36519
36612
|
listByClass: async (classSourcedId, options) => {
|
|
36520
36613
|
const queryParams = new URLSearchParams;
|
|
36521
36614
|
const filters = [`class.sourcedId='${escapeFilterValue2(classSourcedId)}'`];
|
|
@@ -36593,6 +36686,11 @@ function createOneRosterNamespace(client) {
|
|
|
36593
36686
|
}
|
|
36594
36687
|
},
|
|
36595
36688
|
create: async (data) => client["request"](ONEROSTER_ENDPOINTS5.enrollments, "POST", { enrollment: data }),
|
|
36689
|
+
update: async (sourcedId, data) => {
|
|
36690
|
+
await client["request"](`${ONEROSTER_ENDPOINTS5.enrollments}/${sourcedId}`, "PUT", {
|
|
36691
|
+
enrollment: data
|
|
36692
|
+
});
|
|
36693
|
+
},
|
|
36596
36694
|
delete: async (sourcedId) => {
|
|
36597
36695
|
await client["request"](`${ONEROSTER_ENDPOINTS5.enrollments}/${sourcedId}`, "DELETE");
|
|
36598
36696
|
}
|
|
@@ -95106,7 +95204,7 @@ function isValidAdminAttributionDate(value) {
|
|
|
95106
95204
|
const date4 = new Date(Date.UTC(year3, month - 1, day, 12, 0, 0));
|
|
95107
95205
|
return date4.getUTCFullYear() === year3 && date4.getUTCMonth() + 1 === month && date4.getUTCDate() === day;
|
|
95108
95206
|
}
|
|
95109
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS6, TimebackGradeSchema, TimebackSubjectSchema, UpdateTimebackXpRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, AdvanceCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ToggleCourseCompletionRequestSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, InsertAssessmentTestSchema, CreateAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
95207
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS6, TimebackGradeSchema, TimebackSubjectSchema, UpdateTimebackXpRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, AdvanceCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ToggleCourseCompletionRequestSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, InsertAssessmentTestSchema, CreateAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
95110
95208
|
var init_schemas11 = __esm(() => {
|
|
95111
95209
|
init_drizzle_zod();
|
|
95112
95210
|
init_esm();
|
|
@@ -95293,6 +95391,12 @@ var init_schemas11 = __esm(() => {
|
|
|
95293
95391
|
courseId: exports_external.string().min(1),
|
|
95294
95392
|
studentId: exports_external.string().min(1)
|
|
95295
95393
|
});
|
|
95394
|
+
ReactivateEnrollmentRequestSchema = exports_external.object({
|
|
95395
|
+
gameId: exports_external.string().uuid(),
|
|
95396
|
+
courseId: exports_external.string().min(1),
|
|
95397
|
+
studentId: exports_external.string().min(1),
|
|
95398
|
+
enrollmentId: exports_external.string().min(1)
|
|
95399
|
+
});
|
|
95296
95400
|
InsertAssessmentTestSchema = createInsertSchema(gameTimebackAssessmentTests).omit({
|
|
95297
95401
|
id: true,
|
|
95298
95402
|
createdAt: true
|
|
@@ -97472,7 +97576,7 @@ var init_sprite_controller = __esm(() => {
|
|
|
97472
97576
|
});
|
|
97473
97577
|
|
|
97474
97578
|
// ../api-core/src/controllers/timeback.controller.ts
|
|
97475
|
-
var logger65, getTodayXp, getTotalXp, updateTodayXp, getXpHistory, populateStudent, getUser, getUserById, setupIntegration, getIntegrations, verifyIntegration, getConfig2, deleteIntegrations, endActivity, heartbeat, advanceCourse, getStudentXp, getRoster, getStudentOverview, getStudentActivity, getActivityDetail, grantXp, adjustTime, adjustMastery, toggleCompletion, searchStudents, enrollStudent, unenrollStudent, listAssessments, createAssessment, deleteAssessment, reorderAssessments, reorderQuestions, activateAssessment, deactivateAssessment, listQuestions, createQuestion, updateQuestion, deleteQuestion, getAssessmentBankStatus, destroyAssessmentBank, timeback2;
|
|
97579
|
+
var logger65, getTodayXp, getTotalXp, updateTodayXp, getXpHistory, populateStudent, getUser, getUserById, setupIntegration, getIntegrations, verifyIntegration, getConfig2, deleteIntegrations, endActivity, heartbeat, advanceCourse, getStudentXp, getRoster, getStudentOverview, getStudentActivity, getActivityDetail, grantXp, adjustTime, adjustMastery, toggleCompletion, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, deleteAssessment, reorderAssessments, reorderQuestions, activateAssessment, deactivateAssessment, listQuestions, createQuestion, updateQuestion, deleteQuestion, getAssessmentBankStatus, destroyAssessmentBank, timeback2;
|
|
97476
97580
|
var init_timeback_controller = __esm(() => {
|
|
97477
97581
|
init_esm();
|
|
97478
97582
|
init_schemas_index();
|
|
@@ -97919,6 +98023,17 @@ var init_timeback_controller = __esm(() => {
|
|
|
97919
98023
|
});
|
|
97920
98024
|
return ctx.services.timebackAdmin.unenrollStudent(body2, ctx.user);
|
|
97921
98025
|
});
|
|
98026
|
+
reactivateEnrollment = requireGameManagementAccess(async (ctx) => {
|
|
98027
|
+
const body2 = await parseRequestBody(ctx.request, ReactivateEnrollmentRequestSchema);
|
|
98028
|
+
logger65.debug("Reactivating enrollment", {
|
|
98029
|
+
requesterId: ctx.user.id,
|
|
98030
|
+
gameId: body2.gameId,
|
|
98031
|
+
courseId: body2.courseId,
|
|
98032
|
+
studentId: body2.studentId,
|
|
98033
|
+
enrollmentId: body2.enrollmentId
|
|
98034
|
+
});
|
|
98035
|
+
return ctx.services.timebackAdmin.reactivateEnrollment(body2, ctx.user);
|
|
98036
|
+
});
|
|
97922
98037
|
listAssessments = requireGameManagementAccess(async (ctx) => {
|
|
97923
98038
|
const { gameId, courseId } = ctx.params;
|
|
97924
98039
|
if (!gameId || !courseId) {
|
|
@@ -98068,6 +98183,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
98068
98183
|
searchStudents,
|
|
98069
98184
|
enrollStudent,
|
|
98070
98185
|
unenrollStudent,
|
|
98186
|
+
reactivateEnrollment,
|
|
98071
98187
|
listAssessments,
|
|
98072
98188
|
createAssessment,
|
|
98073
98189
|
deleteAssessment,
|