@playcademy/sandbox 0.3.17-beta.23 → 0.3.17-beta.25

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 (3) hide show
  1. package/dist/cli.js +84 -19
  2. package/dist/server.js +84 -19
  3. 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.23",
1333
+ version: "0.3.17-beta.25",
1334
1334
  description: "Local development server for Playcademy game development",
1335
1335
  type: "module",
1336
1336
  exports: {
@@ -30893,13 +30893,54 @@ class TimebackAdminService {
30893
30893
  const remediationItems = events.map((event) => mapCaliperEventToRemediationActivity(event, relevantCourseIds)).filter((item) => Boolean(item));
30894
30894
  return [...groupedGameplayItems, ...remediationItems].toSorted((a, b) => b.occurredAt.localeCompare(a.occurredAt));
30895
30895
  }
30896
- async getStudentEnrollmentsByCourseId(client, studentId, courseIds) {
30897
- const relevantCourseIds = new Set(courseIds);
30898
- const enrollments = await client.edubridge.enrollments.listByUser(studentId);
30899
- return new Map(enrollments.filter((enrollment) => relevantCourseIds.has(enrollment.course.id)).map((enrollment) => [enrollment.course.id, enrollment]));
30896
+ async getStudentEnrollmentsByCourseId(client, studentId, courseIds, options) {
30897
+ const enrollments = new Map;
30898
+ const entries = await Promise.all(courseIds.map(async (courseId) => {
30899
+ const roster = await client.oneroster.enrollments.listByCourse(courseId, {
30900
+ includeInactive: options?.includeInactive,
30901
+ includeUsers: false
30902
+ });
30903
+ const matches = roster.filter((entry) => entry.enrollment.user.sourcedId === studentId).toSorted((a, b) => {
30904
+ const aActive = a.enrollment.status === "active";
30905
+ const bActive = b.enrollment.status === "active";
30906
+ if (aActive !== bActive) {
30907
+ return aActive ? -1 : 1;
30908
+ }
30909
+ return (b.enrollment.dateLastModified ?? "").localeCompare(a.enrollment.dateLastModified ?? "");
30910
+ });
30911
+ return { courseId, match: matches[0] ?? null };
30912
+ }));
30913
+ for (const { courseId, match } of entries) {
30914
+ if (match) {
30915
+ enrollments.set(courseId, {
30916
+ id: match.enrollment.sourcedId,
30917
+ status: match.enrollment.status ?? "active",
30918
+ role: match.enrollment.role ?? "student",
30919
+ beginDate: match.enrollment.beginDate ?? null,
30920
+ endDate: match.enrollment.endDate ?? null,
30921
+ course: {
30922
+ id: courseId,
30923
+ title: match.class?.title ?? "",
30924
+ subjects: null,
30925
+ grades: null
30926
+ }
30927
+ });
30928
+ }
30929
+ }
30930
+ return { enrollments };
30900
30931
  }
30901
30932
  async assertStudentEnrolledInCourse(client, studentId, courseId) {
30902
- const enrollmentsByCourseId = await this.getStudentEnrollmentsByCourseId(client, studentId, [courseId]);
30933
+ const enrollments = await client.edubridge.enrollments.listByUser(studentId);
30934
+ const match = enrollments.find((e) => e.course.id === courseId);
30935
+ if (!match) {
30936
+ throw new NotFoundError("Student enrollment", `${studentId}:${courseId}`);
30937
+ }
30938
+ return match;
30939
+ }
30940
+ async assertStudentHasEnrollmentInCourse(client, studentId, courseId) {
30941
+ const { enrollments: enrollmentsByCourseId } = await this.getStudentEnrollmentsByCourseId(client, studentId, [courseId], {
30942
+ includeInactive: true
30943
+ });
30903
30944
  if (!enrollmentsByCourseId.has(courseId)) {
30904
30945
  throw new NotFoundError("Student enrollment", `${studentId}:${courseId}`);
30905
30946
  }
@@ -30952,7 +30993,7 @@ class TimebackAdminService {
30952
30993
  return [];
30953
30994
  }
30954
30995
  }
30955
- async listStudentsForCourse(gameId, courseId, user) {
30996
+ async listStudentsForCourse(gameId, courseId, user, options) {
30956
30997
  const client = this.requireClient();
30957
30998
  await this.deps.validateGameManagementAccess(user, gameId);
30958
30999
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
@@ -30962,7 +31003,8 @@ class TimebackAdminService {
30962
31003
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
30963
31004
  }
30964
31005
  const roster = await client.oneroster.enrollments.listByCourse(courseId, {
30965
- role: "student"
31006
+ role: "student",
31007
+ includeInactive: options?.includeInactive
30966
31008
  });
30967
31009
  const analyticsByEnrollmentId = await this.loadEnrollmentAnalyticsSummaries(roster.map((rosterEntry) => rosterEntry.enrollment.sourcedId).filter((enrollmentId) => Boolean(enrollmentId)));
30968
31010
  const masterableUnits = await this.getMasterableUnits(courseId);
@@ -30971,6 +31013,7 @@ class TimebackAdminService {
30971
31013
  const summary = enrollmentId ? analyticsByEnrollmentId.get(enrollmentId) : undefined;
30972
31014
  const analyticsUnavailable = Boolean(enrollmentId) && summary?.analyticsAvailable !== true;
30973
31015
  const name3 = rosterEntry.user ? `${rosterEntry.user.givenName} ${rosterEntry.user.familyName}`.trim() : rosterEntry.enrollment.user.sourcedId;
31016
+ const inactive = rosterEntry.enrollment.status === "tobedeleted";
30974
31017
  return {
30975
31018
  studentId: rosterEntry.enrollment.user.sourcedId,
30976
31019
  enrollmentId,
@@ -30984,11 +31027,25 @@ class TimebackAdminService {
30984
31027
  activeTimeSeconds: summary?.activeTimeSeconds ?? 0,
30985
31028
  masteredUnits: summary?.masteredUnits ?? 0,
30986
31029
  masterableUnits,
30987
- pctCompleteApp: TimebackAdminService.computeCompletionPct(summary?.masteredUnits ?? 0, masterableUnits)
31030
+ pctCompleteApp: TimebackAdminService.computeCompletionPct(summary?.masteredUnits ?? 0, masterableUnits),
31031
+ ...inactive ? { inactive } : {}
30988
31032
  };
30989
31033
  });
30990
- students.sort((a, b) => a.name.localeCompare(b.name));
30991
- return { gameId, courseId, students };
31034
+ const studentMap = new Map;
31035
+ for (const student of students) {
31036
+ const existing = studentMap.get(student.studentId);
31037
+ if (!existing || existing.inactive && !student.inactive) {
31038
+ studentMap.set(student.studentId, student);
31039
+ }
31040
+ }
31041
+ const deduped = [...studentMap.values()];
31042
+ deduped.sort((a, b) => {
31043
+ if (a.inactive !== b.inactive) {
31044
+ return a.inactive ? 1 : -1;
31045
+ }
31046
+ return a.name.localeCompare(b.name);
31047
+ });
31048
+ return { gameId, courseId, students: deduped };
30992
31049
  }
30993
31050
  async getStudentOverview(gameId, studentId, user, courseId) {
30994
31051
  const client = this.requireClient();
@@ -31000,7 +31057,9 @@ class TimebackAdminService {
31000
31057
  throw new NotFoundError("Timeback integration", gameId);
31001
31058
  }
31002
31059
  const courseIds = new Set(integrations.map((integration) => integration.courseId));
31003
- const enrollmentsByCourseId = await this.getStudentEnrollmentsByCourseId(client, studentId, [...courseIds]);
31060
+ const { enrollments: enrollmentsByCourseId } = await this.getStudentEnrollmentsByCourseId(client, studentId, [...courseIds], {
31061
+ includeInactive: true
31062
+ });
31004
31063
  if (enrollmentsByCourseId.size === 0) {
31005
31064
  throw new NotFoundError("Student enrollment", courseId ? `${studentId}:${courseId}` : `${studentId}:${gameId}`);
31006
31065
  }
@@ -31015,6 +31074,7 @@ class TimebackAdminService {
31015
31074
  const summary = enrollment ? analyticsByEnrollmentId.get(enrollment.id) : undefined;
31016
31075
  const masterableUnits = masterableUnitsByCourse.get(integration.courseId);
31017
31076
  const analyticsUnavailable = Boolean(enrollment?.id) && summary?.analyticsAvailable !== true;
31077
+ const inactive = enrollment?.status === "tobedeleted";
31018
31078
  return {
31019
31079
  courseId: integration.courseId,
31020
31080
  title: enrollment?.course.title || `${integration.subject} Grade ${integration.grade}`,
@@ -31029,7 +31089,8 @@ class TimebackAdminService {
31029
31089
  masterableUnits,
31030
31090
  pctCompleteApp: TimebackAdminService.computeCompletionPct(summary?.masteredUnits ?? 0, masterableUnits),
31031
31091
  completionStatus: completionStatusByCourse.get(integration.courseId) ?? "none",
31032
- history: summary?.history ?? []
31092
+ history: summary?.history ?? [],
31093
+ ...inactive ? { inactive } : {}
31033
31094
  };
31034
31095
  });
31035
31096
  return {
@@ -31056,7 +31117,7 @@ class TimebackAdminService {
31056
31117
  if (!integration) {
31057
31118
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
31058
31119
  }
31059
- await this.assertStudentEnrolledInCourse(client, studentId, courseId);
31120
+ await this.assertStudentHasEnrollmentInCourse(client, studentId, courseId);
31060
31121
  const relevantCourseIds = new Set([courseId]);
31061
31122
  const fetchLimit = Math.min(safeOffset + safeLimit + 1, TimebackAdminService.MAX_STUDENT_ACTIVITY_OFFSET + TimebackAdminService.MAX_STUDENT_ACTIVITY_LIMIT + 1);
31062
31123
  const allActivities = await this.listRecentActivityForStudent(client, studentId, gameSource, relevantCourseIds, fetchLimit);
@@ -31077,7 +31138,7 @@ class TimebackAdminService {
31077
31138
  if (!integration) {
31078
31139
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
31079
31140
  }
31080
- await this.assertStudentEnrolledInCourse(client, studentId, courseId);
31141
+ await this.assertStudentHasEnrollmentInCourse(client, studentId, courseId);
31081
31142
  const events = await this.fetchCaliperEventsForStudent(client, studentId, gameSource, TimebackAdminService.MAX_RECENT_ACTIVITY_EVENT_FETCH);
31082
31143
  const relevantCourseIds = new Set([courseId]);
31083
31144
  let matchedEvents;
@@ -35684,7 +35745,7 @@ function createOneRosterNamespace(client) {
35684
35745
  listByClass: async (classSourcedId, options) => {
35685
35746
  const queryParams = new URLSearchParams;
35686
35747
  const filters = [`class.sourcedId='${escapeFilterValue2(classSourcedId)}'`];
35687
- if (options?.onlyActive ?? true) {
35748
+ if (!options?.includeInactive) {
35688
35749
  filters.push(`status='active'`);
35689
35750
  }
35690
35751
  queryParams.set("filter", filters.join(" AND "));
@@ -35719,7 +35780,7 @@ function createOneRosterNamespace(client) {
35719
35780
  const queryParams = new URLSearchParams;
35720
35781
  const classFilter = batch.map((classId) => `class.sourcedId='${escapeFilterValue2(classId)}'`).join(" OR ");
35721
35782
  const filters = [batch.length > 1 ? `(${classFilter})` : classFilter];
35722
- if (options?.onlyActive ?? true) {
35783
+ if (!options?.includeInactive) {
35723
35784
  filters.push(`status='active'`);
35724
35785
  }
35725
35786
  if (options?.role) {
@@ -96846,15 +96907,19 @@ var init_timeback_controller = __esm(() => {
96846
96907
  getRoster = requireGameManagementAccess(async (ctx) => {
96847
96908
  const gameId = ctx.params.gameId;
96848
96909
  const courseId = ctx.params.courseId;
96910
+ const includeInactive = ctx.url.searchParams.get("includeInactive") === "true";
96849
96911
  if (!gameId || !courseId) {
96850
96912
  throw ApiError.badRequest("Missing gameId or courseId parameter");
96851
96913
  }
96852
96914
  logger64.debug("Getting course roster", {
96853
96915
  requesterId: ctx.user.id,
96854
96916
  gameId,
96855
- courseId
96917
+ courseId,
96918
+ includeInactive
96919
+ });
96920
+ return ctx.services.timebackAdmin.listStudentsForCourse(gameId, courseId, ctx.user, {
96921
+ includeInactive
96856
96922
  });
96857
- return ctx.services.timebackAdmin.listStudentsForCourse(gameId, courseId, ctx.user);
96858
96923
  });
96859
96924
  getStudentOverview = requireGameManagementAccess(async (ctx) => {
96860
96925
  const timebackId = ctx.params.timebackId;
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.23",
1332
+ version: "0.3.17-beta.25",
1333
1333
  description: "Local development server for Playcademy game development",
1334
1334
  type: "module",
1335
1335
  exports: {
@@ -30892,13 +30892,54 @@ class TimebackAdminService {
30892
30892
  const remediationItems = events.map((event) => mapCaliperEventToRemediationActivity(event, relevantCourseIds)).filter((item) => Boolean(item));
30893
30893
  return [...groupedGameplayItems, ...remediationItems].toSorted((a, b) => b.occurredAt.localeCompare(a.occurredAt));
30894
30894
  }
30895
- async getStudentEnrollmentsByCourseId(client, studentId, courseIds) {
30896
- const relevantCourseIds = new Set(courseIds);
30897
- const enrollments = await client.edubridge.enrollments.listByUser(studentId);
30898
- return new Map(enrollments.filter((enrollment) => relevantCourseIds.has(enrollment.course.id)).map((enrollment) => [enrollment.course.id, enrollment]));
30895
+ async getStudentEnrollmentsByCourseId(client, studentId, courseIds, options) {
30896
+ const enrollments = new Map;
30897
+ const entries = await Promise.all(courseIds.map(async (courseId) => {
30898
+ const roster = await client.oneroster.enrollments.listByCourse(courseId, {
30899
+ includeInactive: options?.includeInactive,
30900
+ includeUsers: false
30901
+ });
30902
+ const matches = roster.filter((entry) => entry.enrollment.user.sourcedId === studentId).toSorted((a, b) => {
30903
+ const aActive = a.enrollment.status === "active";
30904
+ const bActive = b.enrollment.status === "active";
30905
+ if (aActive !== bActive) {
30906
+ return aActive ? -1 : 1;
30907
+ }
30908
+ return (b.enrollment.dateLastModified ?? "").localeCompare(a.enrollment.dateLastModified ?? "");
30909
+ });
30910
+ return { courseId, match: matches[0] ?? null };
30911
+ }));
30912
+ for (const { courseId, match } of entries) {
30913
+ if (match) {
30914
+ enrollments.set(courseId, {
30915
+ id: match.enrollment.sourcedId,
30916
+ status: match.enrollment.status ?? "active",
30917
+ role: match.enrollment.role ?? "student",
30918
+ beginDate: match.enrollment.beginDate ?? null,
30919
+ endDate: match.enrollment.endDate ?? null,
30920
+ course: {
30921
+ id: courseId,
30922
+ title: match.class?.title ?? "",
30923
+ subjects: null,
30924
+ grades: null
30925
+ }
30926
+ });
30927
+ }
30928
+ }
30929
+ return { enrollments };
30899
30930
  }
30900
30931
  async assertStudentEnrolledInCourse(client, studentId, courseId) {
30901
- const enrollmentsByCourseId = await this.getStudentEnrollmentsByCourseId(client, studentId, [courseId]);
30932
+ const enrollments = await client.edubridge.enrollments.listByUser(studentId);
30933
+ const match = enrollments.find((e) => e.course.id === courseId);
30934
+ if (!match) {
30935
+ throw new NotFoundError("Student enrollment", `${studentId}:${courseId}`);
30936
+ }
30937
+ return match;
30938
+ }
30939
+ async assertStudentHasEnrollmentInCourse(client, studentId, courseId) {
30940
+ const { enrollments: enrollmentsByCourseId } = await this.getStudentEnrollmentsByCourseId(client, studentId, [courseId], {
30941
+ includeInactive: true
30942
+ });
30902
30943
  if (!enrollmentsByCourseId.has(courseId)) {
30903
30944
  throw new NotFoundError("Student enrollment", `${studentId}:${courseId}`);
30904
30945
  }
@@ -30951,7 +30992,7 @@ class TimebackAdminService {
30951
30992
  return [];
30952
30993
  }
30953
30994
  }
30954
- async listStudentsForCourse(gameId, courseId, user) {
30995
+ async listStudentsForCourse(gameId, courseId, user, options) {
30955
30996
  const client = this.requireClient();
30956
30997
  await this.deps.validateGameManagementAccess(user, gameId);
30957
30998
  const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
@@ -30961,7 +31002,8 @@ class TimebackAdminService {
30961
31002
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
30962
31003
  }
30963
31004
  const roster = await client.oneroster.enrollments.listByCourse(courseId, {
30964
- role: "student"
31005
+ role: "student",
31006
+ includeInactive: options?.includeInactive
30965
31007
  });
30966
31008
  const analyticsByEnrollmentId = await this.loadEnrollmentAnalyticsSummaries(roster.map((rosterEntry) => rosterEntry.enrollment.sourcedId).filter((enrollmentId) => Boolean(enrollmentId)));
30967
31009
  const masterableUnits = await this.getMasterableUnits(courseId);
@@ -30970,6 +31012,7 @@ class TimebackAdminService {
30970
31012
  const summary = enrollmentId ? analyticsByEnrollmentId.get(enrollmentId) : undefined;
30971
31013
  const analyticsUnavailable = Boolean(enrollmentId) && summary?.analyticsAvailable !== true;
30972
31014
  const name3 = rosterEntry.user ? `${rosterEntry.user.givenName} ${rosterEntry.user.familyName}`.trim() : rosterEntry.enrollment.user.sourcedId;
31015
+ const inactive = rosterEntry.enrollment.status === "tobedeleted";
30973
31016
  return {
30974
31017
  studentId: rosterEntry.enrollment.user.sourcedId,
30975
31018
  enrollmentId,
@@ -30983,11 +31026,25 @@ class TimebackAdminService {
30983
31026
  activeTimeSeconds: summary?.activeTimeSeconds ?? 0,
30984
31027
  masteredUnits: summary?.masteredUnits ?? 0,
30985
31028
  masterableUnits,
30986
- pctCompleteApp: TimebackAdminService.computeCompletionPct(summary?.masteredUnits ?? 0, masterableUnits)
31029
+ pctCompleteApp: TimebackAdminService.computeCompletionPct(summary?.masteredUnits ?? 0, masterableUnits),
31030
+ ...inactive ? { inactive } : {}
30987
31031
  };
30988
31032
  });
30989
- students.sort((a, b) => a.name.localeCompare(b.name));
30990
- return { gameId, courseId, students };
31033
+ const studentMap = new Map;
31034
+ for (const student of students) {
31035
+ const existing = studentMap.get(student.studentId);
31036
+ if (!existing || existing.inactive && !student.inactive) {
31037
+ studentMap.set(student.studentId, student);
31038
+ }
31039
+ }
31040
+ const deduped = [...studentMap.values()];
31041
+ deduped.sort((a, b) => {
31042
+ if (a.inactive !== b.inactive) {
31043
+ return a.inactive ? 1 : -1;
31044
+ }
31045
+ return a.name.localeCompare(b.name);
31046
+ });
31047
+ return { gameId, courseId, students: deduped };
30991
31048
  }
30992
31049
  async getStudentOverview(gameId, studentId, user, courseId) {
30993
31050
  const client = this.requireClient();
@@ -30999,7 +31056,9 @@ class TimebackAdminService {
30999
31056
  throw new NotFoundError("Timeback integration", gameId);
31000
31057
  }
31001
31058
  const courseIds = new Set(integrations.map((integration) => integration.courseId));
31002
- const enrollmentsByCourseId = await this.getStudentEnrollmentsByCourseId(client, studentId, [...courseIds]);
31059
+ const { enrollments: enrollmentsByCourseId } = await this.getStudentEnrollmentsByCourseId(client, studentId, [...courseIds], {
31060
+ includeInactive: true
31061
+ });
31003
31062
  if (enrollmentsByCourseId.size === 0) {
31004
31063
  throw new NotFoundError("Student enrollment", courseId ? `${studentId}:${courseId}` : `${studentId}:${gameId}`);
31005
31064
  }
@@ -31014,6 +31073,7 @@ class TimebackAdminService {
31014
31073
  const summary = enrollment ? analyticsByEnrollmentId.get(enrollment.id) : undefined;
31015
31074
  const masterableUnits = masterableUnitsByCourse.get(integration.courseId);
31016
31075
  const analyticsUnavailable = Boolean(enrollment?.id) && summary?.analyticsAvailable !== true;
31076
+ const inactive = enrollment?.status === "tobedeleted";
31017
31077
  return {
31018
31078
  courseId: integration.courseId,
31019
31079
  title: enrollment?.course.title || `${integration.subject} Grade ${integration.grade}`,
@@ -31028,7 +31088,8 @@ class TimebackAdminService {
31028
31088
  masterableUnits,
31029
31089
  pctCompleteApp: TimebackAdminService.computeCompletionPct(summary?.masteredUnits ?? 0, masterableUnits),
31030
31090
  completionStatus: completionStatusByCourse.get(integration.courseId) ?? "none",
31031
- history: summary?.history ?? []
31091
+ history: summary?.history ?? [],
31092
+ ...inactive ? { inactive } : {}
31032
31093
  };
31033
31094
  });
31034
31095
  return {
@@ -31055,7 +31116,7 @@ class TimebackAdminService {
31055
31116
  if (!integration) {
31056
31117
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
31057
31118
  }
31058
- await this.assertStudentEnrolledInCourse(client, studentId, courseId);
31119
+ await this.assertStudentHasEnrollmentInCourse(client, studentId, courseId);
31059
31120
  const relevantCourseIds = new Set([courseId]);
31060
31121
  const fetchLimit = Math.min(safeOffset + safeLimit + 1, TimebackAdminService.MAX_STUDENT_ACTIVITY_OFFSET + TimebackAdminService.MAX_STUDENT_ACTIVITY_LIMIT + 1);
31061
31122
  const allActivities = await this.listRecentActivityForStudent(client, studentId, gameSource, relevantCourseIds, fetchLimit);
@@ -31076,7 +31137,7 @@ class TimebackAdminService {
31076
31137
  if (!integration) {
31077
31138
  throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
31078
31139
  }
31079
- await this.assertStudentEnrolledInCourse(client, studentId, courseId);
31140
+ await this.assertStudentHasEnrollmentInCourse(client, studentId, courseId);
31080
31141
  const events = await this.fetchCaliperEventsForStudent(client, studentId, gameSource, TimebackAdminService.MAX_RECENT_ACTIVITY_EVENT_FETCH);
31081
31142
  const relevantCourseIds = new Set([courseId]);
31082
31143
  let matchedEvents;
@@ -35683,7 +35744,7 @@ function createOneRosterNamespace(client) {
35683
35744
  listByClass: async (classSourcedId, options) => {
35684
35745
  const queryParams = new URLSearchParams;
35685
35746
  const filters = [`class.sourcedId='${escapeFilterValue2(classSourcedId)}'`];
35686
- if (options?.onlyActive ?? true) {
35747
+ if (!options?.includeInactive) {
35687
35748
  filters.push(`status='active'`);
35688
35749
  }
35689
35750
  queryParams.set("filter", filters.join(" AND "));
@@ -35718,7 +35779,7 @@ function createOneRosterNamespace(client) {
35718
35779
  const queryParams = new URLSearchParams;
35719
35780
  const classFilter = batch.map((classId) => `class.sourcedId='${escapeFilterValue2(classId)}'`).join(" OR ");
35720
35781
  const filters = [batch.length > 1 ? `(${classFilter})` : classFilter];
35721
- if (options?.onlyActive ?? true) {
35782
+ if (!options?.includeInactive) {
35722
35783
  filters.push(`status='active'`);
35723
35784
  }
35724
35785
  if (options?.role) {
@@ -96845,15 +96906,19 @@ var init_timeback_controller = __esm(() => {
96845
96906
  getRoster = requireGameManagementAccess(async (ctx) => {
96846
96907
  const gameId = ctx.params.gameId;
96847
96908
  const courseId = ctx.params.courseId;
96909
+ const includeInactive = ctx.url.searchParams.get("includeInactive") === "true";
96848
96910
  if (!gameId || !courseId) {
96849
96911
  throw ApiError.badRequest("Missing gameId or courseId parameter");
96850
96912
  }
96851
96913
  logger64.debug("Getting course roster", {
96852
96914
  requesterId: ctx.user.id,
96853
96915
  gameId,
96854
- courseId
96916
+ courseId,
96917
+ includeInactive
96918
+ });
96919
+ return ctx.services.timebackAdmin.listStudentsForCourse(gameId, courseId, ctx.user, {
96920
+ includeInactive
96855
96921
  });
96856
- return ctx.services.timebackAdmin.listStudentsForCourse(gameId, courseId, ctx.user);
96857
96922
  });
96858
96923
  getStudentOverview = requireGameManagementAccess(async (ctx) => {
96859
96924
  const timebackId = ctx.params.timebackId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@playcademy/sandbox",
3
- "version": "0.3.17-beta.23",
3
+ "version": "0.3.17-beta.25",
4
4
  "description": "Local development server for Playcademy game development",
5
5
  "type": "module",
6
6
  "exports": {