@playcademy/sandbox 0.4.1 → 0.4.2-beta.1
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 +138 -10
- package/dist/constants.js +2 -1
- package/dist/server.js +138 -10
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -250,7 +250,8 @@ var init_timeback2 = __esm(() => {
|
|
|
250
250
|
GET_XP: "/integrations/timeback/xp",
|
|
251
251
|
GET_MASTERY: "/integrations/timeback/mastery",
|
|
252
252
|
HEARTBEAT: "/integrations/timeback/heartbeat",
|
|
253
|
-
ADVANCE_COURSE: "/integrations/timeback/advance-course"
|
|
253
|
+
ADVANCE_COURSE: "/integrations/timeback/advance-course",
|
|
254
|
+
UNENROLL_COURSE: "/integrations/timeback/unenroll-course"
|
|
254
255
|
};
|
|
255
256
|
TIMEBACK_COURSE_DEFAULTS = {
|
|
256
257
|
gradingScheme: "STANDARD",
|
|
@@ -1076,7 +1077,7 @@ var package_default;
|
|
|
1076
1077
|
var init_package = __esm(() => {
|
|
1077
1078
|
package_default = {
|
|
1078
1079
|
name: "@playcademy/sandbox",
|
|
1079
|
-
version: "0.4.1",
|
|
1080
|
+
version: "0.4.2-beta.1",
|
|
1080
1081
|
description: "Local development server for Playcademy game development",
|
|
1081
1082
|
type: "module",
|
|
1082
1083
|
exports: {
|
|
@@ -28248,7 +28249,8 @@ var init_constants3 = __esm(() => {
|
|
|
28248
28249
|
GET_XP: `/api${TIMEBACK_ROUTES.GET_XP}`,
|
|
28249
28250
|
GET_MASTERY: `/api${TIMEBACK_ROUTES.GET_MASTERY}`,
|
|
28250
28251
|
HEARTBEAT: `/api${TIMEBACK_ROUTES.HEARTBEAT}`,
|
|
28251
|
-
ADVANCE_COURSE: `/api${TIMEBACK_ROUTES.ADVANCE_COURSE}
|
|
28252
|
+
ADVANCE_COURSE: `/api${TIMEBACK_ROUTES.ADVANCE_COURSE}`,
|
|
28253
|
+
UNENROLL_COURSE: `/api${TIMEBACK_ROUTES.UNENROLL_COURSE}`
|
|
28252
28254
|
}
|
|
28253
28255
|
};
|
|
28254
28256
|
});
|
|
@@ -29902,7 +29904,7 @@ function isValidAdminAttributionDate(value) {
|
|
|
29902
29904
|
const date3 = new Date(Date.UTC(year, month - 1, day, 12, 0, 0));
|
|
29903
29905
|
return date3.getUTCFullYear() === year && date3.getUTCMonth() + 1 === month && date3.getUTCDate() === day;
|
|
29904
29906
|
}
|
|
29905
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS4, TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, GameRunMetricsSchema, GameCourseMetricsSchema, GameMetricsResponseSchema, AdvanceCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, ADMIN_GRANT_XP_MIN = -1e5, ADMIN_GRANT_XP_MAX = 1e5, ADMIN_GRANT_XP_AMOUNT_RANGE_MESSAGE, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ReconcileMasteryForConfigChangeSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, InsertAssessmentTestSchema, CreateAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
29907
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS4, TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, GameRunMetricsSchema, GameCourseMetricsSchema, GameMetricsResponseSchema, AdvanceCourseRequestSchema, UnenrollCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, ADMIN_GRANT_XP_MIN = -1e5, ADMIN_GRANT_XP_MAX = 1e5, ADMIN_GRANT_XP_AMOUNT_RANGE_MESSAGE, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ReconcileMasteryForConfigChangeSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, InsertAssessmentTestSchema, CreateAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
29906
29908
|
var init_schemas4 = __esm(() => {
|
|
29907
29909
|
init_drizzle_zod();
|
|
29908
29910
|
init_esm();
|
|
@@ -30004,6 +30006,12 @@ var init_schemas4 = __esm(() => {
|
|
|
30004
30006
|
studentId: exports_external.string().min(1),
|
|
30005
30007
|
subject: TimebackSubjectSchema.optional()
|
|
30006
30008
|
});
|
|
30009
|
+
UnenrollCourseRequestSchema = exports_external.object({
|
|
30010
|
+
gameId: exports_external.string().uuid(),
|
|
30011
|
+
studentId: exports_external.string().min(1),
|
|
30012
|
+
subject: TimebackSubjectSchema.optional(),
|
|
30013
|
+
force: exports_external.boolean().optional()
|
|
30014
|
+
});
|
|
30007
30015
|
HeartbeatRequestSchema = exports_external.object({
|
|
30008
30016
|
gameId: exports_external.string().uuid(),
|
|
30009
30017
|
studentId: exports_external.string().min(1),
|
|
@@ -33208,15 +33216,14 @@ var init_timeback_service = __esm(() => {
|
|
|
33208
33216
|
inProgress: result.inProgress
|
|
33209
33217
|
};
|
|
33210
33218
|
}
|
|
33211
|
-
async
|
|
33219
|
+
async resolveActiveGameCourse({
|
|
33212
33220
|
gameId,
|
|
33213
33221
|
studentId,
|
|
33214
33222
|
subject,
|
|
33215
|
-
|
|
33223
|
+
action
|
|
33216
33224
|
}) {
|
|
33217
33225
|
const client = this.requireClient();
|
|
33218
33226
|
const db2 = this.deps.db;
|
|
33219
|
-
await this.deps.validateDeveloperAccess(user, gameId);
|
|
33220
33227
|
const integrations = await db2.query.gameTimebackIntegrations.findMany({
|
|
33221
33228
|
where: subject ? and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.subject, subject)) : eq(gameTimebackIntegrations.gameId, gameId)
|
|
33222
33229
|
});
|
|
@@ -33231,12 +33238,30 @@ var init_timeback_service = __esm(() => {
|
|
|
33231
33238
|
}
|
|
33232
33239
|
const subjectsInPlay = new Set(enrolledIntegrations.map((i2) => i2.subject));
|
|
33233
33240
|
if (subjectsInPlay.size > 1) {
|
|
33234
|
-
throw new ValidationError(`Ambiguous Timeback
|
|
33241
|
+
throw new ValidationError(`Ambiguous Timeback ${action}: student is enrolled in ${subjectsInPlay.size} parallel ladders (${[...subjectsInPlay].join(", ")}); pass { subject } to disambiguate`);
|
|
33235
33242
|
}
|
|
33236
33243
|
const currentIntegration = enrolledIntegrations.toSorted((left, right) => left.grade - right.grade)[0];
|
|
33244
|
+
const currentEnrollment = enrollments.find((enrollment) => enrollment.course.id === currentIntegration.courseId);
|
|
33245
|
+
return { currentIntegration, currentEnrollment, enrollments };
|
|
33246
|
+
}
|
|
33247
|
+
async advanceCourse({
|
|
33248
|
+
gameId,
|
|
33249
|
+
studentId,
|
|
33250
|
+
subject,
|
|
33251
|
+
user
|
|
33252
|
+
}) {
|
|
33253
|
+
const client = this.requireClient();
|
|
33254
|
+
const db2 = this.deps.db;
|
|
33255
|
+
await this.deps.validateDeveloperAccess(user, gameId);
|
|
33256
|
+
const { currentIntegration, enrollments } = await this.resolveActiveGameCourse({
|
|
33257
|
+
gameId,
|
|
33258
|
+
studentId,
|
|
33259
|
+
subject,
|
|
33260
|
+
action: "advance"
|
|
33261
|
+
});
|
|
33237
33262
|
const masteryStatus = await client.getMasteryStatus(currentIntegration.courseId, studentId);
|
|
33238
33263
|
if (!masteryStatus) {
|
|
33239
|
-
throw new ValidationError(`Cannot advance course: mastery status is unavailable for course ${currentIntegration.courseId}. Ensure the course has mastery configuration and the student has enrollment analytics before calling client.timeback.
|
|
33264
|
+
throw new ValidationError(`Cannot advance course: mastery status is unavailable for course ${currentIntegration.courseId}. Ensure the course has mastery configuration and the student has enrollment analytics before calling client.timeback.course.advance().`);
|
|
33240
33265
|
}
|
|
33241
33266
|
if (!masteryStatus.isComplete) {
|
|
33242
33267
|
const promotion2 = {
|
|
@@ -33274,6 +33299,93 @@ var init_timeback_service = __esm(() => {
|
|
|
33274
33299
|
});
|
|
33275
33300
|
return { status: "ok", promotion };
|
|
33276
33301
|
}
|
|
33302
|
+
async unenrollCourse({
|
|
33303
|
+
gameId,
|
|
33304
|
+
studentId,
|
|
33305
|
+
subject,
|
|
33306
|
+
force,
|
|
33307
|
+
user
|
|
33308
|
+
}) {
|
|
33309
|
+
const client = this.requireClient();
|
|
33310
|
+
await this.deps.validateDeveloperAccess(user, gameId);
|
|
33311
|
+
const { currentIntegration, currentEnrollment } = await this.resolveActiveGameCourse({
|
|
33312
|
+
gameId,
|
|
33313
|
+
studentId,
|
|
33314
|
+
subject,
|
|
33315
|
+
action: "unenroll"
|
|
33316
|
+
});
|
|
33317
|
+
const schoolId = currentEnrollment.school.id;
|
|
33318
|
+
const masteryStatus = await client.getMasteryStatus(currentIntegration.courseId, studentId);
|
|
33319
|
+
if (!masteryStatus && !force) {
|
|
33320
|
+
throw new ValidationError(`Cannot unenroll course: mastery status is unavailable for course ${currentIntegration.courseId}. Pass { force: true } to unenroll without mastery data.`);
|
|
33321
|
+
}
|
|
33322
|
+
if (masteryStatus && !masteryStatus.isComplete && !force) {
|
|
33323
|
+
const unenrollment = {
|
|
33324
|
+
status: "not-mastered",
|
|
33325
|
+
currentCourseId: currentIntegration.courseId,
|
|
33326
|
+
masteredUnits: masteryStatus.masteredUnits,
|
|
33327
|
+
masterableUnits: masteryStatus.masterableUnits
|
|
33328
|
+
};
|
|
33329
|
+
logger20.debug("Skipping game-initiated course unenroll because mastery is incomplete", {
|
|
33330
|
+
event: "timeback.course.unenroll",
|
|
33331
|
+
outcome: "not-mastered",
|
|
33332
|
+
gameId,
|
|
33333
|
+
studentId,
|
|
33334
|
+
courseId: currentIntegration.courseId,
|
|
33335
|
+
subject: currentIntegration.subject,
|
|
33336
|
+
grade: currentIntegration.grade,
|
|
33337
|
+
masteredUnits: masteryStatus.masteredUnits,
|
|
33338
|
+
masterableUnits: masteryStatus.masterableUnits,
|
|
33339
|
+
forced: false,
|
|
33340
|
+
requesterUserId: user.id
|
|
33341
|
+
});
|
|
33342
|
+
return { status: "ok", unenrollment };
|
|
33343
|
+
}
|
|
33344
|
+
const forcedEarlyExit = Boolean(force && (!masteryStatus || !masteryStatus.isComplete));
|
|
33345
|
+
if (forcedEarlyExit) {
|
|
33346
|
+
logger20.warn("Force-unenrolled student before mastery completion", {
|
|
33347
|
+
event: "timeback.course.unenroll",
|
|
33348
|
+
outcome: "force-unenrolled",
|
|
33349
|
+
gameId,
|
|
33350
|
+
studentId,
|
|
33351
|
+
courseId: currentIntegration.courseId,
|
|
33352
|
+
subject: currentIntegration.subject,
|
|
33353
|
+
grade: currentIntegration.grade,
|
|
33354
|
+
masteredUnits: masteryStatus?.masteredUnits,
|
|
33355
|
+
masterableUnits: masteryStatus?.masterableUnits,
|
|
33356
|
+
masteryStatusAvailable: Boolean(masteryStatus),
|
|
33357
|
+
forced: true,
|
|
33358
|
+
requesterUserId: user.id
|
|
33359
|
+
});
|
|
33360
|
+
}
|
|
33361
|
+
await client.edubridge.enrollments.unenroll(studentId, currentIntegration.courseId, schoolId ? { schoolId } : {});
|
|
33362
|
+
client.invalidateEnrollments(studentId);
|
|
33363
|
+
logger20.info("Game-initiated course unenroll completed", {
|
|
33364
|
+
event: "timeback.course.unenroll",
|
|
33365
|
+
outcome: "unenrolled",
|
|
33366
|
+
gameId,
|
|
33367
|
+
studentId,
|
|
33368
|
+
courseId: currentIntegration.courseId,
|
|
33369
|
+
subject: currentIntegration.subject,
|
|
33370
|
+
grade: currentIntegration.grade,
|
|
33371
|
+
masteredUnits: masteryStatus?.masteredUnits,
|
|
33372
|
+
masterableUnits: masteryStatus?.masterableUnits,
|
|
33373
|
+
forced: forcedEarlyExit,
|
|
33374
|
+
requesterUserId: user.id
|
|
33375
|
+
});
|
|
33376
|
+
return {
|
|
33377
|
+
status: "ok",
|
|
33378
|
+
unenrollment: {
|
|
33379
|
+
status: "unenrolled",
|
|
33380
|
+
currentCourseId: currentIntegration.courseId,
|
|
33381
|
+
...masteryStatus ? {
|
|
33382
|
+
masteredUnits: masteryStatus.masteredUnits,
|
|
33383
|
+
masterableUnits: masteryStatus.masterableUnits
|
|
33384
|
+
} : {},
|
|
33385
|
+
...forcedEarlyExit ? { forced: true } : {}
|
|
33386
|
+
}
|
|
33387
|
+
};
|
|
33388
|
+
}
|
|
33277
33389
|
async recordHeartbeat({
|
|
33278
33390
|
gameId,
|
|
33279
33391
|
studentId,
|
|
@@ -94085,7 +94197,7 @@ var init_session_controller = __esm(() => {
|
|
|
94085
94197
|
});
|
|
94086
94198
|
|
|
94087
94199
|
// ../api-core/src/controllers/timeback.controller.ts
|
|
94088
|
-
var logger45, populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, updateIntegration, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, getStudentXp, getStudentMastery, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getActivityDetail, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, deleteAssessment, reorderAssessments, reorderQuestions, activateAssessment, deactivateAssessment, listQuestions, createQuestion, updateQuestion, deleteQuestion, getAssessmentBankStatus, destroyAssessmentBank, timeback2;
|
|
94200
|
+
var logger45, populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, updateIntegration, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, getStudentXp, getStudentMastery, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getActivityDetail, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, deleteAssessment, reorderAssessments, reorderQuestions, activateAssessment, deactivateAssessment, listQuestions, createQuestion, updateQuestion, deleteQuestion, getAssessmentBankStatus, destroyAssessmentBank, timeback2;
|
|
94089
94201
|
var init_timeback_controller = __esm(() => {
|
|
94090
94202
|
init_esm();
|
|
94091
94203
|
init_schemas_index();
|
|
@@ -94323,6 +94435,20 @@ var init_timeback_controller = __esm(() => {
|
|
|
94323
94435
|
user: ctx.user
|
|
94324
94436
|
});
|
|
94325
94437
|
});
|
|
94438
|
+
unenrollCourse = requireDeveloper(async (ctx) => {
|
|
94439
|
+
const body2 = await parseRequestBody(ctx.request, UnenrollCourseRequestSchema);
|
|
94440
|
+
logger45.debug("Unenrolling student from current course", {
|
|
94441
|
+
userId: ctx.user.id,
|
|
94442
|
+
gameId: body2.gameId,
|
|
94443
|
+
studentId: body2.studentId,
|
|
94444
|
+
subject: body2.subject,
|
|
94445
|
+
force: body2.force
|
|
94446
|
+
});
|
|
94447
|
+
return ctx.services.timeback.unenrollCourse({
|
|
94448
|
+
...body2,
|
|
94449
|
+
user: ctx.user
|
|
94450
|
+
});
|
|
94451
|
+
});
|
|
94326
94452
|
getStudentXp = requireDeveloper(async (ctx) => {
|
|
94327
94453
|
const timebackId = ctx.params.timebackId;
|
|
94328
94454
|
if (!timebackId) {
|
|
@@ -94750,6 +94876,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
94750
94876
|
endActivity,
|
|
94751
94877
|
heartbeat,
|
|
94752
94878
|
advanceCourse,
|
|
94879
|
+
unenrollCourse,
|
|
94753
94880
|
getStudentXp,
|
|
94754
94881
|
getStudentMastery,
|
|
94755
94882
|
getRoster,
|
|
@@ -95654,6 +95781,7 @@ var init_timeback6 = __esm(() => {
|
|
|
95654
95781
|
timebackRouter.post("/end-activity", handle2(timeback2.endActivity));
|
|
95655
95782
|
timebackRouter.post("/heartbeat", handle2(timeback2.heartbeat));
|
|
95656
95783
|
timebackRouter.post("/advance-course", handle2(timeback2.advanceCourse));
|
|
95784
|
+
timebackRouter.post("/unenroll-course", handle2(timeback2.unenrollCourse));
|
|
95657
95785
|
timebackRouter.get("/user", async (c2) => {
|
|
95658
95786
|
const user = c2.get("user");
|
|
95659
95787
|
const gameId = c2.get("gameId");
|
package/dist/constants.js
CHANGED
|
@@ -85,7 +85,8 @@ var init_timeback = __esm(() => {
|
|
|
85
85
|
GET_XP: "/integrations/timeback/xp",
|
|
86
86
|
GET_MASTERY: "/integrations/timeback/mastery",
|
|
87
87
|
HEARTBEAT: "/integrations/timeback/heartbeat",
|
|
88
|
-
ADVANCE_COURSE: "/integrations/timeback/advance-course"
|
|
88
|
+
ADVANCE_COURSE: "/integrations/timeback/advance-course",
|
|
89
|
+
UNENROLL_COURSE: "/integrations/timeback/unenroll-course"
|
|
89
90
|
};
|
|
90
91
|
TIMEBACK_COURSE_DEFAULTS = {
|
|
91
92
|
gradingScheme: "STANDARD",
|
package/dist/server.js
CHANGED
|
@@ -249,7 +249,8 @@ var init_timeback2 = __esm(() => {
|
|
|
249
249
|
GET_XP: "/integrations/timeback/xp",
|
|
250
250
|
GET_MASTERY: "/integrations/timeback/mastery",
|
|
251
251
|
HEARTBEAT: "/integrations/timeback/heartbeat",
|
|
252
|
-
ADVANCE_COURSE: "/integrations/timeback/advance-course"
|
|
252
|
+
ADVANCE_COURSE: "/integrations/timeback/advance-course",
|
|
253
|
+
UNENROLL_COURSE: "/integrations/timeback/unenroll-course"
|
|
253
254
|
};
|
|
254
255
|
TIMEBACK_COURSE_DEFAULTS = {
|
|
255
256
|
gradingScheme: "STANDARD",
|
|
@@ -1075,7 +1076,7 @@ var package_default;
|
|
|
1075
1076
|
var init_package = __esm(() => {
|
|
1076
1077
|
package_default = {
|
|
1077
1078
|
name: "@playcademy/sandbox",
|
|
1078
|
-
version: "0.4.1",
|
|
1079
|
+
version: "0.4.2-beta.1",
|
|
1079
1080
|
description: "Local development server for Playcademy game development",
|
|
1080
1081
|
type: "module",
|
|
1081
1082
|
exports: {
|
|
@@ -28247,7 +28248,8 @@ var init_constants3 = __esm(() => {
|
|
|
28247
28248
|
GET_XP: `/api${TIMEBACK_ROUTES.GET_XP}`,
|
|
28248
28249
|
GET_MASTERY: `/api${TIMEBACK_ROUTES.GET_MASTERY}`,
|
|
28249
28250
|
HEARTBEAT: `/api${TIMEBACK_ROUTES.HEARTBEAT}`,
|
|
28250
|
-
ADVANCE_COURSE: `/api${TIMEBACK_ROUTES.ADVANCE_COURSE}
|
|
28251
|
+
ADVANCE_COURSE: `/api${TIMEBACK_ROUTES.ADVANCE_COURSE}`,
|
|
28252
|
+
UNENROLL_COURSE: `/api${TIMEBACK_ROUTES.UNENROLL_COURSE}`
|
|
28251
28253
|
}
|
|
28252
28254
|
};
|
|
28253
28255
|
});
|
|
@@ -29901,7 +29903,7 @@ function isValidAdminAttributionDate(value) {
|
|
|
29901
29903
|
const date3 = new Date(Date.UTC(year, month - 1, day, 12, 0, 0));
|
|
29902
29904
|
return date3.getUTCFullYear() === year && date3.getUTCMonth() + 1 === month && date3.getUTCDate() === day;
|
|
29903
29905
|
}
|
|
29904
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS4, TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, GameRunMetricsSchema, GameCourseMetricsSchema, GameMetricsResponseSchema, AdvanceCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, ADMIN_GRANT_XP_MIN = -1e5, ADMIN_GRANT_XP_MAX = 1e5, ADMIN_GRANT_XP_AMOUNT_RANGE_MESSAGE, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ReconcileMasteryForConfigChangeSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, InsertAssessmentTestSchema, CreateAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
29906
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS4, TimebackGradeSchema, TimebackSubjectSchema, CourseGoalsSchema, UpdateGameTimebackIntegrationRequestSchema, TimebackActivityDataSchema, EndActivityRequestSchema, GameRunMetricsSchema, GameCourseMetricsSchema, GameMetricsResponseSchema, AdvanceCourseRequestSchema, UnenrollCourseRequestSchema, HeartbeatRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, ADMIN_GRANT_XP_MIN = -1e5, ADMIN_GRANT_XP_MAX = 1e5, ADMIN_GRANT_XP_AMOUNT_RANGE_MESSAGE, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ReconcileMasteryForConfigChangeSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema, ReactivateEnrollmentRequestSchema, InsertAssessmentTestSchema, CreateAssessmentRequestSchema, ReorderAssessmentsRequestSchema, ReorderQuestionsRequestSchema;
|
|
29905
29907
|
var init_schemas4 = __esm(() => {
|
|
29906
29908
|
init_drizzle_zod();
|
|
29907
29909
|
init_esm();
|
|
@@ -30003,6 +30005,12 @@ var init_schemas4 = __esm(() => {
|
|
|
30003
30005
|
studentId: exports_external.string().min(1),
|
|
30004
30006
|
subject: TimebackSubjectSchema.optional()
|
|
30005
30007
|
});
|
|
30008
|
+
UnenrollCourseRequestSchema = exports_external.object({
|
|
30009
|
+
gameId: exports_external.string().uuid(),
|
|
30010
|
+
studentId: exports_external.string().min(1),
|
|
30011
|
+
subject: TimebackSubjectSchema.optional(),
|
|
30012
|
+
force: exports_external.boolean().optional()
|
|
30013
|
+
});
|
|
30006
30014
|
HeartbeatRequestSchema = exports_external.object({
|
|
30007
30015
|
gameId: exports_external.string().uuid(),
|
|
30008
30016
|
studentId: exports_external.string().min(1),
|
|
@@ -33207,15 +33215,14 @@ var init_timeback_service = __esm(() => {
|
|
|
33207
33215
|
inProgress: result.inProgress
|
|
33208
33216
|
};
|
|
33209
33217
|
}
|
|
33210
|
-
async
|
|
33218
|
+
async resolveActiveGameCourse({
|
|
33211
33219
|
gameId,
|
|
33212
33220
|
studentId,
|
|
33213
33221
|
subject,
|
|
33214
|
-
|
|
33222
|
+
action
|
|
33215
33223
|
}) {
|
|
33216
33224
|
const client = this.requireClient();
|
|
33217
33225
|
const db2 = this.deps.db;
|
|
33218
|
-
await this.deps.validateDeveloperAccess(user, gameId);
|
|
33219
33226
|
const integrations = await db2.query.gameTimebackIntegrations.findMany({
|
|
33220
33227
|
where: subject ? and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.subject, subject)) : eq(gameTimebackIntegrations.gameId, gameId)
|
|
33221
33228
|
});
|
|
@@ -33230,12 +33237,30 @@ var init_timeback_service = __esm(() => {
|
|
|
33230
33237
|
}
|
|
33231
33238
|
const subjectsInPlay = new Set(enrolledIntegrations.map((i2) => i2.subject));
|
|
33232
33239
|
if (subjectsInPlay.size > 1) {
|
|
33233
|
-
throw new ValidationError(`Ambiguous Timeback
|
|
33240
|
+
throw new ValidationError(`Ambiguous Timeback ${action}: student is enrolled in ${subjectsInPlay.size} parallel ladders (${[...subjectsInPlay].join(", ")}); pass { subject } to disambiguate`);
|
|
33234
33241
|
}
|
|
33235
33242
|
const currentIntegration = enrolledIntegrations.toSorted((left, right) => left.grade - right.grade)[0];
|
|
33243
|
+
const currentEnrollment = enrollments.find((enrollment) => enrollment.course.id === currentIntegration.courseId);
|
|
33244
|
+
return { currentIntegration, currentEnrollment, enrollments };
|
|
33245
|
+
}
|
|
33246
|
+
async advanceCourse({
|
|
33247
|
+
gameId,
|
|
33248
|
+
studentId,
|
|
33249
|
+
subject,
|
|
33250
|
+
user
|
|
33251
|
+
}) {
|
|
33252
|
+
const client = this.requireClient();
|
|
33253
|
+
const db2 = this.deps.db;
|
|
33254
|
+
await this.deps.validateDeveloperAccess(user, gameId);
|
|
33255
|
+
const { currentIntegration, enrollments } = await this.resolveActiveGameCourse({
|
|
33256
|
+
gameId,
|
|
33257
|
+
studentId,
|
|
33258
|
+
subject,
|
|
33259
|
+
action: "advance"
|
|
33260
|
+
});
|
|
33236
33261
|
const masteryStatus = await client.getMasteryStatus(currentIntegration.courseId, studentId);
|
|
33237
33262
|
if (!masteryStatus) {
|
|
33238
|
-
throw new ValidationError(`Cannot advance course: mastery status is unavailable for course ${currentIntegration.courseId}. Ensure the course has mastery configuration and the student has enrollment analytics before calling client.timeback.
|
|
33263
|
+
throw new ValidationError(`Cannot advance course: mastery status is unavailable for course ${currentIntegration.courseId}. Ensure the course has mastery configuration and the student has enrollment analytics before calling client.timeback.course.advance().`);
|
|
33239
33264
|
}
|
|
33240
33265
|
if (!masteryStatus.isComplete) {
|
|
33241
33266
|
const promotion2 = {
|
|
@@ -33273,6 +33298,93 @@ var init_timeback_service = __esm(() => {
|
|
|
33273
33298
|
});
|
|
33274
33299
|
return { status: "ok", promotion };
|
|
33275
33300
|
}
|
|
33301
|
+
async unenrollCourse({
|
|
33302
|
+
gameId,
|
|
33303
|
+
studentId,
|
|
33304
|
+
subject,
|
|
33305
|
+
force,
|
|
33306
|
+
user
|
|
33307
|
+
}) {
|
|
33308
|
+
const client = this.requireClient();
|
|
33309
|
+
await this.deps.validateDeveloperAccess(user, gameId);
|
|
33310
|
+
const { currentIntegration, currentEnrollment } = await this.resolveActiveGameCourse({
|
|
33311
|
+
gameId,
|
|
33312
|
+
studentId,
|
|
33313
|
+
subject,
|
|
33314
|
+
action: "unenroll"
|
|
33315
|
+
});
|
|
33316
|
+
const schoolId = currentEnrollment.school.id;
|
|
33317
|
+
const masteryStatus = await client.getMasteryStatus(currentIntegration.courseId, studentId);
|
|
33318
|
+
if (!masteryStatus && !force) {
|
|
33319
|
+
throw new ValidationError(`Cannot unenroll course: mastery status is unavailable for course ${currentIntegration.courseId}. Pass { force: true } to unenroll without mastery data.`);
|
|
33320
|
+
}
|
|
33321
|
+
if (masteryStatus && !masteryStatus.isComplete && !force) {
|
|
33322
|
+
const unenrollment = {
|
|
33323
|
+
status: "not-mastered",
|
|
33324
|
+
currentCourseId: currentIntegration.courseId,
|
|
33325
|
+
masteredUnits: masteryStatus.masteredUnits,
|
|
33326
|
+
masterableUnits: masteryStatus.masterableUnits
|
|
33327
|
+
};
|
|
33328
|
+
logger20.debug("Skipping game-initiated course unenroll because mastery is incomplete", {
|
|
33329
|
+
event: "timeback.course.unenroll",
|
|
33330
|
+
outcome: "not-mastered",
|
|
33331
|
+
gameId,
|
|
33332
|
+
studentId,
|
|
33333
|
+
courseId: currentIntegration.courseId,
|
|
33334
|
+
subject: currentIntegration.subject,
|
|
33335
|
+
grade: currentIntegration.grade,
|
|
33336
|
+
masteredUnits: masteryStatus.masteredUnits,
|
|
33337
|
+
masterableUnits: masteryStatus.masterableUnits,
|
|
33338
|
+
forced: false,
|
|
33339
|
+
requesterUserId: user.id
|
|
33340
|
+
});
|
|
33341
|
+
return { status: "ok", unenrollment };
|
|
33342
|
+
}
|
|
33343
|
+
const forcedEarlyExit = Boolean(force && (!masteryStatus || !masteryStatus.isComplete));
|
|
33344
|
+
if (forcedEarlyExit) {
|
|
33345
|
+
logger20.warn("Force-unenrolled student before mastery completion", {
|
|
33346
|
+
event: "timeback.course.unenroll",
|
|
33347
|
+
outcome: "force-unenrolled",
|
|
33348
|
+
gameId,
|
|
33349
|
+
studentId,
|
|
33350
|
+
courseId: currentIntegration.courseId,
|
|
33351
|
+
subject: currentIntegration.subject,
|
|
33352
|
+
grade: currentIntegration.grade,
|
|
33353
|
+
masteredUnits: masteryStatus?.masteredUnits,
|
|
33354
|
+
masterableUnits: masteryStatus?.masterableUnits,
|
|
33355
|
+
masteryStatusAvailable: Boolean(masteryStatus),
|
|
33356
|
+
forced: true,
|
|
33357
|
+
requesterUserId: user.id
|
|
33358
|
+
});
|
|
33359
|
+
}
|
|
33360
|
+
await client.edubridge.enrollments.unenroll(studentId, currentIntegration.courseId, schoolId ? { schoolId } : {});
|
|
33361
|
+
client.invalidateEnrollments(studentId);
|
|
33362
|
+
logger20.info("Game-initiated course unenroll completed", {
|
|
33363
|
+
event: "timeback.course.unenroll",
|
|
33364
|
+
outcome: "unenrolled",
|
|
33365
|
+
gameId,
|
|
33366
|
+
studentId,
|
|
33367
|
+
courseId: currentIntegration.courseId,
|
|
33368
|
+
subject: currentIntegration.subject,
|
|
33369
|
+
grade: currentIntegration.grade,
|
|
33370
|
+
masteredUnits: masteryStatus?.masteredUnits,
|
|
33371
|
+
masterableUnits: masteryStatus?.masterableUnits,
|
|
33372
|
+
forced: forcedEarlyExit,
|
|
33373
|
+
requesterUserId: user.id
|
|
33374
|
+
});
|
|
33375
|
+
return {
|
|
33376
|
+
status: "ok",
|
|
33377
|
+
unenrollment: {
|
|
33378
|
+
status: "unenrolled",
|
|
33379
|
+
currentCourseId: currentIntegration.courseId,
|
|
33380
|
+
...masteryStatus ? {
|
|
33381
|
+
masteredUnits: masteryStatus.masteredUnits,
|
|
33382
|
+
masterableUnits: masteryStatus.masterableUnits
|
|
33383
|
+
} : {},
|
|
33384
|
+
...forcedEarlyExit ? { forced: true } : {}
|
|
33385
|
+
}
|
|
33386
|
+
};
|
|
33387
|
+
}
|
|
33276
33388
|
async recordHeartbeat({
|
|
33277
33389
|
gameId,
|
|
33278
33390
|
studentId,
|
|
@@ -94084,7 +94196,7 @@ var init_session_controller = __esm(() => {
|
|
|
94084
94196
|
});
|
|
94085
94197
|
|
|
94086
94198
|
// ../api-core/src/controllers/timeback.controller.ts
|
|
94087
|
-
var logger45, populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, updateIntegration, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, getStudentXp, getStudentMastery, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getActivityDetail, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, deleteAssessment, reorderAssessments, reorderQuestions, activateAssessment, deactivateAssessment, listQuestions, createQuestion, updateQuestion, deleteQuestion, getAssessmentBankStatus, destroyAssessmentBank, timeback2;
|
|
94199
|
+
var logger45, populateStudent, getUser, getUserEnrollments, getUserById, setupIntegration, getIntegrations, updateIntegration, getIntegrationConfig, verifyIntegration, getConfig, deleteIntegrations, endActivity, heartbeat, advanceCourse, unenrollCourse, getStudentXp, getStudentMastery, getRoster, getStudentOverview, getGameMetrics, getStudentActivity, getActivityDetail, grantXp, adjustTime, adjustMastery, reconcileMasteryForConfigChange, searchStudents, enrollStudent, unenrollStudent, reactivateEnrollment, listAssessments, createAssessment, deleteAssessment, reorderAssessments, reorderQuestions, activateAssessment, deactivateAssessment, listQuestions, createQuestion, updateQuestion, deleteQuestion, getAssessmentBankStatus, destroyAssessmentBank, timeback2;
|
|
94088
94200
|
var init_timeback_controller = __esm(() => {
|
|
94089
94201
|
init_esm();
|
|
94090
94202
|
init_schemas_index();
|
|
@@ -94322,6 +94434,20 @@ var init_timeback_controller = __esm(() => {
|
|
|
94322
94434
|
user: ctx.user
|
|
94323
94435
|
});
|
|
94324
94436
|
});
|
|
94437
|
+
unenrollCourse = requireDeveloper(async (ctx) => {
|
|
94438
|
+
const body2 = await parseRequestBody(ctx.request, UnenrollCourseRequestSchema);
|
|
94439
|
+
logger45.debug("Unenrolling student from current course", {
|
|
94440
|
+
userId: ctx.user.id,
|
|
94441
|
+
gameId: body2.gameId,
|
|
94442
|
+
studentId: body2.studentId,
|
|
94443
|
+
subject: body2.subject,
|
|
94444
|
+
force: body2.force
|
|
94445
|
+
});
|
|
94446
|
+
return ctx.services.timeback.unenrollCourse({
|
|
94447
|
+
...body2,
|
|
94448
|
+
user: ctx.user
|
|
94449
|
+
});
|
|
94450
|
+
});
|
|
94325
94451
|
getStudentXp = requireDeveloper(async (ctx) => {
|
|
94326
94452
|
const timebackId = ctx.params.timebackId;
|
|
94327
94453
|
if (!timebackId) {
|
|
@@ -94749,6 +94875,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
94749
94875
|
endActivity,
|
|
94750
94876
|
heartbeat,
|
|
94751
94877
|
advanceCourse,
|
|
94878
|
+
unenrollCourse,
|
|
94752
94879
|
getStudentXp,
|
|
94753
94880
|
getStudentMastery,
|
|
94754
94881
|
getRoster,
|
|
@@ -95653,6 +95780,7 @@ var init_timeback6 = __esm(() => {
|
|
|
95653
95780
|
timebackRouter.post("/end-activity", handle2(timeback2.endActivity));
|
|
95654
95781
|
timebackRouter.post("/heartbeat", handle2(timeback2.heartbeat));
|
|
95655
95782
|
timebackRouter.post("/advance-course", handle2(timeback2.advanceCourse));
|
|
95783
|
+
timebackRouter.post("/unenroll-course", handle2(timeback2.unenrollCourse));
|
|
95656
95784
|
timebackRouter.get("/user", async (c2) => {
|
|
95657
95785
|
const user = c2.get("user");
|
|
95658
95786
|
const gameId = c2.get("gameId");
|