@playcademy/sandbox 0.3.17-beta.34 → 0.3.17-beta.36
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 +96 -152
- package/dist/server.js +96 -152
- 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.36",
|
|
1334
1334
|
description: "Local development server for Playcademy game development",
|
|
1335
1335
|
type: "module",
|
|
1336
1336
|
exports: {
|
|
@@ -30433,6 +30433,24 @@ function resolveAdminEventTime(data) {
|
|
|
30433
30433
|
}
|
|
30434
30434
|
return toAttributionEventTime(data.date);
|
|
30435
30435
|
}
|
|
30436
|
+
function validateMasteryAdjustment(delta, currentMastered, masterableUnits) {
|
|
30437
|
+
if (delta < 0 && currentMastered + delta < 0) {
|
|
30438
|
+
throw new ValidationError(`Adjustment would go below 0. Current: ${currentMastered}, adjustment: ${delta}`);
|
|
30439
|
+
}
|
|
30440
|
+
if (delta < 0 && typeof masterableUnits === "number" && masterableUnits > 0 && currentMastered > masterableUnits && currentMastered + delta > masterableUnits) {
|
|
30441
|
+
const minDelta = masterableUnits - currentMastered;
|
|
30442
|
+
throw new ValidationError(`Adjustment must reduce mastery to at most ${masterableUnits}. Current: ${currentMastered}/${masterableUnits}, minimum adjustment: ${minDelta}`);
|
|
30443
|
+
}
|
|
30444
|
+
if (delta > 0 && typeof masterableUnits === "number" && masterableUnits > 0) {
|
|
30445
|
+
if (currentMastered >= masterableUnits) {
|
|
30446
|
+
throw new ValidationError(`Mastery is already at maximum (${currentMastered}/${masterableUnits}). Only negative adjustments are allowed.`);
|
|
30447
|
+
}
|
|
30448
|
+
if (currentMastered + delta > masterableUnits) {
|
|
30449
|
+
const remaining = masterableUnits - currentMastered;
|
|
30450
|
+
throw new ValidationError(`Adjustment would exceed maximum. Current: ${currentMastered}/${masterableUnits}, max adjustment: +${remaining}`);
|
|
30451
|
+
}
|
|
30452
|
+
}
|
|
30453
|
+
}
|
|
30436
30454
|
function compareEnrollmentsByRecency(a, b) {
|
|
30437
30455
|
const dateCompare = (b.beginDate ?? "").localeCompare(a.beginDate ?? "");
|
|
30438
30456
|
if (dateCompare !== 0) {
|
|
@@ -30792,17 +30810,6 @@ class TimebackAdminService {
|
|
|
30792
30810
|
}
|
|
30793
30811
|
return this.deps.timeback;
|
|
30794
30812
|
}
|
|
30795
|
-
async recordCourseCompletionHistory(client, data) {
|
|
30796
|
-
await client.recordAdminCourseCompletionChange(data).catch((error) => {
|
|
30797
|
-
logger16.error("Failed to record admin course completion history event", {
|
|
30798
|
-
gameId: data.gameId,
|
|
30799
|
-
courseId: data.courseId,
|
|
30800
|
-
studentId: data.studentId,
|
|
30801
|
-
action: data.action,
|
|
30802
|
-
error: error instanceof Error ? error.message : String(error)
|
|
30803
|
-
});
|
|
30804
|
-
});
|
|
30805
|
-
}
|
|
30806
30813
|
async resolveAdminMutationContext(gameId, courseId, user, studentId, accessLevel = "developer") {
|
|
30807
30814
|
const client = this.requireClient();
|
|
30808
30815
|
if (accessLevel === "dashboard") {
|
|
@@ -30901,7 +30908,7 @@ class TimebackAdminService {
|
|
|
30901
30908
|
if (typeof masterableUnits !== "number" || masterableUnits <= 0) {
|
|
30902
30909
|
return;
|
|
30903
30910
|
}
|
|
30904
|
-
return Math.
|
|
30911
|
+
return Math.round(masteredUnits / masterableUnits * 100);
|
|
30905
30912
|
}
|
|
30906
30913
|
async getMasterableUnits(courseId) {
|
|
30907
30914
|
const client = this.requireClient();
|
|
@@ -31177,6 +31184,7 @@ class TimebackAdminService {
|
|
|
31177
31184
|
...enrollmentSummaries ? { enrollments: enrollmentSummaries } : {}
|
|
31178
31185
|
};
|
|
31179
31186
|
});
|
|
31187
|
+
courses.sort((a, b) => a.grade - b.grade);
|
|
31180
31188
|
return {
|
|
31181
31189
|
student: {
|
|
31182
31190
|
studentId,
|
|
@@ -31276,111 +31284,87 @@ class TimebackAdminService {
|
|
|
31276
31284
|
return { status: "ok" };
|
|
31277
31285
|
}
|
|
31278
31286
|
async adjustMasteredUnits(data, user) {
|
|
31279
|
-
const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user
|
|
31287
|
+
const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user);
|
|
31288
|
+
let currentMastered = 0;
|
|
31289
|
+
const masterableUnits = await this.getMasterableUnits(data.courseId);
|
|
31290
|
+
if (data.units !== 0) {
|
|
31291
|
+
const enrollment = await this.assertStudentEnrolledInCourse(client, data.studentId, data.courseId);
|
|
31292
|
+
try {
|
|
31293
|
+
const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
|
|
31294
|
+
currentMastered = this.summarizeAnalyticsFacts(analytics.facts).masteredUnits;
|
|
31295
|
+
} catch {
|
|
31296
|
+
throw new ValidationError("Unable to validate mastery bounds — analytics unavailable. Please retry.");
|
|
31297
|
+
}
|
|
31298
|
+
validateMasteryAdjustment(data.units, currentMastered, masterableUnits);
|
|
31299
|
+
}
|
|
31300
|
+
const pctCompleteApp = typeof masterableUnits === "number" && masterableUnits > 0 ? Math.min(100, Math.max(0, Math.round((currentMastered + data.units) / masterableUnits * 100))) : undefined;
|
|
31280
31301
|
await client.recordAdminMasteryAdjustment({
|
|
31281
31302
|
gameId: data.gameId,
|
|
31282
31303
|
courseId: data.courseId,
|
|
31283
31304
|
studentId: data.studentId,
|
|
31284
31305
|
masteredUnits: data.units,
|
|
31306
|
+
pctCompleteApp,
|
|
31285
31307
|
eventTime: resolveAdminEventTime(data),
|
|
31286
31308
|
reason: data.reason,
|
|
31287
31309
|
actor,
|
|
31288
31310
|
appName,
|
|
31289
31311
|
sensorUrl
|
|
31290
31312
|
});
|
|
31291
|
-
|
|
31292
|
-
|
|
31293
|
-
|
|
31294
|
-
|
|
31295
|
-
|
|
31296
|
-
|
|
31297
|
-
|
|
31298
|
-
|
|
31299
|
-
|
|
31300
|
-
|
|
31301
|
-
|
|
31302
|
-
|
|
31303
|
-
|
|
31304
|
-
|
|
31305
|
-
});
|
|
31306
|
-
if (data.action === "complete") {
|
|
31307
|
-
const masterableUnits = await this.getMasterableUnits(data.courseId);
|
|
31308
|
-
if (masterableUnits && masterableUnits > 0) {
|
|
31309
|
-
const enrollment = await this.assertStudentEnrolledInCourse(client, data.studentId, data.courseId);
|
|
31310
|
-
let currentMastered = 0;
|
|
31311
|
-
try {
|
|
31312
|
-
const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
|
|
31313
|
-
const summary = this.summarizeAnalyticsFacts(analytics.facts);
|
|
31314
|
-
currentMastered = summary.masteredUnits;
|
|
31315
|
-
} catch {
|
|
31316
|
-
logger16.warn("Failed to load analytics for mastery gap calculation", {
|
|
31317
|
-
studentId: data.studentId,
|
|
31318
|
-
courseId: data.courseId
|
|
31313
|
+
if (typeof masterableUnits === "number" && masterableUnits > 0) {
|
|
31314
|
+
const wasMastered = currentMastered >= masterableUnits;
|
|
31315
|
+
const willBeMastered = currentMastered + data.units >= masterableUnits;
|
|
31316
|
+
if (wasMastered !== willBeMastered) {
|
|
31317
|
+
const ids = deriveSourcedIds(data.courseId);
|
|
31318
|
+
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
31319
|
+
const resultId = `${lineItemId}:${data.studentId}:completion`;
|
|
31320
|
+
if (willBeMastered) {
|
|
31321
|
+
await client.oneroster.assessmentLineItems.findOrCreate(lineItemId, {
|
|
31322
|
+
sourcedId: lineItemId,
|
|
31323
|
+
title: "Mastery Completion",
|
|
31324
|
+
status: ONEROSTER_STATUS.active,
|
|
31325
|
+
course: { sourcedId: ids.course },
|
|
31326
|
+
...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
|
|
31319
31327
|
});
|
|
31320
|
-
|
|
31321
|
-
|
|
31322
|
-
|
|
31323
|
-
|
|
31324
|
-
|
|
31325
|
-
|
|
31326
|
-
|
|
31327
|
-
|
|
31328
|
-
|
|
31329
|
-
|
|
31330
|
-
|
|
31331
|
-
|
|
31328
|
+
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
31329
|
+
sourcedId: resultId,
|
|
31330
|
+
status: ONEROSTER_STATUS.active,
|
|
31331
|
+
assessmentLineItem: { sourcedId: lineItemId },
|
|
31332
|
+
student: { sourcedId: data.studentId },
|
|
31333
|
+
score: 100,
|
|
31334
|
+
scoreDate: new Date().toISOString(),
|
|
31335
|
+
scoreStatus: SCORE_STATUS.fullyGraded,
|
|
31336
|
+
inProgress: "false",
|
|
31337
|
+
metadata: {
|
|
31338
|
+
isMasteryCompletion: true,
|
|
31339
|
+
adminAction: true,
|
|
31340
|
+
appName
|
|
31341
|
+
}
|
|
31332
31342
|
});
|
|
31343
|
+
} else {
|
|
31344
|
+
try {
|
|
31345
|
+
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
31346
|
+
sourcedId: resultId,
|
|
31347
|
+
status: ONEROSTER_STATUS.active,
|
|
31348
|
+
assessmentLineItem: { sourcedId: lineItemId },
|
|
31349
|
+
student: { sourcedId: data.studentId },
|
|
31350
|
+
score: 0,
|
|
31351
|
+
scoreDate: new Date().toISOString(),
|
|
31352
|
+
scoreStatus: SCORE_STATUS.notSubmitted,
|
|
31353
|
+
inProgress: "true",
|
|
31354
|
+
metadata: {
|
|
31355
|
+
isMasteryCompletion: true,
|
|
31356
|
+
adminAction: true,
|
|
31357
|
+
appName
|
|
31358
|
+
}
|
|
31359
|
+
});
|
|
31360
|
+
} catch {
|
|
31361
|
+
logger16.debug("No completion entry to revoke", {
|
|
31362
|
+
studentId: data.studentId,
|
|
31363
|
+
courseId: data.courseId
|
|
31364
|
+
});
|
|
31365
|
+
}
|
|
31333
31366
|
}
|
|
31334
31367
|
}
|
|
31335
|
-
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
31336
|
-
sourcedId: resultId,
|
|
31337
|
-
status: ONEROSTER_STATUS.active,
|
|
31338
|
-
assessmentLineItem: { sourcedId: lineItemId },
|
|
31339
|
-
student: { sourcedId: data.studentId },
|
|
31340
|
-
score: 100,
|
|
31341
|
-
scoreDate: new Date().toISOString(),
|
|
31342
|
-
scoreStatus: SCORE_STATUS.fullyGraded,
|
|
31343
|
-
inProgress: "false",
|
|
31344
|
-
metadata: {
|
|
31345
|
-
isMasteryCompletion: true,
|
|
31346
|
-
adminAction: true,
|
|
31347
|
-
appName
|
|
31348
|
-
}
|
|
31349
|
-
});
|
|
31350
|
-
await this.recordCourseCompletionHistory(historyClient, {
|
|
31351
|
-
gameId: data.gameId,
|
|
31352
|
-
courseId: data.courseId,
|
|
31353
|
-
studentId: data.studentId,
|
|
31354
|
-
action: "complete",
|
|
31355
|
-
actor,
|
|
31356
|
-
appName,
|
|
31357
|
-
sensorUrl
|
|
31358
|
-
});
|
|
31359
|
-
} else {
|
|
31360
|
-
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
31361
|
-
sourcedId: resultId,
|
|
31362
|
-
status: ONEROSTER_STATUS.active,
|
|
31363
|
-
assessmentLineItem: { sourcedId: lineItemId },
|
|
31364
|
-
student: { sourcedId: data.studentId },
|
|
31365
|
-
score: 0,
|
|
31366
|
-
scoreDate: new Date().toISOString(),
|
|
31367
|
-
scoreStatus: SCORE_STATUS.notSubmitted,
|
|
31368
|
-
inProgress: "true",
|
|
31369
|
-
metadata: {
|
|
31370
|
-
isMasteryCompletion: true,
|
|
31371
|
-
adminAction: true,
|
|
31372
|
-
appName
|
|
31373
|
-
}
|
|
31374
|
-
});
|
|
31375
|
-
await this.recordCourseCompletionHistory(historyClient, {
|
|
31376
|
-
gameId: data.gameId,
|
|
31377
|
-
courseId: data.courseId,
|
|
31378
|
-
studentId: data.studentId,
|
|
31379
|
-
action: "resume",
|
|
31380
|
-
actor,
|
|
31381
|
-
appName,
|
|
31382
|
-
sensorUrl
|
|
31383
|
-
});
|
|
31384
31368
|
}
|
|
31385
31369
|
return { status: "ok" };
|
|
31386
31370
|
}
|
|
@@ -37041,6 +37025,7 @@ class AdminEventRecorder {
|
|
|
37041
37025
|
defaultActivityId: "playcademy-admin-mastery-adjustment",
|
|
37042
37026
|
eventKind: "remediation-mastery"
|
|
37043
37027
|
});
|
|
37028
|
+
const courseUrl = createOneRosterUrls(TIMEBACK_API_URLS5[this.environment]).course(data.courseId);
|
|
37044
37029
|
await this.caliper.emitActivityEvent({
|
|
37045
37030
|
studentId: ctx.student.id,
|
|
37046
37031
|
studentEmail: ctx.student.email,
|
|
@@ -37055,32 +37040,13 @@ class AdminEventRecorder {
|
|
|
37055
37040
|
appName: ctx.appName,
|
|
37056
37041
|
sensorUrl: ctx.sensorUrl,
|
|
37057
37042
|
process: true,
|
|
37058
|
-
generatedExtensions: ctx.metadata,
|
|
37059
|
-
eventExtensions: ctx.metadata
|
|
37060
|
-
});
|
|
37061
|
-
}
|
|
37062
|
-
async recordCourseCompletionChange(data) {
|
|
37063
|
-
const isResume = data.action === "resume";
|
|
37064
|
-
const ctx = await this.prepareAdminEvent({
|
|
37065
|
-
...data,
|
|
37066
|
-
defaultActivityId: isResume ? "playcademy-admin-course-resumed" : "playcademy-admin-course-completed",
|
|
37067
|
-
reason: "Admin action",
|
|
37068
|
-
eventKind: isResume ? "course-resumed" : "course-completed"
|
|
37069
|
-
});
|
|
37070
|
-
await this.caliper.emitActivityEvent({
|
|
37071
|
-
studentId: ctx.student.id,
|
|
37072
|
-
studentEmail: ctx.student.email,
|
|
37073
|
-
gameId: data.gameId,
|
|
37074
|
-
activityId: ctx.activityId,
|
|
37075
|
-
activityName: isResume ? "Course resumed" : "Course marked complete",
|
|
37076
|
-
courseId: data.courseId,
|
|
37077
|
-
courseName: ctx.courseContext.courseName,
|
|
37078
|
-
subject: ctx.courseContext.subject,
|
|
37079
|
-
appName: ctx.appName,
|
|
37080
|
-
sensorUrl: ctx.sensorUrl,
|
|
37081
|
-
process: false,
|
|
37082
37043
|
includeAttempt: false,
|
|
37083
|
-
|
|
37044
|
+
objectId: `${ctx.sensorUrl.replace(/\/$/, "")}/urn:uuid:${crypto.randomUUID()}`,
|
|
37045
|
+
generatedId: courseUrl,
|
|
37046
|
+
generatedExtensions: {
|
|
37047
|
+
...ctx.metadata,
|
|
37048
|
+
...data.pctCompleteApp !== undefined ? { pctCompleteApp: data.pctCompleteApp } : {}
|
|
37049
|
+
},
|
|
37084
37050
|
eventExtensions: ctx.metadata
|
|
37085
37051
|
});
|
|
37086
37052
|
}
|
|
@@ -38271,10 +38237,6 @@ class TimebackClient {
|
|
|
38271
38237
|
await this._ensureAuthenticated();
|
|
38272
38238
|
return this.adminEventRecorder.recordMasteryAdjustment(data);
|
|
38273
38239
|
}
|
|
38274
|
-
async recordAdminCourseCompletionChange(data) {
|
|
38275
|
-
await this._ensureAuthenticated();
|
|
38276
|
-
return this.adminEventRecorder.recordCourseCompletionChange(data);
|
|
38277
|
-
}
|
|
38278
38240
|
clearCaches() {
|
|
38279
38241
|
this.cacheManager.clearAll();
|
|
38280
38242
|
}
|
|
@@ -95205,7 +95167,7 @@ function isValidAdminAttributionDate(value) {
|
|
|
95205
95167
|
const date4 = new Date(Date.UTC(year3, month - 1, day, 12, 0, 0));
|
|
95206
95168
|
return date4.getUTCFullYear() === year3 && date4.getUTCMonth() + 1 === month && date4.getUTCDate() === day;
|
|
95207
95169
|
}
|
|
95208
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS6, TimebackGradeSchema, TimebackSubjectSchema, UpdateTimebackXpRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, AdvanceCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema,
|
|
95170
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS6, TimebackGradeSchema, TimebackSubjectSchema, UpdateTimebackXpRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, AdvanceCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, InsertAssessmentTestSchema, CreateAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
95209
95171
|
var init_schemas11 = __esm(() => {
|
|
95210
95172
|
init_drizzle_zod();
|
|
95211
95173
|
init_esm();
|
|
@@ -95376,12 +95338,6 @@ var init_schemas11 = __esm(() => {
|
|
|
95376
95338
|
date: AdminAttributionDateSchema.optional(),
|
|
95377
95339
|
useCurrentTime: exports_external.boolean().optional()
|
|
95378
95340
|
});
|
|
95379
|
-
ToggleCourseCompletionRequestSchema = exports_external.object({
|
|
95380
|
-
gameId: exports_external.string().uuid(),
|
|
95381
|
-
courseId: exports_external.string().min(1),
|
|
95382
|
-
studentId: exports_external.string().min(1),
|
|
95383
|
-
action: exports_external.enum(["complete", "resume"])
|
|
95384
|
-
});
|
|
95385
95341
|
EnrollStudentRequestSchema = exports_external.object({
|
|
95386
95342
|
gameId: exports_external.string().uuid(),
|
|
95387
95343
|
courseId: exports_external.string().min(1),
|
|
@@ -97577,7 +97533,7 @@ var init_sprite_controller = __esm(() => {
|
|
|
97577
97533
|
});
|
|
97578
97534
|
|
|
97579
97535
|
// ../api-core/src/controllers/timeback.controller.ts
|
|
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,
|
|
97536
|
+
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, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, deleteAssessment, reorderAssessments, reorderQuestions, activateAssessment, deactivateAssessment, listQuestions, createQuestion, updateQuestion, deleteQuestion, getAssessmentBankStatus, destroyAssessmentBank, timeback2;
|
|
97581
97537
|
var init_timeback_controller = __esm(() => {
|
|
97582
97538
|
init_esm();
|
|
97583
97539
|
init_schemas_index();
|
|
@@ -97978,17 +97934,6 @@ var init_timeback_controller = __esm(() => {
|
|
|
97978
97934
|
});
|
|
97979
97935
|
return ctx.services.timebackAdmin.adjustMasteredUnits(body2, ctx.user);
|
|
97980
97936
|
});
|
|
97981
|
-
toggleCompletion = requireGameManagementAccess(async (ctx) => {
|
|
97982
|
-
const body2 = await parseRequestBody(ctx.request, ToggleCourseCompletionRequestSchema);
|
|
97983
|
-
logger65.debug("Toggling course completion", {
|
|
97984
|
-
requesterId: ctx.user.id,
|
|
97985
|
-
gameId: body2.gameId,
|
|
97986
|
-
courseId: body2.courseId,
|
|
97987
|
-
studentId: body2.studentId,
|
|
97988
|
-
action: body2.action
|
|
97989
|
-
});
|
|
97990
|
-
return ctx.services.timebackAdmin.toggleCourseCompletion(body2, ctx.user);
|
|
97991
|
-
});
|
|
97992
97937
|
searchStudents = requireGameManagementAccess(async (ctx) => {
|
|
97993
97938
|
const gameId = ctx.params.gameId;
|
|
97994
97939
|
const courseId = ctx.params.courseId;
|
|
@@ -98180,7 +98125,6 @@ var init_timeback_controller = __esm(() => {
|
|
|
98180
98125
|
grantXp,
|
|
98181
98126
|
adjustTime,
|
|
98182
98127
|
adjustMastery,
|
|
98183
|
-
toggleCompletion,
|
|
98184
98128
|
searchStudents,
|
|
98185
98129
|
enrollStudent,
|
|
98186
98130
|
unenrollStudent,
|
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.36",
|
|
1333
1333
|
description: "Local development server for Playcademy game development",
|
|
1334
1334
|
type: "module",
|
|
1335
1335
|
exports: {
|
|
@@ -30432,6 +30432,24 @@ function resolveAdminEventTime(data) {
|
|
|
30432
30432
|
}
|
|
30433
30433
|
return toAttributionEventTime(data.date);
|
|
30434
30434
|
}
|
|
30435
|
+
function validateMasteryAdjustment(delta, currentMastered, masterableUnits) {
|
|
30436
|
+
if (delta < 0 && currentMastered + delta < 0) {
|
|
30437
|
+
throw new ValidationError(`Adjustment would go below 0. Current: ${currentMastered}, adjustment: ${delta}`);
|
|
30438
|
+
}
|
|
30439
|
+
if (delta < 0 && typeof masterableUnits === "number" && masterableUnits > 0 && currentMastered > masterableUnits && currentMastered + delta > masterableUnits) {
|
|
30440
|
+
const minDelta = masterableUnits - currentMastered;
|
|
30441
|
+
throw new ValidationError(`Adjustment must reduce mastery to at most ${masterableUnits}. Current: ${currentMastered}/${masterableUnits}, minimum adjustment: ${minDelta}`);
|
|
30442
|
+
}
|
|
30443
|
+
if (delta > 0 && typeof masterableUnits === "number" && masterableUnits > 0) {
|
|
30444
|
+
if (currentMastered >= masterableUnits) {
|
|
30445
|
+
throw new ValidationError(`Mastery is already at maximum (${currentMastered}/${masterableUnits}). Only negative adjustments are allowed.`);
|
|
30446
|
+
}
|
|
30447
|
+
if (currentMastered + delta > masterableUnits) {
|
|
30448
|
+
const remaining = masterableUnits - currentMastered;
|
|
30449
|
+
throw new ValidationError(`Adjustment would exceed maximum. Current: ${currentMastered}/${masterableUnits}, max adjustment: +${remaining}`);
|
|
30450
|
+
}
|
|
30451
|
+
}
|
|
30452
|
+
}
|
|
30435
30453
|
function compareEnrollmentsByRecency(a, b) {
|
|
30436
30454
|
const dateCompare = (b.beginDate ?? "").localeCompare(a.beginDate ?? "");
|
|
30437
30455
|
if (dateCompare !== 0) {
|
|
@@ -30791,17 +30809,6 @@ class TimebackAdminService {
|
|
|
30791
30809
|
}
|
|
30792
30810
|
return this.deps.timeback;
|
|
30793
30811
|
}
|
|
30794
|
-
async recordCourseCompletionHistory(client, data) {
|
|
30795
|
-
await client.recordAdminCourseCompletionChange(data).catch((error) => {
|
|
30796
|
-
logger16.error("Failed to record admin course completion history event", {
|
|
30797
|
-
gameId: data.gameId,
|
|
30798
|
-
courseId: data.courseId,
|
|
30799
|
-
studentId: data.studentId,
|
|
30800
|
-
action: data.action,
|
|
30801
|
-
error: error instanceof Error ? error.message : String(error)
|
|
30802
|
-
});
|
|
30803
|
-
});
|
|
30804
|
-
}
|
|
30805
30812
|
async resolveAdminMutationContext(gameId, courseId, user, studentId, accessLevel = "developer") {
|
|
30806
30813
|
const client = this.requireClient();
|
|
30807
30814
|
if (accessLevel === "dashboard") {
|
|
@@ -30900,7 +30907,7 @@ class TimebackAdminService {
|
|
|
30900
30907
|
if (typeof masterableUnits !== "number" || masterableUnits <= 0) {
|
|
30901
30908
|
return;
|
|
30902
30909
|
}
|
|
30903
|
-
return Math.
|
|
30910
|
+
return Math.round(masteredUnits / masterableUnits * 100);
|
|
30904
30911
|
}
|
|
30905
30912
|
async getMasterableUnits(courseId) {
|
|
30906
30913
|
const client = this.requireClient();
|
|
@@ -31176,6 +31183,7 @@ class TimebackAdminService {
|
|
|
31176
31183
|
...enrollmentSummaries ? { enrollments: enrollmentSummaries } : {}
|
|
31177
31184
|
};
|
|
31178
31185
|
});
|
|
31186
|
+
courses.sort((a, b) => a.grade - b.grade);
|
|
31179
31187
|
return {
|
|
31180
31188
|
student: {
|
|
31181
31189
|
studentId,
|
|
@@ -31275,111 +31283,87 @@ class TimebackAdminService {
|
|
|
31275
31283
|
return { status: "ok" };
|
|
31276
31284
|
}
|
|
31277
31285
|
async adjustMasteredUnits(data, user) {
|
|
31278
|
-
const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user
|
|
31286
|
+
const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user);
|
|
31287
|
+
let currentMastered = 0;
|
|
31288
|
+
const masterableUnits = await this.getMasterableUnits(data.courseId);
|
|
31289
|
+
if (data.units !== 0) {
|
|
31290
|
+
const enrollment = await this.assertStudentEnrolledInCourse(client, data.studentId, data.courseId);
|
|
31291
|
+
try {
|
|
31292
|
+
const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
|
|
31293
|
+
currentMastered = this.summarizeAnalyticsFacts(analytics.facts).masteredUnits;
|
|
31294
|
+
} catch {
|
|
31295
|
+
throw new ValidationError("Unable to validate mastery bounds — analytics unavailable. Please retry.");
|
|
31296
|
+
}
|
|
31297
|
+
validateMasteryAdjustment(data.units, currentMastered, masterableUnits);
|
|
31298
|
+
}
|
|
31299
|
+
const pctCompleteApp = typeof masterableUnits === "number" && masterableUnits > 0 ? Math.min(100, Math.max(0, Math.round((currentMastered + data.units) / masterableUnits * 100))) : undefined;
|
|
31279
31300
|
await client.recordAdminMasteryAdjustment({
|
|
31280
31301
|
gameId: data.gameId,
|
|
31281
31302
|
courseId: data.courseId,
|
|
31282
31303
|
studentId: data.studentId,
|
|
31283
31304
|
masteredUnits: data.units,
|
|
31305
|
+
pctCompleteApp,
|
|
31284
31306
|
eventTime: resolveAdminEventTime(data),
|
|
31285
31307
|
reason: data.reason,
|
|
31286
31308
|
actor,
|
|
31287
31309
|
appName,
|
|
31288
31310
|
sensorUrl
|
|
31289
31311
|
});
|
|
31290
|
-
|
|
31291
|
-
|
|
31292
|
-
|
|
31293
|
-
|
|
31294
|
-
|
|
31295
|
-
|
|
31296
|
-
|
|
31297
|
-
|
|
31298
|
-
|
|
31299
|
-
|
|
31300
|
-
|
|
31301
|
-
|
|
31302
|
-
|
|
31303
|
-
|
|
31304
|
-
});
|
|
31305
|
-
if (data.action === "complete") {
|
|
31306
|
-
const masterableUnits = await this.getMasterableUnits(data.courseId);
|
|
31307
|
-
if (masterableUnits && masterableUnits > 0) {
|
|
31308
|
-
const enrollment = await this.assertStudentEnrolledInCourse(client, data.studentId, data.courseId);
|
|
31309
|
-
let currentMastered = 0;
|
|
31310
|
-
try {
|
|
31311
|
-
const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
|
|
31312
|
-
const summary = this.summarizeAnalyticsFacts(analytics.facts);
|
|
31313
|
-
currentMastered = summary.masteredUnits;
|
|
31314
|
-
} catch {
|
|
31315
|
-
logger16.warn("Failed to load analytics for mastery gap calculation", {
|
|
31316
|
-
studentId: data.studentId,
|
|
31317
|
-
courseId: data.courseId
|
|
31312
|
+
if (typeof masterableUnits === "number" && masterableUnits > 0) {
|
|
31313
|
+
const wasMastered = currentMastered >= masterableUnits;
|
|
31314
|
+
const willBeMastered = currentMastered + data.units >= masterableUnits;
|
|
31315
|
+
if (wasMastered !== willBeMastered) {
|
|
31316
|
+
const ids = deriveSourcedIds(data.courseId);
|
|
31317
|
+
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
31318
|
+
const resultId = `${lineItemId}:${data.studentId}:completion`;
|
|
31319
|
+
if (willBeMastered) {
|
|
31320
|
+
await client.oneroster.assessmentLineItems.findOrCreate(lineItemId, {
|
|
31321
|
+
sourcedId: lineItemId,
|
|
31322
|
+
title: "Mastery Completion",
|
|
31323
|
+
status: ONEROSTER_STATUS.active,
|
|
31324
|
+
course: { sourcedId: ids.course },
|
|
31325
|
+
...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
|
|
31318
31326
|
});
|
|
31319
|
-
|
|
31320
|
-
|
|
31321
|
-
|
|
31322
|
-
|
|
31323
|
-
|
|
31324
|
-
|
|
31325
|
-
|
|
31326
|
-
|
|
31327
|
-
|
|
31328
|
-
|
|
31329
|
-
|
|
31330
|
-
|
|
31327
|
+
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
31328
|
+
sourcedId: resultId,
|
|
31329
|
+
status: ONEROSTER_STATUS.active,
|
|
31330
|
+
assessmentLineItem: { sourcedId: lineItemId },
|
|
31331
|
+
student: { sourcedId: data.studentId },
|
|
31332
|
+
score: 100,
|
|
31333
|
+
scoreDate: new Date().toISOString(),
|
|
31334
|
+
scoreStatus: SCORE_STATUS.fullyGraded,
|
|
31335
|
+
inProgress: "false",
|
|
31336
|
+
metadata: {
|
|
31337
|
+
isMasteryCompletion: true,
|
|
31338
|
+
adminAction: true,
|
|
31339
|
+
appName
|
|
31340
|
+
}
|
|
31331
31341
|
});
|
|
31342
|
+
} else {
|
|
31343
|
+
try {
|
|
31344
|
+
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
31345
|
+
sourcedId: resultId,
|
|
31346
|
+
status: ONEROSTER_STATUS.active,
|
|
31347
|
+
assessmentLineItem: { sourcedId: lineItemId },
|
|
31348
|
+
student: { sourcedId: data.studentId },
|
|
31349
|
+
score: 0,
|
|
31350
|
+
scoreDate: new Date().toISOString(),
|
|
31351
|
+
scoreStatus: SCORE_STATUS.notSubmitted,
|
|
31352
|
+
inProgress: "true",
|
|
31353
|
+
metadata: {
|
|
31354
|
+
isMasteryCompletion: true,
|
|
31355
|
+
adminAction: true,
|
|
31356
|
+
appName
|
|
31357
|
+
}
|
|
31358
|
+
});
|
|
31359
|
+
} catch {
|
|
31360
|
+
logger16.debug("No completion entry to revoke", {
|
|
31361
|
+
studentId: data.studentId,
|
|
31362
|
+
courseId: data.courseId
|
|
31363
|
+
});
|
|
31364
|
+
}
|
|
31332
31365
|
}
|
|
31333
31366
|
}
|
|
31334
|
-
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
31335
|
-
sourcedId: resultId,
|
|
31336
|
-
status: ONEROSTER_STATUS.active,
|
|
31337
|
-
assessmentLineItem: { sourcedId: lineItemId },
|
|
31338
|
-
student: { sourcedId: data.studentId },
|
|
31339
|
-
score: 100,
|
|
31340
|
-
scoreDate: new Date().toISOString(),
|
|
31341
|
-
scoreStatus: SCORE_STATUS.fullyGraded,
|
|
31342
|
-
inProgress: "false",
|
|
31343
|
-
metadata: {
|
|
31344
|
-
isMasteryCompletion: true,
|
|
31345
|
-
adminAction: true,
|
|
31346
|
-
appName
|
|
31347
|
-
}
|
|
31348
|
-
});
|
|
31349
|
-
await this.recordCourseCompletionHistory(historyClient, {
|
|
31350
|
-
gameId: data.gameId,
|
|
31351
|
-
courseId: data.courseId,
|
|
31352
|
-
studentId: data.studentId,
|
|
31353
|
-
action: "complete",
|
|
31354
|
-
actor,
|
|
31355
|
-
appName,
|
|
31356
|
-
sensorUrl
|
|
31357
|
-
});
|
|
31358
|
-
} else {
|
|
31359
|
-
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
31360
|
-
sourcedId: resultId,
|
|
31361
|
-
status: ONEROSTER_STATUS.active,
|
|
31362
|
-
assessmentLineItem: { sourcedId: lineItemId },
|
|
31363
|
-
student: { sourcedId: data.studentId },
|
|
31364
|
-
score: 0,
|
|
31365
|
-
scoreDate: new Date().toISOString(),
|
|
31366
|
-
scoreStatus: SCORE_STATUS.notSubmitted,
|
|
31367
|
-
inProgress: "true",
|
|
31368
|
-
metadata: {
|
|
31369
|
-
isMasteryCompletion: true,
|
|
31370
|
-
adminAction: true,
|
|
31371
|
-
appName
|
|
31372
|
-
}
|
|
31373
|
-
});
|
|
31374
|
-
await this.recordCourseCompletionHistory(historyClient, {
|
|
31375
|
-
gameId: data.gameId,
|
|
31376
|
-
courseId: data.courseId,
|
|
31377
|
-
studentId: data.studentId,
|
|
31378
|
-
action: "resume",
|
|
31379
|
-
actor,
|
|
31380
|
-
appName,
|
|
31381
|
-
sensorUrl
|
|
31382
|
-
});
|
|
31383
31367
|
}
|
|
31384
31368
|
return { status: "ok" };
|
|
31385
31369
|
}
|
|
@@ -37040,6 +37024,7 @@ class AdminEventRecorder {
|
|
|
37040
37024
|
defaultActivityId: "playcademy-admin-mastery-adjustment",
|
|
37041
37025
|
eventKind: "remediation-mastery"
|
|
37042
37026
|
});
|
|
37027
|
+
const courseUrl = createOneRosterUrls(TIMEBACK_API_URLS5[this.environment]).course(data.courseId);
|
|
37043
37028
|
await this.caliper.emitActivityEvent({
|
|
37044
37029
|
studentId: ctx.student.id,
|
|
37045
37030
|
studentEmail: ctx.student.email,
|
|
@@ -37054,32 +37039,13 @@ class AdminEventRecorder {
|
|
|
37054
37039
|
appName: ctx.appName,
|
|
37055
37040
|
sensorUrl: ctx.sensorUrl,
|
|
37056
37041
|
process: true,
|
|
37057
|
-
generatedExtensions: ctx.metadata,
|
|
37058
|
-
eventExtensions: ctx.metadata
|
|
37059
|
-
});
|
|
37060
|
-
}
|
|
37061
|
-
async recordCourseCompletionChange(data) {
|
|
37062
|
-
const isResume = data.action === "resume";
|
|
37063
|
-
const ctx = await this.prepareAdminEvent({
|
|
37064
|
-
...data,
|
|
37065
|
-
defaultActivityId: isResume ? "playcademy-admin-course-resumed" : "playcademy-admin-course-completed",
|
|
37066
|
-
reason: "Admin action",
|
|
37067
|
-
eventKind: isResume ? "course-resumed" : "course-completed"
|
|
37068
|
-
});
|
|
37069
|
-
await this.caliper.emitActivityEvent({
|
|
37070
|
-
studentId: ctx.student.id,
|
|
37071
|
-
studentEmail: ctx.student.email,
|
|
37072
|
-
gameId: data.gameId,
|
|
37073
|
-
activityId: ctx.activityId,
|
|
37074
|
-
activityName: isResume ? "Course resumed" : "Course marked complete",
|
|
37075
|
-
courseId: data.courseId,
|
|
37076
|
-
courseName: ctx.courseContext.courseName,
|
|
37077
|
-
subject: ctx.courseContext.subject,
|
|
37078
|
-
appName: ctx.appName,
|
|
37079
|
-
sensorUrl: ctx.sensorUrl,
|
|
37080
|
-
process: false,
|
|
37081
37042
|
includeAttempt: false,
|
|
37082
|
-
|
|
37043
|
+
objectId: `${ctx.sensorUrl.replace(/\/$/, "")}/urn:uuid:${crypto.randomUUID()}`,
|
|
37044
|
+
generatedId: courseUrl,
|
|
37045
|
+
generatedExtensions: {
|
|
37046
|
+
...ctx.metadata,
|
|
37047
|
+
...data.pctCompleteApp !== undefined ? { pctCompleteApp: data.pctCompleteApp } : {}
|
|
37048
|
+
},
|
|
37083
37049
|
eventExtensions: ctx.metadata
|
|
37084
37050
|
});
|
|
37085
37051
|
}
|
|
@@ -38270,10 +38236,6 @@ class TimebackClient {
|
|
|
38270
38236
|
await this._ensureAuthenticated();
|
|
38271
38237
|
return this.adminEventRecorder.recordMasteryAdjustment(data);
|
|
38272
38238
|
}
|
|
38273
|
-
async recordAdminCourseCompletionChange(data) {
|
|
38274
|
-
await this._ensureAuthenticated();
|
|
38275
|
-
return this.adminEventRecorder.recordCourseCompletionChange(data);
|
|
38276
|
-
}
|
|
38277
38239
|
clearCaches() {
|
|
38278
38240
|
this.cacheManager.clearAll();
|
|
38279
38241
|
}
|
|
@@ -95204,7 +95166,7 @@ function isValidAdminAttributionDate(value) {
|
|
|
95204
95166
|
const date4 = new Date(Date.UTC(year3, month - 1, day, 12, 0, 0));
|
|
95205
95167
|
return date4.getUTCFullYear() === year3 && date4.getUTCMonth() + 1 === month && date4.getUTCDate() === day;
|
|
95206
95168
|
}
|
|
95207
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS6, TimebackGradeSchema, TimebackSubjectSchema, UpdateTimebackXpRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, AdvanceCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema,
|
|
95169
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS6, TimebackGradeSchema, TimebackSubjectSchema, UpdateTimebackXpRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, AdvanceCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, InsertAssessmentTestSchema, CreateAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
95208
95170
|
var init_schemas11 = __esm(() => {
|
|
95209
95171
|
init_drizzle_zod();
|
|
95210
95172
|
init_esm();
|
|
@@ -95375,12 +95337,6 @@ var init_schemas11 = __esm(() => {
|
|
|
95375
95337
|
date: AdminAttributionDateSchema.optional(),
|
|
95376
95338
|
useCurrentTime: exports_external.boolean().optional()
|
|
95377
95339
|
});
|
|
95378
|
-
ToggleCourseCompletionRequestSchema = exports_external.object({
|
|
95379
|
-
gameId: exports_external.string().uuid(),
|
|
95380
|
-
courseId: exports_external.string().min(1),
|
|
95381
|
-
studentId: exports_external.string().min(1),
|
|
95382
|
-
action: exports_external.enum(["complete", "resume"])
|
|
95383
|
-
});
|
|
95384
95340
|
EnrollStudentRequestSchema = exports_external.object({
|
|
95385
95341
|
gameId: exports_external.string().uuid(),
|
|
95386
95342
|
courseId: exports_external.string().min(1),
|
|
@@ -97576,7 +97532,7 @@ var init_sprite_controller = __esm(() => {
|
|
|
97576
97532
|
});
|
|
97577
97533
|
|
|
97578
97534
|
// ../api-core/src/controllers/timeback.controller.ts
|
|
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,
|
|
97535
|
+
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, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, deleteAssessment, reorderAssessments, reorderQuestions, activateAssessment, deactivateAssessment, listQuestions, createQuestion, updateQuestion, deleteQuestion, getAssessmentBankStatus, destroyAssessmentBank, timeback2;
|
|
97580
97536
|
var init_timeback_controller = __esm(() => {
|
|
97581
97537
|
init_esm();
|
|
97582
97538
|
init_schemas_index();
|
|
@@ -97977,17 +97933,6 @@ var init_timeback_controller = __esm(() => {
|
|
|
97977
97933
|
});
|
|
97978
97934
|
return ctx.services.timebackAdmin.adjustMasteredUnits(body2, ctx.user);
|
|
97979
97935
|
});
|
|
97980
|
-
toggleCompletion = requireGameManagementAccess(async (ctx) => {
|
|
97981
|
-
const body2 = await parseRequestBody(ctx.request, ToggleCourseCompletionRequestSchema);
|
|
97982
|
-
logger65.debug("Toggling course completion", {
|
|
97983
|
-
requesterId: ctx.user.id,
|
|
97984
|
-
gameId: body2.gameId,
|
|
97985
|
-
courseId: body2.courseId,
|
|
97986
|
-
studentId: body2.studentId,
|
|
97987
|
-
action: body2.action
|
|
97988
|
-
});
|
|
97989
|
-
return ctx.services.timebackAdmin.toggleCourseCompletion(body2, ctx.user);
|
|
97990
|
-
});
|
|
97991
97936
|
searchStudents = requireGameManagementAccess(async (ctx) => {
|
|
97992
97937
|
const gameId = ctx.params.gameId;
|
|
97993
97938
|
const courseId = ctx.params.courseId;
|
|
@@ -98179,7 +98124,6 @@ var init_timeback_controller = __esm(() => {
|
|
|
98179
98124
|
grantXp,
|
|
98180
98125
|
adjustTime,
|
|
98181
98126
|
adjustMastery,
|
|
98182
|
-
toggleCompletion,
|
|
98183
98127
|
searchStudents,
|
|
98184
98128
|
enrollStudent,
|
|
98185
98129
|
unenrollStudent,
|