@playcademy/vite-plugin 0.2.29-beta.6 → 0.2.30-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.
Files changed (2) hide show
  1. package/dist/index.js +150 -32
  2. 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.16",
25373
+ version: "0.3.17-beta.34",
25374
25374
  description: "Local development server for Playcademy game development",
25375
25375
  type: "module",
25376
25376
  exports: {
@@ -54340,6 +54340,13 @@ function resolveAdminEventTime(data) {
54340
54340
  }
54341
54341
  return toAttributionEventTime(data.date);
54342
54342
  }
54343
+ function compareEnrollmentsByRecency(a, b) {
54344
+ const dateCompare = (b.beginDate ?? "").localeCompare(a.beginDate ?? "");
54345
+ if (dateCompare !== 0) {
54346
+ return dateCompare;
54347
+ }
54348
+ return (b.dateLastModified ?? "").localeCompare(a.dateLastModified ?? "");
54349
+ }
54343
54350
  var init_timeback_admin_util = __esm(() => {
54344
54351
  init_errors();
54345
54352
  });
@@ -54851,6 +54858,7 @@ class TimebackAdminService {
54851
54858
  }
54852
54859
  async getStudentEnrollmentsByCourseId(client, studentId, courseIds, options) {
54853
54860
  const enrollments = new Map;
54861
+ const allEnrollments = new Map;
54854
54862
  const entries = await Promise.all(courseIds.map(async (courseId) => {
54855
54863
  const roster = await client.oneroster.enrollments.listByCourse(courseId, {
54856
54864
  includeInactive: options?.includeInactive,
@@ -54862,28 +54870,30 @@ class TimebackAdminService {
54862
54870
  if (aActive !== bActive) {
54863
54871
  return aActive ? -1 : 1;
54864
54872
  }
54865
- return (b.enrollment.dateLastModified ?? "").localeCompare(a.enrollment.dateLastModified ?? "");
54873
+ return compareEnrollmentsByRecency(a.enrollment, b.enrollment);
54866
54874
  });
54867
- return { courseId, match: matches[0] ?? null };
54875
+ return { courseId, matches };
54868
54876
  }));
54869
- for (const { courseId, match } of entries) {
54870
- if (match) {
54871
- enrollments.set(courseId, {
54872
- id: match.enrollment.sourcedId,
54873
- status: match.enrollment.status ?? "active",
54874
- role: match.enrollment.role ?? "student",
54875
- beginDate: match.enrollment.beginDate ?? null,
54876
- endDate: match.enrollment.endDate ?? null,
54877
- course: {
54878
- id: courseId,
54879
- title: match.class?.title ?? "",
54880
- subjects: null,
54881
- grades: null
54882
- }
54883
- });
54877
+ for (const { courseId, matches } of entries) {
54878
+ const records = matches.map((match) => ({
54879
+ id: match.enrollment.sourcedId,
54880
+ status: match.enrollment.status ?? "active",
54881
+ role: match.enrollment.role ?? "student",
54882
+ beginDate: match.enrollment.beginDate ?? null,
54883
+ endDate: match.enrollment.endDate ?? null,
54884
+ course: {
54885
+ id: courseId,
54886
+ title: match.class?.title ?? "",
54887
+ subjects: null,
54888
+ grades: null
54889
+ }
54890
+ }));
54891
+ if (records.length > 0) {
54892
+ enrollments.set(courseId, records[0]);
54884
54893
  }
54894
+ allEnrollments.set(courseId, records);
54885
54895
  }
54886
- return { enrollments };
54896
+ return { enrollments, allEnrollments };
54887
54897
  }
54888
54898
  async assertStudentEnrolledInCourse(client, studentId, courseId) {
54889
54899
  const enrollments = await client.edubridge.enrollments.listByUser(studentId);
@@ -54970,7 +54980,7 @@ class TimebackAdminService {
54970
54980
  const enrollmentId = rosterEntry.enrollment.sourcedId || null;
54971
54981
  const summary = enrollmentId ? analyticsByEnrollmentId.get(enrollmentId) : undefined;
54972
54982
  const analyticsUnavailable = Boolean(enrollmentId) && summary?.analyticsAvailable !== true;
54973
- const name3 = rosterEntry.user ? `${rosterEntry.user.givenName} ${rosterEntry.user.familyName}`.trim() : rosterEntry.enrollment.user.sourcedId;
54983
+ const name3 = rosterEntry.user ? `${rosterEntry.user.givenName} ${rosterEntry.user.familyName}`.trim() || "No name specified" : "No name specified";
54974
54984
  const inactive = rosterEntry.enrollment.status === "tobedeleted";
54975
54985
  return {
54976
54986
  studentId: rosterEntry.enrollment.user.sourcedId,
@@ -55015,14 +55025,15 @@ class TimebackAdminService {
55015
55025
  throw new NotFoundError("Timeback integration", gameId);
55016
55026
  }
55017
55027
  const courseIds = new Set(integrations.map((integration) => integration.courseId));
55018
- const { enrollments: enrollmentsByCourseId } = await this.getStudentEnrollmentsByCourseId(client, studentId, [...courseIds], {
55028
+ const { enrollments: enrollmentsByCourseId, allEnrollments: allEnrollmentsByCourseId } = await this.getStudentEnrollmentsByCourseId(client, studentId, [...courseIds], {
55019
55029
  includeInactive: true
55020
55030
  });
55021
55031
  if (enrollmentsByCourseId.size === 0) {
55022
55032
  throw new NotFoundError("Student enrollment", courseId ? `${studentId}:${courseId}` : `${studentId}:${gameId}`);
55023
55033
  }
55034
+ const allEnrollmentIds = [...allEnrollmentsByCourseId.values()].flat().map((enrollment) => enrollment.id);
55024
55035
  const studentProfile = await client.oneroster.users.get(studentId);
55025
- const analyticsByEnrollmentId = await this.loadEnrollmentAnalyticsSummaries([...enrollmentsByCourseId.values()].map((enrollment) => enrollment.id));
55036
+ const analyticsByEnrollmentId = await this.loadEnrollmentAnalyticsSummaries(allEnrollmentIds);
55026
55037
  const [masterableUnitsByCourse, completionStatusByCourse] = await Promise.all([
55027
55038
  this.getMasterableUnitsByCourse(integrations.map((integration) => integration.courseId)),
55028
55039
  this.getCompletionStatusByCourse(client, integrations.map((integration) => integration.courseId), studentId)
@@ -55033,6 +55044,24 @@ class TimebackAdminService {
55033
55044
  const masterableUnits = masterableUnitsByCourse.get(integration.courseId);
55034
55045
  const analyticsUnavailable = Boolean(enrollment?.id) && summary?.analyticsAvailable !== true;
55035
55046
  const inactive = enrollment?.status === "tobedeleted";
55047
+ const courseEnrollments = allEnrollmentsByCourseId.get(integration.courseId) ?? [];
55048
+ const enrollmentSummaries = courseEnrollments.length > 1 ? courseEnrollments.map((record) => {
55049
+ const recordSummary = analyticsByEnrollmentId.get(record.id);
55050
+ const recordAnalyticsUnavailable = recordSummary?.analyticsAvailable !== true;
55051
+ return {
55052
+ enrollmentId: record.id,
55053
+ status: record.status === "tobedeleted" ? "tobedeleted" : "active",
55054
+ beginDate: record.beginDate,
55055
+ endDate: record.endDate,
55056
+ analyticsUnavailable: recordAnalyticsUnavailable,
55057
+ totalXp: recordSummary?.totalXp ?? 0,
55058
+ todayXp: recordSummary?.todayXp ?? 0,
55059
+ activeTimeSeconds: recordSummary?.activeTimeSeconds ?? 0,
55060
+ masteredUnits: recordSummary?.masteredUnits ?? 0,
55061
+ pctCompleteApp: TimebackAdminService.computeCompletionPct(recordSummary?.masteredUnits ?? 0, masterableUnits),
55062
+ history: recordSummary?.history ?? []
55063
+ };
55064
+ }) : undefined;
55036
55065
  return {
55037
55066
  courseId: integration.courseId,
55038
55067
  title: enrollment?.course.title || `${integration.subject} Grade ${integration.grade}`,
@@ -55048,7 +55077,8 @@ class TimebackAdminService {
55048
55077
  pctCompleteApp: TimebackAdminService.computeCompletionPct(summary?.masteredUnits ?? 0, masterableUnits),
55049
55078
  completionStatus: completionStatusByCourse.get(integration.courseId) ?? "none",
55050
55079
  history: summary?.history ?? [],
55051
- ...inactive ? { inactive } : {}
55080
+ ...inactive ? { inactive } : {},
55081
+ ...enrollmentSummaries ? { enrollments: enrollmentSummaries } : {}
55052
55082
  };
55053
55083
  });
55054
55084
  return {
@@ -55290,17 +55320,34 @@ class TimebackAdminService {
55290
55320
  });
55291
55321
  return { students: [] };
55292
55322
  }
55293
- const roster = await client.oneroster.enrollments.listByCourse(courseId, {
55323
+ const fullRoster = await client.oneroster.enrollments.listByCourse(courseId, {
55294
55324
  role: "student",
55325
+ includeInactive: true,
55295
55326
  includeUsers: false
55296
55327
  });
55297
- const enrolledStudentIds = new Set(roster.map((entry) => entry.enrollment.user.sourcedId));
55298
- const students = allUsers.filter((entry) => Boolean(entry.sourcedId) && entry.roles?.some((role) => role.role === "student") === true).map((entry) => ({
55299
- studentId: entry.sourcedId,
55300
- name: `${entry.givenName || ""} ${entry.familyName || ""}`.trim() || entry.sourcedId,
55301
- email: entry.email || null,
55302
- alreadyEnrolled: enrolledStudentIds.has(entry.sourcedId)
55303
- }));
55328
+ const enrolledStudentIds = new Set(fullRoster.filter((entry) => entry.enrollment.status === "active").map((entry) => entry.enrollment.user.sourcedId));
55329
+ const pastEnrollmentsByStudent = new Map;
55330
+ const inactiveEntries = fullRoster.filter((entry) => entry.enrollment.status === "tobedeleted").toSorted((a, b) => compareEnrollmentsByRecency(a.enrollment, b.enrollment));
55331
+ for (const entry of inactiveEntries) {
55332
+ const studentId = entry.enrollment.user.sourcedId;
55333
+ const list = pastEnrollmentsByStudent.get(studentId) ?? [];
55334
+ list.push({
55335
+ enrollmentId: entry.enrollment.sourcedId,
55336
+ beginDate: entry.enrollment.beginDate ?? null,
55337
+ endDate: entry.enrollment.endDate ?? null
55338
+ });
55339
+ pastEnrollmentsByStudent.set(studentId, list);
55340
+ }
55341
+ const students = allUsers.filter((entry) => Boolean(entry.sourcedId) && entry.roles?.some((role) => role.role === "student") === true).map((entry) => {
55342
+ const past = pastEnrollmentsByStudent.get(entry.sourcedId) ?? [];
55343
+ return {
55344
+ studentId: entry.sourcedId,
55345
+ name: `${entry.givenName || ""} ${entry.familyName || ""}`.trim() || "No name specified",
55346
+ email: entry.email || null,
55347
+ alreadyEnrolled: enrolledStudentIds.has(entry.sourcedId),
55348
+ ...past.length > 0 ? { pastEnrollments: past } : {}
55349
+ };
55350
+ });
55304
55351
  return { students };
55305
55352
  }
55306
55353
  async enrollStudent(data, user) {
@@ -55331,6 +55378,48 @@ class TimebackAdminService {
55331
55378
  client.invalidateEnrollments(data.studentId);
55332
55379
  return { status: "ok" };
55333
55380
  }
55381
+ async reactivateEnrollment(data, user) {
55382
+ const client = this.requireClient();
55383
+ await this.deps.validateGameManagementAccess(user, data.gameId);
55384
+ const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
55385
+ where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId))
55386
+ });
55387
+ if (!integration) {
55388
+ throw new NotFoundError("Timeback integration", `${data.gameId}:${data.courseId}`);
55389
+ }
55390
+ const enrollment = await client.oneroster.enrollments.get(data.enrollmentId);
55391
+ if (!enrollment) {
55392
+ throw new NotFoundError("Enrollment", data.enrollmentId);
55393
+ }
55394
+ if (enrollment.user.sourcedId !== data.studentId) {
55395
+ throw new ValidationError("Enrollment does not belong to the specified student");
55396
+ }
55397
+ if (enrollment.status === "active") {
55398
+ throw new ValidationError("Enrollment is already active");
55399
+ }
55400
+ const { allEnrollments } = await this.getStudentEnrollmentsByCourseId(client, data.studentId, [data.courseId], { includeInactive: true });
55401
+ const courseEnrollmentIds = new Set((allEnrollments.get(data.courseId) ?? []).map((e) => e.id));
55402
+ if (!courseEnrollmentIds.has(data.enrollmentId)) {
55403
+ throw new ValidationError("Enrollment does not belong to the specified course");
55404
+ }
55405
+ const activeEnrollments = await client.edubridge.enrollments.listByUser(data.studentId);
55406
+ if (activeEnrollments.some((e) => e.course.id === data.courseId)) {
55407
+ throw new ValidationError("Student already has an active enrollment for this course. Unenroll from the current enrollment before reactivating a past one.");
55408
+ }
55409
+ await client.oneroster.enrollments.update(data.enrollmentId, {
55410
+ role: enrollment.role,
55411
+ primary: enrollment.primary,
55412
+ beginDate: enrollment.beginDate,
55413
+ endDate: enrollment.endDate,
55414
+ user: enrollment.user,
55415
+ class: enrollment.class,
55416
+ school: enrollment.school,
55417
+ sourcedId: data.enrollmentId,
55418
+ status: "active"
55419
+ });
55420
+ client.invalidateEnrollments(data.studentId);
55421
+ return { status: "ok" };
55422
+ }
55334
55423
  async getCompletionStatus(client, courseId, studentId) {
55335
55424
  const ids = deriveSourcedIds(courseId);
55336
55425
  const lineItemId = `${ids.course}-mastery-completion-assessment`;
@@ -60403,6 +60492,10 @@ function createOneRosterNamespace(client) {
60403
60492
  }
60404
60493
  },
60405
60494
  enrollments: {
60495
+ get: async (sourcedId) => {
60496
+ const response = await client["request"](`${ONEROSTER_ENDPOINTS5.enrollments}/${sourcedId}`, "GET");
60497
+ return response.enrollment;
60498
+ },
60406
60499
  listByClass: async (classSourcedId, options) => {
60407
60500
  const queryParams = new URLSearchParams;
60408
60501
  const filters = [`class.sourcedId='${escapeFilterValue2(classSourcedId)}'`];
@@ -60480,6 +60573,11 @@ function createOneRosterNamespace(client) {
60480
60573
  }
60481
60574
  },
60482
60575
  create: async (data) => client["request"](ONEROSTER_ENDPOINTS5.enrollments, "POST", { enrollment: data }),
60576
+ update: async (sourcedId, data) => {
60577
+ await client["request"](`${ONEROSTER_ENDPOINTS5.enrollments}/${sourcedId}`, "PUT", {
60578
+ enrollment: data
60579
+ });
60580
+ },
60483
60581
  delete: async (sourcedId) => {
60484
60582
  await client["request"](`${ONEROSTER_ENDPOINTS5.enrollments}/${sourcedId}`, "DELETE");
60485
60583
  }
@@ -121699,6 +121797,7 @@ var AdjustTimebackMasteryRequestSchema;
121699
121797
  var ToggleCourseCompletionRequestSchema;
121700
121798
  var EnrollStudentRequestSchema;
121701
121799
  var UnenrollStudentRequestSchema;
121800
+ var ReactivateEnrollmentRequestSchema;
121702
121801
  var InsertAssessmentTestSchema;
121703
121802
  var CreateAssessmentRequestSchema;
121704
121803
  var ReorderAssessmentsRequestSchema;
@@ -121889,6 +121988,12 @@ var init_schemas11 = __esm(() => {
121889
121988
  courseId: exports_external.string().min(1),
121890
121989
  studentId: exports_external.string().min(1)
121891
121990
  });
121991
+ ReactivateEnrollmentRequestSchema = exports_external.object({
121992
+ gameId: exports_external.string().uuid(),
121993
+ courseId: exports_external.string().min(1),
121994
+ studentId: exports_external.string().min(1),
121995
+ enrollmentId: exports_external.string().min(1)
121996
+ });
121892
121997
  InsertAssessmentTestSchema = createInsertSchema(gameTimebackAssessmentTests).omit({
121893
121998
  id: true,
121894
121999
  createdAt: true
@@ -124142,6 +124247,7 @@ var toggleCompletion;
124142
124247
  var searchStudents;
124143
124248
  var enrollStudent;
124144
124249
  var unenrollStudent;
124250
+ var reactivateEnrollment;
124145
124251
  var listAssessments;
124146
124252
  var createAssessment;
124147
124253
  var deleteAssessment;
@@ -124602,6 +124708,17 @@ var init_timeback_controller = __esm(() => {
124602
124708
  });
124603
124709
  return ctx.services.timebackAdmin.unenrollStudent(body2, ctx.user);
124604
124710
  });
124711
+ reactivateEnrollment = requireGameManagementAccess(async (ctx) => {
124712
+ const body2 = await parseRequestBody(ctx.request, ReactivateEnrollmentRequestSchema);
124713
+ logger65.debug("Reactivating enrollment", {
124714
+ requesterId: ctx.user.id,
124715
+ gameId: body2.gameId,
124716
+ courseId: body2.courseId,
124717
+ studentId: body2.studentId,
124718
+ enrollmentId: body2.enrollmentId
124719
+ });
124720
+ return ctx.services.timebackAdmin.reactivateEnrollment(body2, ctx.user);
124721
+ });
124605
124722
  listAssessments = requireGameManagementAccess(async (ctx) => {
124606
124723
  const { gameId, courseId } = ctx.params;
124607
124724
  if (!gameId || !courseId) {
@@ -124751,6 +124868,7 @@ var init_timeback_controller = __esm(() => {
124751
124868
  searchStudents,
124752
124869
  enrollStudent,
124753
124870
  unenrollStudent,
124871
+ reactivateEnrollment,
124754
124872
  listAssessments,
124755
124873
  createAssessment,
124756
124874
  deleteAssessment,
@@ -127584,7 +127702,7 @@ var import_picocolors12 = __toESM(require_picocolors(), 1);
127584
127702
  // package.json
127585
127703
  var package_default2 = {
127586
127704
  name: "@playcademy/vite-plugin",
127587
- version: "0.2.29-beta.6",
127705
+ version: "0.2.30-beta.1",
127588
127706
  type: "module",
127589
127707
  exports: {
127590
127708
  ".": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/vite-plugin",
3
- "version": "0.2.29-beta.6",
3
+ "version": "0.2.30-beta.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {