@playcademy/vite-plugin 0.2.29 → 0.2.30-beta.2
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/index.js +243 -183
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25370,7 +25370,7 @@ var package_default;
|
|
|
25370
25370
|
var init_package = __esm(() => {
|
|
25371
25371
|
package_default = {
|
|
25372
25372
|
name: "@playcademy/sandbox",
|
|
25373
|
-
version: "0.3.
|
|
25373
|
+
version: "0.3.17-beta.35",
|
|
25374
25374
|
description: "Local development server for Playcademy game development",
|
|
25375
25375
|
type: "module",
|
|
25376
25376
|
exports: {
|
|
@@ -54340,6 +54340,31 @@ function resolveAdminEventTime(data) {
|
|
|
54340
54340
|
}
|
|
54341
54341
|
return toAttributionEventTime(data.date);
|
|
54342
54342
|
}
|
|
54343
|
+
function validateMasteryAdjustment(delta, currentMastered, masterableUnits) {
|
|
54344
|
+
if (delta < 0 && currentMastered + delta < 0) {
|
|
54345
|
+
throw new ValidationError(`Adjustment would go below 0. Current: ${currentMastered}, adjustment: ${delta}`);
|
|
54346
|
+
}
|
|
54347
|
+
if (delta < 0 && typeof masterableUnits === "number" && masterableUnits > 0 && currentMastered > masterableUnits && currentMastered + delta > masterableUnits) {
|
|
54348
|
+
const minDelta = masterableUnits - currentMastered;
|
|
54349
|
+
throw new ValidationError(`Adjustment must reduce mastery to at most ${masterableUnits}. Current: ${currentMastered}/${masterableUnits}, minimum adjustment: ${minDelta}`);
|
|
54350
|
+
}
|
|
54351
|
+
if (delta > 0 && typeof masterableUnits === "number" && masterableUnits > 0) {
|
|
54352
|
+
if (currentMastered >= masterableUnits) {
|
|
54353
|
+
throw new ValidationError(`Mastery is already at maximum (${currentMastered}/${masterableUnits}). Only negative adjustments are allowed.`);
|
|
54354
|
+
}
|
|
54355
|
+
if (currentMastered + delta > masterableUnits) {
|
|
54356
|
+
const remaining = masterableUnits - currentMastered;
|
|
54357
|
+
throw new ValidationError(`Adjustment would exceed maximum. Current: ${currentMastered}/${masterableUnits}, max adjustment: +${remaining}`);
|
|
54358
|
+
}
|
|
54359
|
+
}
|
|
54360
|
+
}
|
|
54361
|
+
function compareEnrollmentsByRecency(a, b) {
|
|
54362
|
+
const dateCompare = (b.beginDate ?? "").localeCompare(a.beginDate ?? "");
|
|
54363
|
+
if (dateCompare !== 0) {
|
|
54364
|
+
return dateCompare;
|
|
54365
|
+
}
|
|
54366
|
+
return (b.dateLastModified ?? "").localeCompare(a.dateLastModified ?? "");
|
|
54367
|
+
}
|
|
54343
54368
|
var init_timeback_admin_util = __esm(() => {
|
|
54344
54369
|
init_errors();
|
|
54345
54370
|
});
|
|
@@ -54689,17 +54714,6 @@ class TimebackAdminService {
|
|
|
54689
54714
|
}
|
|
54690
54715
|
return this.deps.timeback;
|
|
54691
54716
|
}
|
|
54692
|
-
async recordCourseCompletionHistory(client, data) {
|
|
54693
|
-
await client.recordAdminCourseCompletionChange(data).catch((error) => {
|
|
54694
|
-
logger16.error("Failed to record admin course completion history event", {
|
|
54695
|
-
gameId: data.gameId,
|
|
54696
|
-
courseId: data.courseId,
|
|
54697
|
-
studentId: data.studentId,
|
|
54698
|
-
action: data.action,
|
|
54699
|
-
error: error instanceof Error ? error.message : String(error)
|
|
54700
|
-
});
|
|
54701
|
-
});
|
|
54702
|
-
}
|
|
54703
54717
|
async resolveAdminMutationContext(gameId, courseId, user, studentId, accessLevel = "developer") {
|
|
54704
54718
|
const client = this.requireClient();
|
|
54705
54719
|
if (accessLevel === "dashboard") {
|
|
@@ -54798,7 +54812,7 @@ class TimebackAdminService {
|
|
|
54798
54812
|
if (typeof masterableUnits !== "number" || masterableUnits <= 0) {
|
|
54799
54813
|
return;
|
|
54800
54814
|
}
|
|
54801
|
-
return Math.
|
|
54815
|
+
return Math.round(masteredUnits / masterableUnits * 100);
|
|
54802
54816
|
}
|
|
54803
54817
|
async getMasterableUnits(courseId) {
|
|
54804
54818
|
const client = this.requireClient();
|
|
@@ -54851,6 +54865,7 @@ class TimebackAdminService {
|
|
|
54851
54865
|
}
|
|
54852
54866
|
async getStudentEnrollmentsByCourseId(client, studentId, courseIds, options) {
|
|
54853
54867
|
const enrollments = new Map;
|
|
54868
|
+
const allEnrollments = new Map;
|
|
54854
54869
|
const entries = await Promise.all(courseIds.map(async (courseId) => {
|
|
54855
54870
|
const roster = await client.oneroster.enrollments.listByCourse(courseId, {
|
|
54856
54871
|
includeInactive: options?.includeInactive,
|
|
@@ -54862,28 +54877,30 @@ class TimebackAdminService {
|
|
|
54862
54877
|
if (aActive !== bActive) {
|
|
54863
54878
|
return aActive ? -1 : 1;
|
|
54864
54879
|
}
|
|
54865
|
-
return (
|
|
54880
|
+
return compareEnrollmentsByRecency(a.enrollment, b.enrollment);
|
|
54866
54881
|
});
|
|
54867
|
-
return { courseId,
|
|
54882
|
+
return { courseId, matches };
|
|
54868
54883
|
}));
|
|
54869
|
-
for (const { courseId,
|
|
54870
|
-
|
|
54871
|
-
|
|
54872
|
-
|
|
54873
|
-
|
|
54874
|
-
|
|
54875
|
-
|
|
54876
|
-
|
|
54877
|
-
|
|
54878
|
-
|
|
54879
|
-
|
|
54880
|
-
|
|
54881
|
-
|
|
54882
|
-
|
|
54883
|
-
|
|
54884
|
+
for (const { courseId, matches } of entries) {
|
|
54885
|
+
const records = matches.map((match) => ({
|
|
54886
|
+
id: match.enrollment.sourcedId,
|
|
54887
|
+
status: match.enrollment.status ?? "active",
|
|
54888
|
+
role: match.enrollment.role ?? "student",
|
|
54889
|
+
beginDate: match.enrollment.beginDate ?? null,
|
|
54890
|
+
endDate: match.enrollment.endDate ?? null,
|
|
54891
|
+
course: {
|
|
54892
|
+
id: courseId,
|
|
54893
|
+
title: match.class?.title ?? "",
|
|
54894
|
+
subjects: null,
|
|
54895
|
+
grades: null
|
|
54896
|
+
}
|
|
54897
|
+
}));
|
|
54898
|
+
if (records.length > 0) {
|
|
54899
|
+
enrollments.set(courseId, records[0]);
|
|
54884
54900
|
}
|
|
54901
|
+
allEnrollments.set(courseId, records);
|
|
54885
54902
|
}
|
|
54886
|
-
return { enrollments };
|
|
54903
|
+
return { enrollments, allEnrollments };
|
|
54887
54904
|
}
|
|
54888
54905
|
async assertStudentEnrolledInCourse(client, studentId, courseId) {
|
|
54889
54906
|
const enrollments = await client.edubridge.enrollments.listByUser(studentId);
|
|
@@ -54970,7 +54987,7 @@ class TimebackAdminService {
|
|
|
54970
54987
|
const enrollmentId = rosterEntry.enrollment.sourcedId || null;
|
|
54971
54988
|
const summary = enrollmentId ? analyticsByEnrollmentId.get(enrollmentId) : undefined;
|
|
54972
54989
|
const analyticsUnavailable = Boolean(enrollmentId) && summary?.analyticsAvailable !== true;
|
|
54973
|
-
const name3 = rosterEntry.user ? `${rosterEntry.user.givenName} ${rosterEntry.user.familyName}`.trim() :
|
|
54990
|
+
const name3 = rosterEntry.user ? `${rosterEntry.user.givenName} ${rosterEntry.user.familyName}`.trim() || "No name specified" : "No name specified";
|
|
54974
54991
|
const inactive = rosterEntry.enrollment.status === "tobedeleted";
|
|
54975
54992
|
return {
|
|
54976
54993
|
studentId: rosterEntry.enrollment.user.sourcedId,
|
|
@@ -55015,14 +55032,15 @@ class TimebackAdminService {
|
|
|
55015
55032
|
throw new NotFoundError("Timeback integration", gameId);
|
|
55016
55033
|
}
|
|
55017
55034
|
const courseIds = new Set(integrations.map((integration) => integration.courseId));
|
|
55018
|
-
const { enrollments: enrollmentsByCourseId } = await this.getStudentEnrollmentsByCourseId(client, studentId, [...courseIds], {
|
|
55035
|
+
const { enrollments: enrollmentsByCourseId, allEnrollments: allEnrollmentsByCourseId } = await this.getStudentEnrollmentsByCourseId(client, studentId, [...courseIds], {
|
|
55019
55036
|
includeInactive: true
|
|
55020
55037
|
});
|
|
55021
55038
|
if (enrollmentsByCourseId.size === 0) {
|
|
55022
55039
|
throw new NotFoundError("Student enrollment", courseId ? `${studentId}:${courseId}` : `${studentId}:${gameId}`);
|
|
55023
55040
|
}
|
|
55041
|
+
const allEnrollmentIds = [...allEnrollmentsByCourseId.values()].flat().map((enrollment) => enrollment.id);
|
|
55024
55042
|
const studentProfile = await client.oneroster.users.get(studentId);
|
|
55025
|
-
const analyticsByEnrollmentId = await this.loadEnrollmentAnalyticsSummaries(
|
|
55043
|
+
const analyticsByEnrollmentId = await this.loadEnrollmentAnalyticsSummaries(allEnrollmentIds);
|
|
55026
55044
|
const [masterableUnitsByCourse, completionStatusByCourse] = await Promise.all([
|
|
55027
55045
|
this.getMasterableUnitsByCourse(integrations.map((integration) => integration.courseId)),
|
|
55028
55046
|
this.getCompletionStatusByCourse(client, integrations.map((integration) => integration.courseId), studentId)
|
|
@@ -55033,6 +55051,24 @@ class TimebackAdminService {
|
|
|
55033
55051
|
const masterableUnits = masterableUnitsByCourse.get(integration.courseId);
|
|
55034
55052
|
const analyticsUnavailable = Boolean(enrollment?.id) && summary?.analyticsAvailable !== true;
|
|
55035
55053
|
const inactive = enrollment?.status === "tobedeleted";
|
|
55054
|
+
const courseEnrollments = allEnrollmentsByCourseId.get(integration.courseId) ?? [];
|
|
55055
|
+
const enrollmentSummaries = courseEnrollments.length > 1 ? courseEnrollments.map((record) => {
|
|
55056
|
+
const recordSummary = analyticsByEnrollmentId.get(record.id);
|
|
55057
|
+
const recordAnalyticsUnavailable = recordSummary?.analyticsAvailable !== true;
|
|
55058
|
+
return {
|
|
55059
|
+
enrollmentId: record.id,
|
|
55060
|
+
status: record.status === "tobedeleted" ? "tobedeleted" : "active",
|
|
55061
|
+
beginDate: record.beginDate,
|
|
55062
|
+
endDate: record.endDate,
|
|
55063
|
+
analyticsUnavailable: recordAnalyticsUnavailable,
|
|
55064
|
+
totalXp: recordSummary?.totalXp ?? 0,
|
|
55065
|
+
todayXp: recordSummary?.todayXp ?? 0,
|
|
55066
|
+
activeTimeSeconds: recordSummary?.activeTimeSeconds ?? 0,
|
|
55067
|
+
masteredUnits: recordSummary?.masteredUnits ?? 0,
|
|
55068
|
+
pctCompleteApp: TimebackAdminService.computeCompletionPct(recordSummary?.masteredUnits ?? 0, masterableUnits),
|
|
55069
|
+
history: recordSummary?.history ?? []
|
|
55070
|
+
};
|
|
55071
|
+
}) : undefined;
|
|
55036
55072
|
return {
|
|
55037
55073
|
courseId: integration.courseId,
|
|
55038
55074
|
title: enrollment?.course.title || `${integration.subject} Grade ${integration.grade}`,
|
|
@@ -55048,9 +55084,11 @@ class TimebackAdminService {
|
|
|
55048
55084
|
pctCompleteApp: TimebackAdminService.computeCompletionPct(summary?.masteredUnits ?? 0, masterableUnits),
|
|
55049
55085
|
completionStatus: completionStatusByCourse.get(integration.courseId) ?? "none",
|
|
55050
55086
|
history: summary?.history ?? [],
|
|
55051
|
-
...inactive ? { inactive } : {}
|
|
55087
|
+
...inactive ? { inactive } : {},
|
|
55088
|
+
...enrollmentSummaries ? { enrollments: enrollmentSummaries } : {}
|
|
55052
55089
|
};
|
|
55053
55090
|
});
|
|
55091
|
+
courses.sort((a, b) => a.grade - b.grade);
|
|
55054
55092
|
return {
|
|
55055
55093
|
student: {
|
|
55056
55094
|
studentId,
|
|
@@ -55150,111 +55188,87 @@ class TimebackAdminService {
|
|
|
55150
55188
|
return { status: "ok" };
|
|
55151
55189
|
}
|
|
55152
55190
|
async adjustMasteredUnits(data, user) {
|
|
55153
|
-
const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user
|
|
55191
|
+
const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user);
|
|
55192
|
+
let currentMastered = 0;
|
|
55193
|
+
const masterableUnits = await this.getMasterableUnits(data.courseId);
|
|
55194
|
+
if (data.units !== 0) {
|
|
55195
|
+
const enrollment = await this.assertStudentEnrolledInCourse(client, data.studentId, data.courseId);
|
|
55196
|
+
try {
|
|
55197
|
+
const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
|
|
55198
|
+
currentMastered = this.summarizeAnalyticsFacts(analytics.facts).masteredUnits;
|
|
55199
|
+
} catch {
|
|
55200
|
+
throw new ValidationError("Unable to validate mastery bounds — analytics unavailable. Please retry.");
|
|
55201
|
+
}
|
|
55202
|
+
validateMasteryAdjustment(data.units, currentMastered, masterableUnits);
|
|
55203
|
+
}
|
|
55204
|
+
const pctCompleteApp = typeof masterableUnits === "number" && masterableUnits > 0 ? Math.min(100, Math.max(0, Math.round((currentMastered + data.units) / masterableUnits * 100))) : undefined;
|
|
55154
55205
|
await client.recordAdminMasteryAdjustment({
|
|
55155
55206
|
gameId: data.gameId,
|
|
55156
55207
|
courseId: data.courseId,
|
|
55157
55208
|
studentId: data.studentId,
|
|
55158
55209
|
masteredUnits: data.units,
|
|
55210
|
+
pctCompleteApp,
|
|
55159
55211
|
eventTime: resolveAdminEventTime(data),
|
|
55160
55212
|
reason: data.reason,
|
|
55161
55213
|
actor,
|
|
55162
55214
|
appName,
|
|
55163
55215
|
sensorUrl
|
|
55164
55216
|
});
|
|
55165
|
-
|
|
55166
|
-
|
|
55167
|
-
|
|
55168
|
-
|
|
55169
|
-
|
|
55170
|
-
|
|
55171
|
-
|
|
55172
|
-
|
|
55173
|
-
|
|
55174
|
-
|
|
55175
|
-
|
|
55176
|
-
|
|
55177
|
-
|
|
55178
|
-
|
|
55179
|
-
});
|
|
55180
|
-
if (data.action === "complete") {
|
|
55181
|
-
const masterableUnits = await this.getMasterableUnits(data.courseId);
|
|
55182
|
-
if (masterableUnits && masterableUnits > 0) {
|
|
55183
|
-
const enrollment = await this.assertStudentEnrolledInCourse(client, data.studentId, data.courseId);
|
|
55184
|
-
let currentMastered = 0;
|
|
55185
|
-
try {
|
|
55186
|
-
const analytics = await client.edubridge.analytics.getEnrollmentFacts(enrollment.id, { timezone: PLATFORM_TIMEZONE });
|
|
55187
|
-
const summary = this.summarizeAnalyticsFacts(analytics.facts);
|
|
55188
|
-
currentMastered = summary.masteredUnits;
|
|
55189
|
-
} catch {
|
|
55190
|
-
logger16.warn("Failed to load analytics for mastery gap calculation", {
|
|
55191
|
-
studentId: data.studentId,
|
|
55192
|
-
courseId: data.courseId
|
|
55217
|
+
if (typeof masterableUnits === "number" && masterableUnits > 0) {
|
|
55218
|
+
const wasMastered = currentMastered >= masterableUnits;
|
|
55219
|
+
const willBeMastered = currentMastered + data.units >= masterableUnits;
|
|
55220
|
+
if (wasMastered !== willBeMastered) {
|
|
55221
|
+
const ids = deriveSourcedIds(data.courseId);
|
|
55222
|
+
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
55223
|
+
const resultId = `${lineItemId}:${data.studentId}:completion`;
|
|
55224
|
+
if (willBeMastered) {
|
|
55225
|
+
await client.oneroster.assessmentLineItems.findOrCreate(lineItemId, {
|
|
55226
|
+
sourcedId: lineItemId,
|
|
55227
|
+
title: "Mastery Completion",
|
|
55228
|
+
status: ONEROSTER_STATUS.active,
|
|
55229
|
+
course: { sourcedId: ids.course },
|
|
55230
|
+
...ids.componentResource ? { componentResource: { sourcedId: ids.componentResource } } : {}
|
|
55193
55231
|
});
|
|
55194
|
-
|
|
55195
|
-
|
|
55196
|
-
|
|
55197
|
-
|
|
55198
|
-
|
|
55199
|
-
|
|
55200
|
-
|
|
55201
|
-
|
|
55202
|
-
|
|
55203
|
-
|
|
55204
|
-
|
|
55205
|
-
|
|
55232
|
+
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
55233
|
+
sourcedId: resultId,
|
|
55234
|
+
status: ONEROSTER_STATUS.active,
|
|
55235
|
+
assessmentLineItem: { sourcedId: lineItemId },
|
|
55236
|
+
student: { sourcedId: data.studentId },
|
|
55237
|
+
score: 100,
|
|
55238
|
+
scoreDate: new Date().toISOString(),
|
|
55239
|
+
scoreStatus: SCORE_STATUS.fullyGraded,
|
|
55240
|
+
inProgress: "false",
|
|
55241
|
+
metadata: {
|
|
55242
|
+
isMasteryCompletion: true,
|
|
55243
|
+
adminAction: true,
|
|
55244
|
+
appName
|
|
55245
|
+
}
|
|
55206
55246
|
});
|
|
55247
|
+
} else {
|
|
55248
|
+
try {
|
|
55249
|
+
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
55250
|
+
sourcedId: resultId,
|
|
55251
|
+
status: ONEROSTER_STATUS.active,
|
|
55252
|
+
assessmentLineItem: { sourcedId: lineItemId },
|
|
55253
|
+
student: { sourcedId: data.studentId },
|
|
55254
|
+
score: 0,
|
|
55255
|
+
scoreDate: new Date().toISOString(),
|
|
55256
|
+
scoreStatus: SCORE_STATUS.notSubmitted,
|
|
55257
|
+
inProgress: "true",
|
|
55258
|
+
metadata: {
|
|
55259
|
+
isMasteryCompletion: true,
|
|
55260
|
+
adminAction: true,
|
|
55261
|
+
appName
|
|
55262
|
+
}
|
|
55263
|
+
});
|
|
55264
|
+
} catch {
|
|
55265
|
+
logger16.debug("No completion entry to revoke", {
|
|
55266
|
+
studentId: data.studentId,
|
|
55267
|
+
courseId: data.courseId
|
|
55268
|
+
});
|
|
55269
|
+
}
|
|
55207
55270
|
}
|
|
55208
55271
|
}
|
|
55209
|
-
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
55210
|
-
sourcedId: resultId,
|
|
55211
|
-
status: ONEROSTER_STATUS.active,
|
|
55212
|
-
assessmentLineItem: { sourcedId: lineItemId },
|
|
55213
|
-
student: { sourcedId: data.studentId },
|
|
55214
|
-
score: 100,
|
|
55215
|
-
scoreDate: new Date().toISOString(),
|
|
55216
|
-
scoreStatus: SCORE_STATUS.fullyGraded,
|
|
55217
|
-
inProgress: "false",
|
|
55218
|
-
metadata: {
|
|
55219
|
-
isMasteryCompletion: true,
|
|
55220
|
-
adminAction: true,
|
|
55221
|
-
appName
|
|
55222
|
-
}
|
|
55223
|
-
});
|
|
55224
|
-
await this.recordCourseCompletionHistory(historyClient, {
|
|
55225
|
-
gameId: data.gameId,
|
|
55226
|
-
courseId: data.courseId,
|
|
55227
|
-
studentId: data.studentId,
|
|
55228
|
-
action: "complete",
|
|
55229
|
-
actor,
|
|
55230
|
-
appName,
|
|
55231
|
-
sensorUrl
|
|
55232
|
-
});
|
|
55233
|
-
} else {
|
|
55234
|
-
await client.oneroster.assessmentResults.upsert(resultId, {
|
|
55235
|
-
sourcedId: resultId,
|
|
55236
|
-
status: ONEROSTER_STATUS.active,
|
|
55237
|
-
assessmentLineItem: { sourcedId: lineItemId },
|
|
55238
|
-
student: { sourcedId: data.studentId },
|
|
55239
|
-
score: 0,
|
|
55240
|
-
scoreDate: new Date().toISOString(),
|
|
55241
|
-
scoreStatus: SCORE_STATUS.notSubmitted,
|
|
55242
|
-
inProgress: "true",
|
|
55243
|
-
metadata: {
|
|
55244
|
-
isMasteryCompletion: true,
|
|
55245
|
-
adminAction: true,
|
|
55246
|
-
appName
|
|
55247
|
-
}
|
|
55248
|
-
});
|
|
55249
|
-
await this.recordCourseCompletionHistory(historyClient, {
|
|
55250
|
-
gameId: data.gameId,
|
|
55251
|
-
courseId: data.courseId,
|
|
55252
|
-
studentId: data.studentId,
|
|
55253
|
-
action: "resume",
|
|
55254
|
-
actor,
|
|
55255
|
-
appName,
|
|
55256
|
-
sensorUrl
|
|
55257
|
-
});
|
|
55258
55272
|
}
|
|
55259
55273
|
return { status: "ok" };
|
|
55260
55274
|
}
|
|
@@ -55290,17 +55304,34 @@ class TimebackAdminService {
|
|
|
55290
55304
|
});
|
|
55291
55305
|
return { students: [] };
|
|
55292
55306
|
}
|
|
55293
|
-
const
|
|
55307
|
+
const fullRoster = await client.oneroster.enrollments.listByCourse(courseId, {
|
|
55294
55308
|
role: "student",
|
|
55309
|
+
includeInactive: true,
|
|
55295
55310
|
includeUsers: false
|
|
55296
55311
|
});
|
|
55297
|
-
const enrolledStudentIds = new Set(
|
|
55298
|
-
const
|
|
55299
|
-
|
|
55300
|
-
|
|
55301
|
-
|
|
55302
|
-
|
|
55303
|
-
|
|
55312
|
+
const enrolledStudentIds = new Set(fullRoster.filter((entry) => entry.enrollment.status === "active").map((entry) => entry.enrollment.user.sourcedId));
|
|
55313
|
+
const pastEnrollmentsByStudent = new Map;
|
|
55314
|
+
const inactiveEntries = fullRoster.filter((entry) => entry.enrollment.status === "tobedeleted").toSorted((a, b) => compareEnrollmentsByRecency(a.enrollment, b.enrollment));
|
|
55315
|
+
for (const entry of inactiveEntries) {
|
|
55316
|
+
const studentId = entry.enrollment.user.sourcedId;
|
|
55317
|
+
const list = pastEnrollmentsByStudent.get(studentId) ?? [];
|
|
55318
|
+
list.push({
|
|
55319
|
+
enrollmentId: entry.enrollment.sourcedId,
|
|
55320
|
+
beginDate: entry.enrollment.beginDate ?? null,
|
|
55321
|
+
endDate: entry.enrollment.endDate ?? null
|
|
55322
|
+
});
|
|
55323
|
+
pastEnrollmentsByStudent.set(studentId, list);
|
|
55324
|
+
}
|
|
55325
|
+
const students = allUsers.filter((entry) => Boolean(entry.sourcedId) && entry.roles?.some((role) => role.role === "student") === true).map((entry) => {
|
|
55326
|
+
const past = pastEnrollmentsByStudent.get(entry.sourcedId) ?? [];
|
|
55327
|
+
return {
|
|
55328
|
+
studentId: entry.sourcedId,
|
|
55329
|
+
name: `${entry.givenName || ""} ${entry.familyName || ""}`.trim() || "No name specified",
|
|
55330
|
+
email: entry.email || null,
|
|
55331
|
+
alreadyEnrolled: enrolledStudentIds.has(entry.sourcedId),
|
|
55332
|
+
...past.length > 0 ? { pastEnrollments: past } : {}
|
|
55333
|
+
};
|
|
55334
|
+
});
|
|
55304
55335
|
return { students };
|
|
55305
55336
|
}
|
|
55306
55337
|
async enrollStudent(data, user) {
|
|
@@ -55331,6 +55362,48 @@ class TimebackAdminService {
|
|
|
55331
55362
|
client.invalidateEnrollments(data.studentId);
|
|
55332
55363
|
return { status: "ok" };
|
|
55333
55364
|
}
|
|
55365
|
+
async reactivateEnrollment(data, user) {
|
|
55366
|
+
const client = this.requireClient();
|
|
55367
|
+
await this.deps.validateGameManagementAccess(user, data.gameId);
|
|
55368
|
+
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
55369
|
+
where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId))
|
|
55370
|
+
});
|
|
55371
|
+
if (!integration) {
|
|
55372
|
+
throw new NotFoundError("Timeback integration", `${data.gameId}:${data.courseId}`);
|
|
55373
|
+
}
|
|
55374
|
+
const enrollment = await client.oneroster.enrollments.get(data.enrollmentId);
|
|
55375
|
+
if (!enrollment) {
|
|
55376
|
+
throw new NotFoundError("Enrollment", data.enrollmentId);
|
|
55377
|
+
}
|
|
55378
|
+
if (enrollment.user.sourcedId !== data.studentId) {
|
|
55379
|
+
throw new ValidationError("Enrollment does not belong to the specified student");
|
|
55380
|
+
}
|
|
55381
|
+
if (enrollment.status === "active") {
|
|
55382
|
+
throw new ValidationError("Enrollment is already active");
|
|
55383
|
+
}
|
|
55384
|
+
const { allEnrollments } = await this.getStudentEnrollmentsByCourseId(client, data.studentId, [data.courseId], { includeInactive: true });
|
|
55385
|
+
const courseEnrollmentIds = new Set((allEnrollments.get(data.courseId) ?? []).map((e) => e.id));
|
|
55386
|
+
if (!courseEnrollmentIds.has(data.enrollmentId)) {
|
|
55387
|
+
throw new ValidationError("Enrollment does not belong to the specified course");
|
|
55388
|
+
}
|
|
55389
|
+
const activeEnrollments = await client.edubridge.enrollments.listByUser(data.studentId);
|
|
55390
|
+
if (activeEnrollments.some((e) => e.course.id === data.courseId)) {
|
|
55391
|
+
throw new ValidationError("Student already has an active enrollment for this course. Unenroll from the current enrollment before reactivating a past one.");
|
|
55392
|
+
}
|
|
55393
|
+
await client.oneroster.enrollments.update(data.enrollmentId, {
|
|
55394
|
+
role: enrollment.role,
|
|
55395
|
+
primary: enrollment.primary,
|
|
55396
|
+
beginDate: enrollment.beginDate,
|
|
55397
|
+
endDate: enrollment.endDate,
|
|
55398
|
+
user: enrollment.user,
|
|
55399
|
+
class: enrollment.class,
|
|
55400
|
+
school: enrollment.school,
|
|
55401
|
+
sourcedId: data.enrollmentId,
|
|
55402
|
+
status: "active"
|
|
55403
|
+
});
|
|
55404
|
+
client.invalidateEnrollments(data.studentId);
|
|
55405
|
+
return { status: "ok" };
|
|
55406
|
+
}
|
|
55334
55407
|
async getCompletionStatus(client, courseId, studentId) {
|
|
55335
55408
|
const ids = deriveSourcedIds(courseId);
|
|
55336
55409
|
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
@@ -60403,6 +60476,10 @@ function createOneRosterNamespace(client) {
|
|
|
60403
60476
|
}
|
|
60404
60477
|
},
|
|
60405
60478
|
enrollments: {
|
|
60479
|
+
get: async (sourcedId) => {
|
|
60480
|
+
const response = await client["request"](`${ONEROSTER_ENDPOINTS5.enrollments}/${sourcedId}`, "GET");
|
|
60481
|
+
return response.enrollment;
|
|
60482
|
+
},
|
|
60406
60483
|
listByClass: async (classSourcedId, options) => {
|
|
60407
60484
|
const queryParams = new URLSearchParams;
|
|
60408
60485
|
const filters = [`class.sourcedId='${escapeFilterValue2(classSourcedId)}'`];
|
|
@@ -60480,6 +60557,11 @@ function createOneRosterNamespace(client) {
|
|
|
60480
60557
|
}
|
|
60481
60558
|
},
|
|
60482
60559
|
create: async (data) => client["request"](ONEROSTER_ENDPOINTS5.enrollments, "POST", { enrollment: data }),
|
|
60560
|
+
update: async (sourcedId, data) => {
|
|
60561
|
+
await client["request"](`${ONEROSTER_ENDPOINTS5.enrollments}/${sourcedId}`, "PUT", {
|
|
60562
|
+
enrollment: data
|
|
60563
|
+
});
|
|
60564
|
+
},
|
|
60483
60565
|
delete: async (sourcedId) => {
|
|
60484
60566
|
await client["request"](`${ONEROSTER_ENDPOINTS5.enrollments}/${sourcedId}`, "DELETE");
|
|
60485
60567
|
}
|
|
@@ -60829,6 +60911,7 @@ class AdminEventRecorder {
|
|
|
60829
60911
|
defaultActivityId: "playcademy-admin-mastery-adjustment",
|
|
60830
60912
|
eventKind: "remediation-mastery"
|
|
60831
60913
|
});
|
|
60914
|
+
const courseUrl = createOneRosterUrls(TIMEBACK_API_URLS5[this.environment]).course(data.courseId);
|
|
60832
60915
|
await this.caliper.emitActivityEvent({
|
|
60833
60916
|
studentId: ctx.student.id,
|
|
60834
60917
|
studentEmail: ctx.student.email,
|
|
@@ -60843,32 +60926,13 @@ class AdminEventRecorder {
|
|
|
60843
60926
|
appName: ctx.appName,
|
|
60844
60927
|
sensorUrl: ctx.sensorUrl,
|
|
60845
60928
|
process: true,
|
|
60846
|
-
generatedExtensions: ctx.metadata,
|
|
60847
|
-
eventExtensions: ctx.metadata
|
|
60848
|
-
});
|
|
60849
|
-
}
|
|
60850
|
-
async recordCourseCompletionChange(data) {
|
|
60851
|
-
const isResume = data.action === "resume";
|
|
60852
|
-
const ctx = await this.prepareAdminEvent({
|
|
60853
|
-
...data,
|
|
60854
|
-
defaultActivityId: isResume ? "playcademy-admin-course-resumed" : "playcademy-admin-course-completed",
|
|
60855
|
-
reason: "Admin action",
|
|
60856
|
-
eventKind: isResume ? "course-resumed" : "course-completed"
|
|
60857
|
-
});
|
|
60858
|
-
await this.caliper.emitActivityEvent({
|
|
60859
|
-
studentId: ctx.student.id,
|
|
60860
|
-
studentEmail: ctx.student.email,
|
|
60861
|
-
gameId: data.gameId,
|
|
60862
|
-
activityId: ctx.activityId,
|
|
60863
|
-
activityName: isResume ? "Course resumed" : "Course marked complete",
|
|
60864
|
-
courseId: data.courseId,
|
|
60865
|
-
courseName: ctx.courseContext.courseName,
|
|
60866
|
-
subject: ctx.courseContext.subject,
|
|
60867
|
-
appName: ctx.appName,
|
|
60868
|
-
sensorUrl: ctx.sensorUrl,
|
|
60869
|
-
process: false,
|
|
60870
60929
|
includeAttempt: false,
|
|
60871
|
-
|
|
60930
|
+
objectId: `${ctx.sensorUrl.replace(/\/$/, "")}/urn:uuid:${crypto.randomUUID()}`,
|
|
60931
|
+
generatedId: courseUrl,
|
|
60932
|
+
generatedExtensions: {
|
|
60933
|
+
...ctx.metadata,
|
|
60934
|
+
...data.pctCompleteApp !== undefined ? { pctCompleteApp: data.pctCompleteApp } : {}
|
|
60935
|
+
},
|
|
60872
60936
|
eventExtensions: ctx.metadata
|
|
60873
60937
|
});
|
|
60874
60938
|
}
|
|
@@ -62059,10 +62123,6 @@ class TimebackClient {
|
|
|
62059
62123
|
await this._ensureAuthenticated();
|
|
62060
62124
|
return this.adminEventRecorder.recordMasteryAdjustment(data);
|
|
62061
62125
|
}
|
|
62062
|
-
async recordAdminCourseCompletionChange(data) {
|
|
62063
|
-
await this._ensureAuthenticated();
|
|
62064
|
-
return this.adminEventRecorder.recordCourseCompletionChange(data);
|
|
62065
|
-
}
|
|
62066
62126
|
clearCaches() {
|
|
62067
62127
|
this.cacheManager.clearAll();
|
|
62068
62128
|
}
|
|
@@ -121696,9 +121756,9 @@ var AdminAttributionDateSchema;
|
|
|
121696
121756
|
var GrantTimebackXpRequestSchema;
|
|
121697
121757
|
var AdjustTimebackTimeRequestSchema;
|
|
121698
121758
|
var AdjustTimebackMasteryRequestSchema;
|
|
121699
|
-
var ToggleCourseCompletionRequestSchema;
|
|
121700
121759
|
var EnrollStudentRequestSchema;
|
|
121701
121760
|
var UnenrollStudentRequestSchema;
|
|
121761
|
+
var ReactivateEnrollmentRequestSchema;
|
|
121702
121762
|
var InsertAssessmentTestSchema;
|
|
121703
121763
|
var CreateAssessmentRequestSchema;
|
|
121704
121764
|
var ReorderAssessmentsRequestSchema;
|
|
@@ -121873,12 +121933,6 @@ var init_schemas11 = __esm(() => {
|
|
|
121873
121933
|
date: AdminAttributionDateSchema.optional(),
|
|
121874
121934
|
useCurrentTime: exports_external.boolean().optional()
|
|
121875
121935
|
});
|
|
121876
|
-
ToggleCourseCompletionRequestSchema = exports_external.object({
|
|
121877
|
-
gameId: exports_external.string().uuid(),
|
|
121878
|
-
courseId: exports_external.string().min(1),
|
|
121879
|
-
studentId: exports_external.string().min(1),
|
|
121880
|
-
action: exports_external.enum(["complete", "resume"])
|
|
121881
|
-
});
|
|
121882
121936
|
EnrollStudentRequestSchema = exports_external.object({
|
|
121883
121937
|
gameId: exports_external.string().uuid(),
|
|
121884
121938
|
courseId: exports_external.string().min(1),
|
|
@@ -121889,6 +121943,12 @@ var init_schemas11 = __esm(() => {
|
|
|
121889
121943
|
courseId: exports_external.string().min(1),
|
|
121890
121944
|
studentId: exports_external.string().min(1)
|
|
121891
121945
|
});
|
|
121946
|
+
ReactivateEnrollmentRequestSchema = exports_external.object({
|
|
121947
|
+
gameId: exports_external.string().uuid(),
|
|
121948
|
+
courseId: exports_external.string().min(1),
|
|
121949
|
+
studentId: exports_external.string().min(1),
|
|
121950
|
+
enrollmentId: exports_external.string().min(1)
|
|
121951
|
+
});
|
|
121892
121952
|
InsertAssessmentTestSchema = createInsertSchema(gameTimebackAssessmentTests).omit({
|
|
121893
121953
|
id: true,
|
|
121894
121954
|
createdAt: true
|
|
@@ -124138,10 +124198,10 @@ var getActivityDetail;
|
|
|
124138
124198
|
var grantXp;
|
|
124139
124199
|
var adjustTime;
|
|
124140
124200
|
var adjustMastery;
|
|
124141
|
-
var toggleCompletion;
|
|
124142
124201
|
var searchStudents;
|
|
124143
124202
|
var enrollStudent;
|
|
124144
124203
|
var unenrollStudent;
|
|
124204
|
+
var reactivateEnrollment;
|
|
124145
124205
|
var listAssessments;
|
|
124146
124206
|
var createAssessment;
|
|
124147
124207
|
var deleteAssessment;
|
|
@@ -124556,17 +124616,6 @@ var init_timeback_controller = __esm(() => {
|
|
|
124556
124616
|
});
|
|
124557
124617
|
return ctx.services.timebackAdmin.adjustMasteredUnits(body2, ctx.user);
|
|
124558
124618
|
});
|
|
124559
|
-
toggleCompletion = requireGameManagementAccess(async (ctx) => {
|
|
124560
|
-
const body2 = await parseRequestBody(ctx.request, ToggleCourseCompletionRequestSchema);
|
|
124561
|
-
logger65.debug("Toggling course completion", {
|
|
124562
|
-
requesterId: ctx.user.id,
|
|
124563
|
-
gameId: body2.gameId,
|
|
124564
|
-
courseId: body2.courseId,
|
|
124565
|
-
studentId: body2.studentId,
|
|
124566
|
-
action: body2.action
|
|
124567
|
-
});
|
|
124568
|
-
return ctx.services.timebackAdmin.toggleCourseCompletion(body2, ctx.user);
|
|
124569
|
-
});
|
|
124570
124619
|
searchStudents = requireGameManagementAccess(async (ctx) => {
|
|
124571
124620
|
const gameId = ctx.params.gameId;
|
|
124572
124621
|
const courseId = ctx.params.courseId;
|
|
@@ -124602,6 +124651,17 @@ var init_timeback_controller = __esm(() => {
|
|
|
124602
124651
|
});
|
|
124603
124652
|
return ctx.services.timebackAdmin.unenrollStudent(body2, ctx.user);
|
|
124604
124653
|
});
|
|
124654
|
+
reactivateEnrollment = requireGameManagementAccess(async (ctx) => {
|
|
124655
|
+
const body2 = await parseRequestBody(ctx.request, ReactivateEnrollmentRequestSchema);
|
|
124656
|
+
logger65.debug("Reactivating enrollment", {
|
|
124657
|
+
requesterId: ctx.user.id,
|
|
124658
|
+
gameId: body2.gameId,
|
|
124659
|
+
courseId: body2.courseId,
|
|
124660
|
+
studentId: body2.studentId,
|
|
124661
|
+
enrollmentId: body2.enrollmentId
|
|
124662
|
+
});
|
|
124663
|
+
return ctx.services.timebackAdmin.reactivateEnrollment(body2, ctx.user);
|
|
124664
|
+
});
|
|
124605
124665
|
listAssessments = requireGameManagementAccess(async (ctx) => {
|
|
124606
124666
|
const { gameId, courseId } = ctx.params;
|
|
124607
124667
|
if (!gameId || !courseId) {
|
|
@@ -124747,10 +124807,10 @@ var init_timeback_controller = __esm(() => {
|
|
|
124747
124807
|
grantXp,
|
|
124748
124808
|
adjustTime,
|
|
124749
124809
|
adjustMastery,
|
|
124750
|
-
toggleCompletion,
|
|
124751
124810
|
searchStudents,
|
|
124752
124811
|
enrollStudent,
|
|
124753
124812
|
unenrollStudent,
|
|
124813
|
+
reactivateEnrollment,
|
|
124754
124814
|
listAssessments,
|
|
124755
124815
|
createAssessment,
|
|
124756
124816
|
deleteAssessment,
|
|
@@ -127584,7 +127644,7 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
|
127584
127644
|
// package.json
|
|
127585
127645
|
var package_default2 = {
|
|
127586
127646
|
name: "@playcademy/vite-plugin",
|
|
127587
|
-
version: "0.2.
|
|
127647
|
+
version: "0.2.30-beta.2",
|
|
127588
127648
|
type: "module",
|
|
127589
127649
|
exports: {
|
|
127590
127650
|
".": {
|