@playcademy/sandbox 0.3.17-beta.5 → 0.3.17-beta.7
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 +210 -21
- package/dist/server.js +210 -21
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1310,7 +1310,7 @@ var package_default;
|
|
|
1310
1310
|
var init_package = __esm(() => {
|
|
1311
1311
|
package_default = {
|
|
1312
1312
|
name: "@playcademy/sandbox",
|
|
1313
|
-
version: "0.3.17-beta.
|
|
1313
|
+
version: "0.3.17-beta.7",
|
|
1314
1314
|
description: "Local development server for Playcademy game development",
|
|
1315
1315
|
type: "module",
|
|
1316
1316
|
exports: {
|
|
@@ -11550,7 +11550,7 @@ var init_table6 = __esm(() => {
|
|
|
11550
11550
|
init_drizzle_orm();
|
|
11551
11551
|
init_pg_core();
|
|
11552
11552
|
init_table5();
|
|
11553
|
-
userRoleEnum = pgEnum("user_role", ["admin", "player", "developer"]);
|
|
11553
|
+
userRoleEnum = pgEnum("user_role", ["admin", "player", "developer", "teacher"]);
|
|
11554
11554
|
developerStatusEnum = pgEnum("developer_status", ["none", "pending", "approved"]);
|
|
11555
11555
|
users = pgTable("user", {
|
|
11556
11556
|
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
@@ -26733,10 +26733,13 @@ var init_game_service = __esm(() => {
|
|
|
26733
26733
|
});
|
|
26734
26734
|
}
|
|
26735
26735
|
async listManageable(user) {
|
|
26736
|
-
|
|
26736
|
+
const seesAllGames = user.role === "admin" || user.role === "teacher";
|
|
26737
|
+
if (!seesAllGames) {
|
|
26738
|
+
this.validateDeveloperStatus(user);
|
|
26739
|
+
}
|
|
26737
26740
|
const db2 = this.deps.db;
|
|
26738
26741
|
return db2.query.games.findMany({
|
|
26739
|
-
where:
|
|
26742
|
+
where: seesAllGames ? undefined : eq(games.developerId, user.id),
|
|
26740
26743
|
orderBy: [desc(games.createdAt)]
|
|
26741
26744
|
});
|
|
26742
26745
|
}
|
|
@@ -27140,6 +27143,19 @@ var init_game_service = __esm(() => {
|
|
|
27140
27143
|
throw new NotFoundError("Game", gameId);
|
|
27141
27144
|
}
|
|
27142
27145
|
}
|
|
27146
|
+
async validateGameManagementAccess(user, gameId) {
|
|
27147
|
+
if (user.role === "admin" || user.role === "teacher") {
|
|
27148
|
+
const gameExists = await this.deps.db.query.games.findFirst({
|
|
27149
|
+
where: eq(games.id, gameId),
|
|
27150
|
+
columns: { id: true }
|
|
27151
|
+
});
|
|
27152
|
+
if (!gameExists) {
|
|
27153
|
+
throw new NotFoundError("Game", gameId);
|
|
27154
|
+
}
|
|
27155
|
+
return;
|
|
27156
|
+
}
|
|
27157
|
+
return this.validateDeveloperAccess(user, gameId);
|
|
27158
|
+
}
|
|
27143
27159
|
async validateDeveloperAccessBySlug(user, slug) {
|
|
27144
27160
|
this.validateDeveloperStatus(user);
|
|
27145
27161
|
const db2 = this.deps.db;
|
|
@@ -27210,6 +27226,7 @@ function createGameServices(deps) {
|
|
|
27210
27226
|
validators: {
|
|
27211
27227
|
validateDeveloperAccessBySlug: (user, slug) => game.validateDeveloperAccessBySlug(user, slug),
|
|
27212
27228
|
validateDeveloperAccess: (user, gameId) => game.validateDeveloperAccess(user, gameId),
|
|
27229
|
+
validateGameManagementAccess: (user, gameId) => game.validateGameManagementAccess(user, gameId),
|
|
27213
27230
|
validateOwnership: (user, gameId) => game.validateOwnership(user, gameId)
|
|
27214
27231
|
}
|
|
27215
27232
|
};
|
|
@@ -30612,9 +30629,13 @@ class TimebackAdminService {
|
|
|
30612
30629
|
});
|
|
30613
30630
|
});
|
|
30614
30631
|
}
|
|
30615
|
-
async resolveAdminMutationContext(gameId, courseId, user, studentId) {
|
|
30632
|
+
async resolveAdminMutationContext(gameId, courseId, user, studentId, accessLevel = "developer") {
|
|
30616
30633
|
const client = this.requireClient();
|
|
30617
|
-
|
|
30634
|
+
if (accessLevel === "dashboard") {
|
|
30635
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
30636
|
+
} else {
|
|
30637
|
+
await this.deps.validateDeveloperAccess(user, gameId);
|
|
30638
|
+
}
|
|
30618
30639
|
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
30619
30640
|
where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
|
|
30620
30641
|
});
|
|
@@ -30874,7 +30895,7 @@ class TimebackAdminService {
|
|
|
30874
30895
|
}
|
|
30875
30896
|
async listStudentsForCourse(gameId, courseId, user) {
|
|
30876
30897
|
const client = this.requireClient();
|
|
30877
|
-
await this.deps.
|
|
30898
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
30878
30899
|
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
30879
30900
|
where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
|
|
30880
30901
|
});
|
|
@@ -30912,7 +30933,7 @@ class TimebackAdminService {
|
|
|
30912
30933
|
}
|
|
30913
30934
|
async getStudentOverview(gameId, studentId, user, courseId) {
|
|
30914
30935
|
const client = this.requireClient();
|
|
30915
|
-
await this.deps.
|
|
30936
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
30916
30937
|
const integrations = await this.deps.db.query.gameTimebackIntegrations.findMany({
|
|
30917
30938
|
where: courseId ? and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId)) : eq(gameTimebackIntegrations.gameId, gameId)
|
|
30918
30939
|
});
|
|
@@ -30966,7 +30987,7 @@ class TimebackAdminService {
|
|
|
30966
30987
|
const client = this.requireClient();
|
|
30967
30988
|
const safeLimit = Math.max(1, Math.min(limit, TimebackAdminService.MAX_STUDENT_ACTIVITY_LIMIT));
|
|
30968
30989
|
const safeOffset = Math.max(0, Math.min(offset, TimebackAdminService.MAX_STUDENT_ACTIVITY_OFFSET));
|
|
30969
|
-
await this.deps.
|
|
30990
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
30970
30991
|
const [integration, sensorUrl] = await Promise.all([
|
|
30971
30992
|
this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
30972
30993
|
where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
|
|
@@ -31030,7 +31051,7 @@ class TimebackAdminService {
|
|
|
31030
31051
|
return { status: "ok" };
|
|
31031
31052
|
}
|
|
31032
31053
|
async toggleCourseCompletion(data, user) {
|
|
31033
|
-
const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user, data.studentId);
|
|
31054
|
+
const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user, data.studentId, "dashboard");
|
|
31034
31055
|
const historyClient = client;
|
|
31035
31056
|
const ids = deriveSourcedIds(data.courseId);
|
|
31036
31057
|
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
@@ -31123,6 +31144,77 @@ class TimebackAdminService {
|
|
|
31123
31144
|
}
|
|
31124
31145
|
return { status: "ok" };
|
|
31125
31146
|
}
|
|
31147
|
+
async searchStudentsForEnrollment(gameId, courseId, query, user) {
|
|
31148
|
+
const client = this.requireClient();
|
|
31149
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
31150
|
+
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
31151
|
+
where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
|
|
31152
|
+
});
|
|
31153
|
+
if (!integration) {
|
|
31154
|
+
throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
|
|
31155
|
+
}
|
|
31156
|
+
const trimmedQuery = query.trim();
|
|
31157
|
+
if (trimmedQuery.length < 2) {
|
|
31158
|
+
return { students: [] };
|
|
31159
|
+
}
|
|
31160
|
+
const filterParts = [
|
|
31161
|
+
`givenName~'${escapeFilterValue(trimmedQuery)}'`,
|
|
31162
|
+
`familyName~'${escapeFilterValue(trimmedQuery)}'`,
|
|
31163
|
+
`email~'${escapeFilterValue(trimmedQuery)}'`
|
|
31164
|
+
];
|
|
31165
|
+
const filter = filterParts.join(" OR ");
|
|
31166
|
+
const params = new URLSearchParams({ filter, limit: "25" });
|
|
31167
|
+
const endpoint = `/ims/oneroster/rostering/v1p2/users?${params}`;
|
|
31168
|
+
let allUsers = [];
|
|
31169
|
+
try {
|
|
31170
|
+
const response = await client["request"](endpoint, "GET");
|
|
31171
|
+
allUsers = response.users || [];
|
|
31172
|
+
} catch (error) {
|
|
31173
|
+
logger16.warn("Failed to search OneRoster users", {
|
|
31174
|
+
query: trimmedQuery,
|
|
31175
|
+
error: error instanceof Error ? error.message : String(error)
|
|
31176
|
+
});
|
|
31177
|
+
return { students: [] };
|
|
31178
|
+
}
|
|
31179
|
+
const roster = await client.oneroster.enrollments.listByCourse(courseId, {
|
|
31180
|
+
role: "student",
|
|
31181
|
+
includeUsers: false
|
|
31182
|
+
});
|
|
31183
|
+
const enrolledStudentIds = new Set(roster.map((entry) => entry.enrollment.user.sourcedId));
|
|
31184
|
+
const students = allUsers.filter((entry) => Boolean(entry.sourcedId) && entry.roles?.some((role) => role.role === "student") === true).map((entry) => ({
|
|
31185
|
+
studentId: entry.sourcedId,
|
|
31186
|
+
name: `${entry.givenName || ""} ${entry.familyName || ""}`.trim() || entry.sourcedId,
|
|
31187
|
+
email: entry.email || null,
|
|
31188
|
+
alreadyEnrolled: enrolledStudentIds.has(entry.sourcedId)
|
|
31189
|
+
}));
|
|
31190
|
+
return { students };
|
|
31191
|
+
}
|
|
31192
|
+
async enrollStudent(data, user) {
|
|
31193
|
+
const client = this.requireClient();
|
|
31194
|
+
await this.deps.validateGameManagementAccess(user, data.gameId);
|
|
31195
|
+
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
31196
|
+
where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId))
|
|
31197
|
+
});
|
|
31198
|
+
if (!integration) {
|
|
31199
|
+
throw new NotFoundError("Timeback integration", `${data.gameId}:${data.courseId}`);
|
|
31200
|
+
}
|
|
31201
|
+
await client.edubridge.enrollments.enroll(data.studentId, data.courseId, {
|
|
31202
|
+
role: "student"
|
|
31203
|
+
});
|
|
31204
|
+
return { status: "ok" };
|
|
31205
|
+
}
|
|
31206
|
+
async unenrollStudent(data, user) {
|
|
31207
|
+
const client = this.requireClient();
|
|
31208
|
+
await this.deps.validateGameManagementAccess(user, data.gameId);
|
|
31209
|
+
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
31210
|
+
where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId))
|
|
31211
|
+
});
|
|
31212
|
+
if (!integration) {
|
|
31213
|
+
throw new NotFoundError("Timeback integration", `${data.gameId}:${data.courseId}`);
|
|
31214
|
+
}
|
|
31215
|
+
await client.edubridge.enrollments.unenroll(data.studentId, data.courseId);
|
|
31216
|
+
return { status: "ok" };
|
|
31217
|
+
}
|
|
31126
31218
|
async getCompletionStatus(client, courseId, studentId) {
|
|
31127
31219
|
const ids = deriveSourcedIds(courseId);
|
|
31128
31220
|
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
@@ -31574,7 +31666,7 @@ class TimebackService {
|
|
|
31574
31666
|
return { integrations, ...verbose && verboseData.length > 0 && { verbose: verboseData } };
|
|
31575
31667
|
}
|
|
31576
31668
|
async getIntegrations(gameId, user) {
|
|
31577
|
-
await this.deps.
|
|
31669
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
31578
31670
|
const rows = await this.deps.db.query.gameTimebackIntegrations.findMany({
|
|
31579
31671
|
where: eq(gameTimebackIntegrations.gameId, gameId)
|
|
31580
31672
|
});
|
|
@@ -31829,6 +31921,7 @@ function createPlatformServices(deps) {
|
|
|
31829
31921
|
alerts,
|
|
31830
31922
|
validateDeveloperAccessBySlug,
|
|
31831
31923
|
validateDeveloperAccess,
|
|
31924
|
+
validateGameManagementAccess,
|
|
31832
31925
|
validateOwnership
|
|
31833
31926
|
} = deps;
|
|
31834
31927
|
const bucket = new BucketService({
|
|
@@ -31863,12 +31956,14 @@ function createPlatformServices(deps) {
|
|
|
31863
31956
|
const timeback2 = new TimebackService({
|
|
31864
31957
|
db: db2,
|
|
31865
31958
|
timeback: timebackClient,
|
|
31866
|
-
validateDeveloperAccess
|
|
31959
|
+
validateDeveloperAccess,
|
|
31960
|
+
validateGameManagementAccess
|
|
31867
31961
|
});
|
|
31868
31962
|
const timebackAdmin = new TimebackAdminService({
|
|
31869
31963
|
db: db2,
|
|
31870
31964
|
timeback: timebackClient,
|
|
31871
|
-
validateDeveloperAccess
|
|
31965
|
+
validateDeveloperAccess,
|
|
31966
|
+
validateGameManagementAccess
|
|
31872
31967
|
});
|
|
31873
31968
|
return {
|
|
31874
31969
|
bucket,
|
|
@@ -35000,6 +35095,34 @@ function createEduBridgeNamespace(client) {
|
|
|
35000
35095
|
listByUser: async (userId) => {
|
|
35001
35096
|
const response = await client["request"](`/edubridge/enrollments/user/${userId}`, "GET");
|
|
35002
35097
|
return response.data;
|
|
35098
|
+
},
|
|
35099
|
+
enroll: async (userId, courseId, options) => {
|
|
35100
|
+
const segments = [userId, courseId];
|
|
35101
|
+
if (options?.schoolId) {
|
|
35102
|
+
segments.push(options.schoolId);
|
|
35103
|
+
}
|
|
35104
|
+
const body2 = {};
|
|
35105
|
+
if (options?.role) {
|
|
35106
|
+
body2.role = options.role;
|
|
35107
|
+
}
|
|
35108
|
+
if (options?.sourcedId) {
|
|
35109
|
+
body2.sourcedId = options.sourcedId;
|
|
35110
|
+
}
|
|
35111
|
+
if (options?.beginDate) {
|
|
35112
|
+
body2.beginDate = options.beginDate;
|
|
35113
|
+
}
|
|
35114
|
+
if (options?.metadata) {
|
|
35115
|
+
body2.metadata = options.metadata;
|
|
35116
|
+
}
|
|
35117
|
+
const response = await client["request"](`/edubridge/enrollments/enroll/${segments.join("/")}`, "POST", body2);
|
|
35118
|
+
return response.data;
|
|
35119
|
+
},
|
|
35120
|
+
unenroll: async (userId, courseId, options) => {
|
|
35121
|
+
const segments = [userId, courseId];
|
|
35122
|
+
if (options?.schoolId) {
|
|
35123
|
+
segments.push(options.schoolId);
|
|
35124
|
+
}
|
|
35125
|
+
await client["request"](`/edubridge/enrollments/unenroll/${segments.join("/")}`, "DELETE");
|
|
35003
35126
|
}
|
|
35004
35127
|
};
|
|
35005
35128
|
const analytics = {
|
|
@@ -35175,6 +35298,10 @@ function createOneRosterNamespace(client) {
|
|
|
35175
35298
|
logTimebackError("list course roster", error, { courseSourcedId });
|
|
35176
35299
|
throw error;
|
|
35177
35300
|
}
|
|
35301
|
+
},
|
|
35302
|
+
create: async (data) => client["request"](ONEROSTER_ENDPOINTS4.enrollments, "POST", { enrollment: data }),
|
|
35303
|
+
delete: async (sourcedId) => {
|
|
35304
|
+
await client["request"](`${ONEROSTER_ENDPOINTS4.enrollments}/${sourcedId}`, "DELETE");
|
|
35178
35305
|
}
|
|
35179
35306
|
},
|
|
35180
35307
|
organizations: {
|
|
@@ -93507,7 +93634,7 @@ function isValidAdminAttributionDate(value) {
|
|
|
93507
93634
|
const date4 = new Date(Date.UTC(year3, month - 1, day, 12, 0, 0));
|
|
93508
93635
|
return date4.getUTCFullYear() === year3 && date4.getUTCMonth() + 1 === month && date4.getUTCDate() === day;
|
|
93509
93636
|
}
|
|
93510
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS5, TimebackGradeSchema, TimebackSubjectSchema, UpdateTimebackXpRequestSchema, EndActivityRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ToggleCourseCompletionRequestSchema;
|
|
93637
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS5, TimebackGradeSchema, TimebackSubjectSchema, UpdateTimebackXpRequestSchema, EndActivityRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ToggleCourseCompletionRequestSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema;
|
|
93511
93638
|
var init_schemas11 = __esm(() => {
|
|
93512
93639
|
init_esm();
|
|
93513
93640
|
TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
|
@@ -93650,6 +93777,16 @@ var init_schemas11 = __esm(() => {
|
|
|
93650
93777
|
studentId: exports_external.string().min(1),
|
|
93651
93778
|
action: exports_external.enum(["complete", "resume"])
|
|
93652
93779
|
});
|
|
93780
|
+
EnrollStudentRequestSchema = exports_external.object({
|
|
93781
|
+
gameId: exports_external.string().uuid(),
|
|
93782
|
+
courseId: exports_external.string().min(1),
|
|
93783
|
+
studentId: exports_external.string().min(1)
|
|
93784
|
+
});
|
|
93785
|
+
UnenrollStudentRequestSchema = exports_external.object({
|
|
93786
|
+
gameId: exports_external.string().uuid(),
|
|
93787
|
+
courseId: exports_external.string().min(1),
|
|
93788
|
+
studentId: exports_external.string().min(1)
|
|
93789
|
+
});
|
|
93653
93790
|
});
|
|
93654
93791
|
|
|
93655
93792
|
// ../data/src/schemas.index.ts
|
|
@@ -93676,6 +93813,9 @@ function isAuthenticated(ctx) {
|
|
|
93676
93813
|
var init_types9 = () => {};
|
|
93677
93814
|
|
|
93678
93815
|
// ../api-core/src/utils/auth.util.ts
|
|
93816
|
+
function hasGameManagementAccess(user) {
|
|
93817
|
+
return user.role === "admin" || user.role === "teacher" || user.role === "developer" && user.developerStatus === "approved";
|
|
93818
|
+
}
|
|
93679
93819
|
function requireAuth(handler) {
|
|
93680
93820
|
return async (ctx) => {
|
|
93681
93821
|
if (!isAuthenticated(ctx)) {
|
|
@@ -93719,6 +93859,17 @@ function requireDeveloper(handler) {
|
|
|
93719
93859
|
return handler(ctx);
|
|
93720
93860
|
};
|
|
93721
93861
|
}
|
|
93862
|
+
function requireGameManagementAccess(handler) {
|
|
93863
|
+
return async (ctx) => {
|
|
93864
|
+
if (!isAuthenticated(ctx)) {
|
|
93865
|
+
throw ApiError.unauthorized("Valid session or bearer token required");
|
|
93866
|
+
}
|
|
93867
|
+
if (!hasGameManagementAccess(ctx.user)) {
|
|
93868
|
+
throw ApiError.forbidden("Game management access required");
|
|
93869
|
+
}
|
|
93870
|
+
return handler(ctx);
|
|
93871
|
+
};
|
|
93872
|
+
}
|
|
93722
93873
|
var init_auth_util = __esm(() => {
|
|
93723
93874
|
init_errors();
|
|
93724
93875
|
init_types9();
|
|
@@ -95776,7 +95927,7 @@ var init_sprite_controller = __esm(() => {
|
|
|
95776
95927
|
});
|
|
95777
95928
|
|
|
95778
95929
|
// ../api-core/src/controllers/timeback.controller.ts
|
|
95779
|
-
var logger63, getTodayXp, getTotalXp, updateTodayXp, getXpHistory, populateStudent, getUser, getUserById, setupIntegration, getIntegrations, verifyIntegration, getConfig2, deleteIntegrations, endActivity, getStudentXp, getRoster, getStudentOverview, getStudentActivity, grantXp, adjustTime, adjustMastery, toggleCompletion, timeback2;
|
|
95930
|
+
var logger63, getTodayXp, getTotalXp, updateTodayXp, getXpHistory, populateStudent, getUser, getUserById, setupIntegration, getIntegrations, verifyIntegration, getConfig2, deleteIntegrations, endActivity, getStudentXp, getRoster, getStudentOverview, getStudentActivity, grantXp, adjustTime, adjustMastery, toggleCompletion, searchStudents, enrollStudent, unenrollStudent, timeback2;
|
|
95780
95931
|
var init_timeback_controller = __esm(() => {
|
|
95781
95932
|
init_esm();
|
|
95782
95933
|
init_schemas_index();
|
|
@@ -95866,7 +96017,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
95866
96017
|
});
|
|
95867
96018
|
return ctx.services.timeback.setupIntegration(body2.gameId, body2, ctx.user);
|
|
95868
96019
|
});
|
|
95869
|
-
getIntegrations =
|
|
96020
|
+
getIntegrations = requireGameManagementAccess(async (ctx) => {
|
|
95870
96021
|
const gameId = ctx.params.gameId;
|
|
95871
96022
|
if (!gameId) {
|
|
95872
96023
|
throw ApiError.badRequest("Missing gameId");
|
|
@@ -95991,7 +96142,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
95991
96142
|
include
|
|
95992
96143
|
});
|
|
95993
96144
|
});
|
|
95994
|
-
getRoster =
|
|
96145
|
+
getRoster = requireGameManagementAccess(async (ctx) => {
|
|
95995
96146
|
const gameId = ctx.params.gameId;
|
|
95996
96147
|
const courseId = ctx.params.courseId;
|
|
95997
96148
|
if (!gameId || !courseId) {
|
|
@@ -96004,7 +96155,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
96004
96155
|
});
|
|
96005
96156
|
return ctx.services.timebackAdmin.listStudentsForCourse(gameId, courseId, ctx.user);
|
|
96006
96157
|
});
|
|
96007
|
-
getStudentOverview =
|
|
96158
|
+
getStudentOverview = requireGameManagementAccess(async (ctx) => {
|
|
96008
96159
|
const timebackId = ctx.params.timebackId;
|
|
96009
96160
|
const gameId = ctx.url.searchParams.get("gameId") || undefined;
|
|
96010
96161
|
const courseId = ctx.url.searchParams.get("courseId") || undefined;
|
|
@@ -96019,7 +96170,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
96019
96170
|
});
|
|
96020
96171
|
return ctx.services.timebackAdmin.getStudentOverview(gameId, timebackId, ctx.user, courseId);
|
|
96021
96172
|
});
|
|
96022
|
-
getStudentActivity =
|
|
96173
|
+
getStudentActivity = requireGameManagementAccess(async (ctx) => {
|
|
96023
96174
|
const timebackId = ctx.params.timebackId;
|
|
96024
96175
|
const courseId = ctx.params.courseId;
|
|
96025
96176
|
const gameId = ctx.url.searchParams.get("gameId") || undefined;
|
|
@@ -96082,7 +96233,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
96082
96233
|
});
|
|
96083
96234
|
return ctx.services.timebackAdmin.adjustMasteredUnits(body2, ctx.user);
|
|
96084
96235
|
});
|
|
96085
|
-
toggleCompletion =
|
|
96236
|
+
toggleCompletion = requireGameManagementAccess(async (ctx) => {
|
|
96086
96237
|
const body2 = await parseRequestBody(ctx.request, ToggleCourseCompletionRequestSchema);
|
|
96087
96238
|
logger63.debug("Toggling course completion", {
|
|
96088
96239
|
requesterId: ctx.user.id,
|
|
@@ -96093,6 +96244,41 @@ var init_timeback_controller = __esm(() => {
|
|
|
96093
96244
|
});
|
|
96094
96245
|
return ctx.services.timebackAdmin.toggleCourseCompletion(body2, ctx.user);
|
|
96095
96246
|
});
|
|
96247
|
+
searchStudents = requireGameManagementAccess(async (ctx) => {
|
|
96248
|
+
const gameId = ctx.params.gameId;
|
|
96249
|
+
const courseId = ctx.params.courseId;
|
|
96250
|
+
const query = ctx.url.searchParams.get("q") || "";
|
|
96251
|
+
if (!gameId || !courseId) {
|
|
96252
|
+
throw ApiError.badRequest("Missing gameId or courseId parameter");
|
|
96253
|
+
}
|
|
96254
|
+
logger63.debug("Searching students for enrollment", {
|
|
96255
|
+
requesterId: ctx.user.id,
|
|
96256
|
+
gameId,
|
|
96257
|
+
courseId,
|
|
96258
|
+
query
|
|
96259
|
+
});
|
|
96260
|
+
return ctx.services.timebackAdmin.searchStudentsForEnrollment(gameId, courseId, query, ctx.user);
|
|
96261
|
+
});
|
|
96262
|
+
enrollStudent = requireGameManagementAccess(async (ctx) => {
|
|
96263
|
+
const body2 = await parseRequestBody(ctx.request, EnrollStudentRequestSchema);
|
|
96264
|
+
logger63.debug("Enrolling student", {
|
|
96265
|
+
requesterId: ctx.user.id,
|
|
96266
|
+
gameId: body2.gameId,
|
|
96267
|
+
courseId: body2.courseId,
|
|
96268
|
+
studentId: body2.studentId
|
|
96269
|
+
});
|
|
96270
|
+
return ctx.services.timebackAdmin.enrollStudent(body2, ctx.user);
|
|
96271
|
+
});
|
|
96272
|
+
unenrollStudent = requireGameManagementAccess(async (ctx) => {
|
|
96273
|
+
const body2 = await parseRequestBody(ctx.request, UnenrollStudentRequestSchema);
|
|
96274
|
+
logger63.debug("Unenrolling student", {
|
|
96275
|
+
requesterId: ctx.user.id,
|
|
96276
|
+
gameId: body2.gameId,
|
|
96277
|
+
courseId: body2.courseId,
|
|
96278
|
+
studentId: body2.studentId
|
|
96279
|
+
});
|
|
96280
|
+
return ctx.services.timebackAdmin.unenrollStudent(body2, ctx.user);
|
|
96281
|
+
});
|
|
96096
96282
|
timeback2 = {
|
|
96097
96283
|
getTodayXp,
|
|
96098
96284
|
getTotalXp,
|
|
@@ -96114,7 +96300,10 @@ var init_timeback_controller = __esm(() => {
|
|
|
96114
96300
|
grantXp,
|
|
96115
96301
|
adjustTime,
|
|
96116
96302
|
adjustMastery,
|
|
96117
|
-
toggleCompletion
|
|
96303
|
+
toggleCompletion,
|
|
96304
|
+
searchStudents,
|
|
96305
|
+
enrollStudent,
|
|
96306
|
+
unenrollStudent
|
|
96118
96307
|
};
|
|
96119
96308
|
});
|
|
96120
96309
|
|
package/dist/server.js
CHANGED
|
@@ -1309,7 +1309,7 @@ var package_default;
|
|
|
1309
1309
|
var init_package = __esm(() => {
|
|
1310
1310
|
package_default = {
|
|
1311
1311
|
name: "@playcademy/sandbox",
|
|
1312
|
-
version: "0.3.17-beta.
|
|
1312
|
+
version: "0.3.17-beta.7",
|
|
1313
1313
|
description: "Local development server for Playcademy game development",
|
|
1314
1314
|
type: "module",
|
|
1315
1315
|
exports: {
|
|
@@ -11549,7 +11549,7 @@ var init_table6 = __esm(() => {
|
|
|
11549
11549
|
init_drizzle_orm();
|
|
11550
11550
|
init_pg_core();
|
|
11551
11551
|
init_table5();
|
|
11552
|
-
userRoleEnum = pgEnum("user_role", ["admin", "player", "developer"]);
|
|
11552
|
+
userRoleEnum = pgEnum("user_role", ["admin", "player", "developer", "teacher"]);
|
|
11553
11553
|
developerStatusEnum = pgEnum("developer_status", ["none", "pending", "approved"]);
|
|
11554
11554
|
users = pgTable("user", {
|
|
11555
11555
|
id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
@@ -26732,10 +26732,13 @@ var init_game_service = __esm(() => {
|
|
|
26732
26732
|
});
|
|
26733
26733
|
}
|
|
26734
26734
|
async listManageable(user) {
|
|
26735
|
-
|
|
26735
|
+
const seesAllGames = user.role === "admin" || user.role === "teacher";
|
|
26736
|
+
if (!seesAllGames) {
|
|
26737
|
+
this.validateDeveloperStatus(user);
|
|
26738
|
+
}
|
|
26736
26739
|
const db2 = this.deps.db;
|
|
26737
26740
|
return db2.query.games.findMany({
|
|
26738
|
-
where:
|
|
26741
|
+
where: seesAllGames ? undefined : eq(games.developerId, user.id),
|
|
26739
26742
|
orderBy: [desc(games.createdAt)]
|
|
26740
26743
|
});
|
|
26741
26744
|
}
|
|
@@ -27139,6 +27142,19 @@ var init_game_service = __esm(() => {
|
|
|
27139
27142
|
throw new NotFoundError("Game", gameId);
|
|
27140
27143
|
}
|
|
27141
27144
|
}
|
|
27145
|
+
async validateGameManagementAccess(user, gameId) {
|
|
27146
|
+
if (user.role === "admin" || user.role === "teacher") {
|
|
27147
|
+
const gameExists = await this.deps.db.query.games.findFirst({
|
|
27148
|
+
where: eq(games.id, gameId),
|
|
27149
|
+
columns: { id: true }
|
|
27150
|
+
});
|
|
27151
|
+
if (!gameExists) {
|
|
27152
|
+
throw new NotFoundError("Game", gameId);
|
|
27153
|
+
}
|
|
27154
|
+
return;
|
|
27155
|
+
}
|
|
27156
|
+
return this.validateDeveloperAccess(user, gameId);
|
|
27157
|
+
}
|
|
27142
27158
|
async validateDeveloperAccessBySlug(user, slug) {
|
|
27143
27159
|
this.validateDeveloperStatus(user);
|
|
27144
27160
|
const db2 = this.deps.db;
|
|
@@ -27209,6 +27225,7 @@ function createGameServices(deps) {
|
|
|
27209
27225
|
validators: {
|
|
27210
27226
|
validateDeveloperAccessBySlug: (user, slug) => game.validateDeveloperAccessBySlug(user, slug),
|
|
27211
27227
|
validateDeveloperAccess: (user, gameId) => game.validateDeveloperAccess(user, gameId),
|
|
27228
|
+
validateGameManagementAccess: (user, gameId) => game.validateGameManagementAccess(user, gameId),
|
|
27212
27229
|
validateOwnership: (user, gameId) => game.validateOwnership(user, gameId)
|
|
27213
27230
|
}
|
|
27214
27231
|
};
|
|
@@ -30611,9 +30628,13 @@ class TimebackAdminService {
|
|
|
30611
30628
|
});
|
|
30612
30629
|
});
|
|
30613
30630
|
}
|
|
30614
|
-
async resolveAdminMutationContext(gameId, courseId, user, studentId) {
|
|
30631
|
+
async resolveAdminMutationContext(gameId, courseId, user, studentId, accessLevel = "developer") {
|
|
30615
30632
|
const client = this.requireClient();
|
|
30616
|
-
|
|
30633
|
+
if (accessLevel === "dashboard") {
|
|
30634
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
30635
|
+
} else {
|
|
30636
|
+
await this.deps.validateDeveloperAccess(user, gameId);
|
|
30637
|
+
}
|
|
30617
30638
|
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
30618
30639
|
where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
|
|
30619
30640
|
});
|
|
@@ -30873,7 +30894,7 @@ class TimebackAdminService {
|
|
|
30873
30894
|
}
|
|
30874
30895
|
async listStudentsForCourse(gameId, courseId, user) {
|
|
30875
30896
|
const client = this.requireClient();
|
|
30876
|
-
await this.deps.
|
|
30897
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
30877
30898
|
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
30878
30899
|
where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
|
|
30879
30900
|
});
|
|
@@ -30911,7 +30932,7 @@ class TimebackAdminService {
|
|
|
30911
30932
|
}
|
|
30912
30933
|
async getStudentOverview(gameId, studentId, user, courseId) {
|
|
30913
30934
|
const client = this.requireClient();
|
|
30914
|
-
await this.deps.
|
|
30935
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
30915
30936
|
const integrations = await this.deps.db.query.gameTimebackIntegrations.findMany({
|
|
30916
30937
|
where: courseId ? and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId)) : eq(gameTimebackIntegrations.gameId, gameId)
|
|
30917
30938
|
});
|
|
@@ -30965,7 +30986,7 @@ class TimebackAdminService {
|
|
|
30965
30986
|
const client = this.requireClient();
|
|
30966
30987
|
const safeLimit = Math.max(1, Math.min(limit, TimebackAdminService.MAX_STUDENT_ACTIVITY_LIMIT));
|
|
30967
30988
|
const safeOffset = Math.max(0, Math.min(offset, TimebackAdminService.MAX_STUDENT_ACTIVITY_OFFSET));
|
|
30968
|
-
await this.deps.
|
|
30989
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
30969
30990
|
const [integration, sensorUrl] = await Promise.all([
|
|
30970
30991
|
this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
30971
30992
|
where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
|
|
@@ -31029,7 +31050,7 @@ class TimebackAdminService {
|
|
|
31029
31050
|
return { status: "ok" };
|
|
31030
31051
|
}
|
|
31031
31052
|
async toggleCourseCompletion(data, user) {
|
|
31032
|
-
const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user, data.studentId);
|
|
31053
|
+
const { client, sensorUrl, appName, actor } = await this.resolveAdminMutationContext(data.gameId, data.courseId, user, data.studentId, "dashboard");
|
|
31033
31054
|
const historyClient = client;
|
|
31034
31055
|
const ids = deriveSourcedIds(data.courseId);
|
|
31035
31056
|
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
@@ -31122,6 +31143,77 @@ class TimebackAdminService {
|
|
|
31122
31143
|
}
|
|
31123
31144
|
return { status: "ok" };
|
|
31124
31145
|
}
|
|
31146
|
+
async searchStudentsForEnrollment(gameId, courseId, query, user) {
|
|
31147
|
+
const client = this.requireClient();
|
|
31148
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
31149
|
+
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
31150
|
+
where: and(eq(gameTimebackIntegrations.gameId, gameId), eq(gameTimebackIntegrations.courseId, courseId))
|
|
31151
|
+
});
|
|
31152
|
+
if (!integration) {
|
|
31153
|
+
throw new NotFoundError("Timeback integration", `${gameId}:${courseId}`);
|
|
31154
|
+
}
|
|
31155
|
+
const trimmedQuery = query.trim();
|
|
31156
|
+
if (trimmedQuery.length < 2) {
|
|
31157
|
+
return { students: [] };
|
|
31158
|
+
}
|
|
31159
|
+
const filterParts = [
|
|
31160
|
+
`givenName~'${escapeFilterValue(trimmedQuery)}'`,
|
|
31161
|
+
`familyName~'${escapeFilterValue(trimmedQuery)}'`,
|
|
31162
|
+
`email~'${escapeFilterValue(trimmedQuery)}'`
|
|
31163
|
+
];
|
|
31164
|
+
const filter = filterParts.join(" OR ");
|
|
31165
|
+
const params = new URLSearchParams({ filter, limit: "25" });
|
|
31166
|
+
const endpoint = `/ims/oneroster/rostering/v1p2/users?${params}`;
|
|
31167
|
+
let allUsers = [];
|
|
31168
|
+
try {
|
|
31169
|
+
const response = await client["request"](endpoint, "GET");
|
|
31170
|
+
allUsers = response.users || [];
|
|
31171
|
+
} catch (error) {
|
|
31172
|
+
logger16.warn("Failed to search OneRoster users", {
|
|
31173
|
+
query: trimmedQuery,
|
|
31174
|
+
error: error instanceof Error ? error.message : String(error)
|
|
31175
|
+
});
|
|
31176
|
+
return { students: [] };
|
|
31177
|
+
}
|
|
31178
|
+
const roster = await client.oneroster.enrollments.listByCourse(courseId, {
|
|
31179
|
+
role: "student",
|
|
31180
|
+
includeUsers: false
|
|
31181
|
+
});
|
|
31182
|
+
const enrolledStudentIds = new Set(roster.map((entry) => entry.enrollment.user.sourcedId));
|
|
31183
|
+
const students = allUsers.filter((entry) => Boolean(entry.sourcedId) && entry.roles?.some((role) => role.role === "student") === true).map((entry) => ({
|
|
31184
|
+
studentId: entry.sourcedId,
|
|
31185
|
+
name: `${entry.givenName || ""} ${entry.familyName || ""}`.trim() || entry.sourcedId,
|
|
31186
|
+
email: entry.email || null,
|
|
31187
|
+
alreadyEnrolled: enrolledStudentIds.has(entry.sourcedId)
|
|
31188
|
+
}));
|
|
31189
|
+
return { students };
|
|
31190
|
+
}
|
|
31191
|
+
async enrollStudent(data, user) {
|
|
31192
|
+
const client = this.requireClient();
|
|
31193
|
+
await this.deps.validateGameManagementAccess(user, data.gameId);
|
|
31194
|
+
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
31195
|
+
where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId))
|
|
31196
|
+
});
|
|
31197
|
+
if (!integration) {
|
|
31198
|
+
throw new NotFoundError("Timeback integration", `${data.gameId}:${data.courseId}`);
|
|
31199
|
+
}
|
|
31200
|
+
await client.edubridge.enrollments.enroll(data.studentId, data.courseId, {
|
|
31201
|
+
role: "student"
|
|
31202
|
+
});
|
|
31203
|
+
return { status: "ok" };
|
|
31204
|
+
}
|
|
31205
|
+
async unenrollStudent(data, user) {
|
|
31206
|
+
const client = this.requireClient();
|
|
31207
|
+
await this.deps.validateGameManagementAccess(user, data.gameId);
|
|
31208
|
+
const integration = await this.deps.db.query.gameTimebackIntegrations.findFirst({
|
|
31209
|
+
where: and(eq(gameTimebackIntegrations.gameId, data.gameId), eq(gameTimebackIntegrations.courseId, data.courseId))
|
|
31210
|
+
});
|
|
31211
|
+
if (!integration) {
|
|
31212
|
+
throw new NotFoundError("Timeback integration", `${data.gameId}:${data.courseId}`);
|
|
31213
|
+
}
|
|
31214
|
+
await client.edubridge.enrollments.unenroll(data.studentId, data.courseId);
|
|
31215
|
+
return { status: "ok" };
|
|
31216
|
+
}
|
|
31125
31217
|
async getCompletionStatus(client, courseId, studentId) {
|
|
31126
31218
|
const ids = deriveSourcedIds(courseId);
|
|
31127
31219
|
const lineItemId = `${ids.course}-mastery-completion-assessment`;
|
|
@@ -31573,7 +31665,7 @@ class TimebackService {
|
|
|
31573
31665
|
return { integrations, ...verbose && verboseData.length > 0 && { verbose: verboseData } };
|
|
31574
31666
|
}
|
|
31575
31667
|
async getIntegrations(gameId, user) {
|
|
31576
|
-
await this.deps.
|
|
31668
|
+
await this.deps.validateGameManagementAccess(user, gameId);
|
|
31577
31669
|
const rows = await this.deps.db.query.gameTimebackIntegrations.findMany({
|
|
31578
31670
|
where: eq(gameTimebackIntegrations.gameId, gameId)
|
|
31579
31671
|
});
|
|
@@ -31828,6 +31920,7 @@ function createPlatformServices(deps) {
|
|
|
31828
31920
|
alerts,
|
|
31829
31921
|
validateDeveloperAccessBySlug,
|
|
31830
31922
|
validateDeveloperAccess,
|
|
31923
|
+
validateGameManagementAccess,
|
|
31831
31924
|
validateOwnership
|
|
31832
31925
|
} = deps;
|
|
31833
31926
|
const bucket = new BucketService({
|
|
@@ -31862,12 +31955,14 @@ function createPlatformServices(deps) {
|
|
|
31862
31955
|
const timeback2 = new TimebackService({
|
|
31863
31956
|
db: db2,
|
|
31864
31957
|
timeback: timebackClient,
|
|
31865
|
-
validateDeveloperAccess
|
|
31958
|
+
validateDeveloperAccess,
|
|
31959
|
+
validateGameManagementAccess
|
|
31866
31960
|
});
|
|
31867
31961
|
const timebackAdmin = new TimebackAdminService({
|
|
31868
31962
|
db: db2,
|
|
31869
31963
|
timeback: timebackClient,
|
|
31870
|
-
validateDeveloperAccess
|
|
31964
|
+
validateDeveloperAccess,
|
|
31965
|
+
validateGameManagementAccess
|
|
31871
31966
|
});
|
|
31872
31967
|
return {
|
|
31873
31968
|
bucket,
|
|
@@ -34999,6 +35094,34 @@ function createEduBridgeNamespace(client) {
|
|
|
34999
35094
|
listByUser: async (userId) => {
|
|
35000
35095
|
const response = await client["request"](`/edubridge/enrollments/user/${userId}`, "GET");
|
|
35001
35096
|
return response.data;
|
|
35097
|
+
},
|
|
35098
|
+
enroll: async (userId, courseId, options) => {
|
|
35099
|
+
const segments = [userId, courseId];
|
|
35100
|
+
if (options?.schoolId) {
|
|
35101
|
+
segments.push(options.schoolId);
|
|
35102
|
+
}
|
|
35103
|
+
const body2 = {};
|
|
35104
|
+
if (options?.role) {
|
|
35105
|
+
body2.role = options.role;
|
|
35106
|
+
}
|
|
35107
|
+
if (options?.sourcedId) {
|
|
35108
|
+
body2.sourcedId = options.sourcedId;
|
|
35109
|
+
}
|
|
35110
|
+
if (options?.beginDate) {
|
|
35111
|
+
body2.beginDate = options.beginDate;
|
|
35112
|
+
}
|
|
35113
|
+
if (options?.metadata) {
|
|
35114
|
+
body2.metadata = options.metadata;
|
|
35115
|
+
}
|
|
35116
|
+
const response = await client["request"](`/edubridge/enrollments/enroll/${segments.join("/")}`, "POST", body2);
|
|
35117
|
+
return response.data;
|
|
35118
|
+
},
|
|
35119
|
+
unenroll: async (userId, courseId, options) => {
|
|
35120
|
+
const segments = [userId, courseId];
|
|
35121
|
+
if (options?.schoolId) {
|
|
35122
|
+
segments.push(options.schoolId);
|
|
35123
|
+
}
|
|
35124
|
+
await client["request"](`/edubridge/enrollments/unenroll/${segments.join("/")}`, "DELETE");
|
|
35002
35125
|
}
|
|
35003
35126
|
};
|
|
35004
35127
|
const analytics = {
|
|
@@ -35174,6 +35297,10 @@ function createOneRosterNamespace(client) {
|
|
|
35174
35297
|
logTimebackError("list course roster", error, { courseSourcedId });
|
|
35175
35298
|
throw error;
|
|
35176
35299
|
}
|
|
35300
|
+
},
|
|
35301
|
+
create: async (data) => client["request"](ONEROSTER_ENDPOINTS4.enrollments, "POST", { enrollment: data }),
|
|
35302
|
+
delete: async (sourcedId) => {
|
|
35303
|
+
await client["request"](`${ONEROSTER_ENDPOINTS4.enrollments}/${sourcedId}`, "DELETE");
|
|
35177
35304
|
}
|
|
35178
35305
|
},
|
|
35179
35306
|
organizations: {
|
|
@@ -93506,7 +93633,7 @@ function isValidAdminAttributionDate(value) {
|
|
|
93506
93633
|
const date4 = new Date(Date.UTC(year3, month - 1, day, 12, 0, 0));
|
|
93507
93634
|
return date4.getUTCFullYear() === year3 && date4.getUTCMonth() + 1 === month && date4.getUTCDate() === day;
|
|
93508
93635
|
}
|
|
93509
|
-
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS5, TimebackGradeSchema, TimebackSubjectSchema, UpdateTimebackXpRequestSchema, EndActivityRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ToggleCourseCompletionRequestSchema;
|
|
93636
|
+
var TIMEBACK_GRADES, TIMEBACK_SUBJECTS5, TimebackGradeSchema, TimebackSubjectSchema, UpdateTimebackXpRequestSchema, EndActivityRequestSchema, PopulateStudentRequestSchema, DerivedPlatformCourseConfigSchema, TimebackBaseConfigSchema, PlatformTimebackSetupRequestSchema, AdminTimebackMutationBaseSchema, AdminAttributionDateSchema, GrantTimebackXpRequestSchema, AdjustTimebackTimeRequestSchema, AdjustTimebackMasteryRequestSchema, ToggleCourseCompletionRequestSchema, EnrollStudentRequestSchema, UnenrollStudentRequestSchema;
|
|
93510
93637
|
var init_schemas11 = __esm(() => {
|
|
93511
93638
|
init_esm();
|
|
93512
93639
|
TIMEBACK_GRADES = [-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13];
|
|
@@ -93649,6 +93776,16 @@ var init_schemas11 = __esm(() => {
|
|
|
93649
93776
|
studentId: exports_external.string().min(1),
|
|
93650
93777
|
action: exports_external.enum(["complete", "resume"])
|
|
93651
93778
|
});
|
|
93779
|
+
EnrollStudentRequestSchema = exports_external.object({
|
|
93780
|
+
gameId: exports_external.string().uuid(),
|
|
93781
|
+
courseId: exports_external.string().min(1),
|
|
93782
|
+
studentId: exports_external.string().min(1)
|
|
93783
|
+
});
|
|
93784
|
+
UnenrollStudentRequestSchema = exports_external.object({
|
|
93785
|
+
gameId: exports_external.string().uuid(),
|
|
93786
|
+
courseId: exports_external.string().min(1),
|
|
93787
|
+
studentId: exports_external.string().min(1)
|
|
93788
|
+
});
|
|
93652
93789
|
});
|
|
93653
93790
|
|
|
93654
93791
|
// ../data/src/schemas.index.ts
|
|
@@ -93675,6 +93812,9 @@ function isAuthenticated(ctx) {
|
|
|
93675
93812
|
var init_types9 = () => {};
|
|
93676
93813
|
|
|
93677
93814
|
// ../api-core/src/utils/auth.util.ts
|
|
93815
|
+
function hasGameManagementAccess(user) {
|
|
93816
|
+
return user.role === "admin" || user.role === "teacher" || user.role === "developer" && user.developerStatus === "approved";
|
|
93817
|
+
}
|
|
93678
93818
|
function requireAuth(handler) {
|
|
93679
93819
|
return async (ctx) => {
|
|
93680
93820
|
if (!isAuthenticated(ctx)) {
|
|
@@ -93718,6 +93858,17 @@ function requireDeveloper(handler) {
|
|
|
93718
93858
|
return handler(ctx);
|
|
93719
93859
|
};
|
|
93720
93860
|
}
|
|
93861
|
+
function requireGameManagementAccess(handler) {
|
|
93862
|
+
return async (ctx) => {
|
|
93863
|
+
if (!isAuthenticated(ctx)) {
|
|
93864
|
+
throw ApiError.unauthorized("Valid session or bearer token required");
|
|
93865
|
+
}
|
|
93866
|
+
if (!hasGameManagementAccess(ctx.user)) {
|
|
93867
|
+
throw ApiError.forbidden("Game management access required");
|
|
93868
|
+
}
|
|
93869
|
+
return handler(ctx);
|
|
93870
|
+
};
|
|
93871
|
+
}
|
|
93721
93872
|
var init_auth_util = __esm(() => {
|
|
93722
93873
|
init_errors();
|
|
93723
93874
|
init_types9();
|
|
@@ -95775,7 +95926,7 @@ var init_sprite_controller = __esm(() => {
|
|
|
95775
95926
|
});
|
|
95776
95927
|
|
|
95777
95928
|
// ../api-core/src/controllers/timeback.controller.ts
|
|
95778
|
-
var logger63, getTodayXp, getTotalXp, updateTodayXp, getXpHistory, populateStudent, getUser, getUserById, setupIntegration, getIntegrations, verifyIntegration, getConfig2, deleteIntegrations, endActivity, getStudentXp, getRoster, getStudentOverview, getStudentActivity, grantXp, adjustTime, adjustMastery, toggleCompletion, timeback2;
|
|
95929
|
+
var logger63, getTodayXp, getTotalXp, updateTodayXp, getXpHistory, populateStudent, getUser, getUserById, setupIntegration, getIntegrations, verifyIntegration, getConfig2, deleteIntegrations, endActivity, getStudentXp, getRoster, getStudentOverview, getStudentActivity, grantXp, adjustTime, adjustMastery, toggleCompletion, searchStudents, enrollStudent, unenrollStudent, timeback2;
|
|
95779
95930
|
var init_timeback_controller = __esm(() => {
|
|
95780
95931
|
init_esm();
|
|
95781
95932
|
init_schemas_index();
|
|
@@ -95865,7 +96016,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
95865
96016
|
});
|
|
95866
96017
|
return ctx.services.timeback.setupIntegration(body2.gameId, body2, ctx.user);
|
|
95867
96018
|
});
|
|
95868
|
-
getIntegrations =
|
|
96019
|
+
getIntegrations = requireGameManagementAccess(async (ctx) => {
|
|
95869
96020
|
const gameId = ctx.params.gameId;
|
|
95870
96021
|
if (!gameId) {
|
|
95871
96022
|
throw ApiError.badRequest("Missing gameId");
|
|
@@ -95990,7 +96141,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
95990
96141
|
include
|
|
95991
96142
|
});
|
|
95992
96143
|
});
|
|
95993
|
-
getRoster =
|
|
96144
|
+
getRoster = requireGameManagementAccess(async (ctx) => {
|
|
95994
96145
|
const gameId = ctx.params.gameId;
|
|
95995
96146
|
const courseId = ctx.params.courseId;
|
|
95996
96147
|
if (!gameId || !courseId) {
|
|
@@ -96003,7 +96154,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
96003
96154
|
});
|
|
96004
96155
|
return ctx.services.timebackAdmin.listStudentsForCourse(gameId, courseId, ctx.user);
|
|
96005
96156
|
});
|
|
96006
|
-
getStudentOverview =
|
|
96157
|
+
getStudentOverview = requireGameManagementAccess(async (ctx) => {
|
|
96007
96158
|
const timebackId = ctx.params.timebackId;
|
|
96008
96159
|
const gameId = ctx.url.searchParams.get("gameId") || undefined;
|
|
96009
96160
|
const courseId = ctx.url.searchParams.get("courseId") || undefined;
|
|
@@ -96018,7 +96169,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
96018
96169
|
});
|
|
96019
96170
|
return ctx.services.timebackAdmin.getStudentOverview(gameId, timebackId, ctx.user, courseId);
|
|
96020
96171
|
});
|
|
96021
|
-
getStudentActivity =
|
|
96172
|
+
getStudentActivity = requireGameManagementAccess(async (ctx) => {
|
|
96022
96173
|
const timebackId = ctx.params.timebackId;
|
|
96023
96174
|
const courseId = ctx.params.courseId;
|
|
96024
96175
|
const gameId = ctx.url.searchParams.get("gameId") || undefined;
|
|
@@ -96081,7 +96232,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
96081
96232
|
});
|
|
96082
96233
|
return ctx.services.timebackAdmin.adjustMasteredUnits(body2, ctx.user);
|
|
96083
96234
|
});
|
|
96084
|
-
toggleCompletion =
|
|
96235
|
+
toggleCompletion = requireGameManagementAccess(async (ctx) => {
|
|
96085
96236
|
const body2 = await parseRequestBody(ctx.request, ToggleCourseCompletionRequestSchema);
|
|
96086
96237
|
logger63.debug("Toggling course completion", {
|
|
96087
96238
|
requesterId: ctx.user.id,
|
|
@@ -96092,6 +96243,41 @@ var init_timeback_controller = __esm(() => {
|
|
|
96092
96243
|
});
|
|
96093
96244
|
return ctx.services.timebackAdmin.toggleCourseCompletion(body2, ctx.user);
|
|
96094
96245
|
});
|
|
96246
|
+
searchStudents = requireGameManagementAccess(async (ctx) => {
|
|
96247
|
+
const gameId = ctx.params.gameId;
|
|
96248
|
+
const courseId = ctx.params.courseId;
|
|
96249
|
+
const query = ctx.url.searchParams.get("q") || "";
|
|
96250
|
+
if (!gameId || !courseId) {
|
|
96251
|
+
throw ApiError.badRequest("Missing gameId or courseId parameter");
|
|
96252
|
+
}
|
|
96253
|
+
logger63.debug("Searching students for enrollment", {
|
|
96254
|
+
requesterId: ctx.user.id,
|
|
96255
|
+
gameId,
|
|
96256
|
+
courseId,
|
|
96257
|
+
query
|
|
96258
|
+
});
|
|
96259
|
+
return ctx.services.timebackAdmin.searchStudentsForEnrollment(gameId, courseId, query, ctx.user);
|
|
96260
|
+
});
|
|
96261
|
+
enrollStudent = requireGameManagementAccess(async (ctx) => {
|
|
96262
|
+
const body2 = await parseRequestBody(ctx.request, EnrollStudentRequestSchema);
|
|
96263
|
+
logger63.debug("Enrolling student", {
|
|
96264
|
+
requesterId: ctx.user.id,
|
|
96265
|
+
gameId: body2.gameId,
|
|
96266
|
+
courseId: body2.courseId,
|
|
96267
|
+
studentId: body2.studentId
|
|
96268
|
+
});
|
|
96269
|
+
return ctx.services.timebackAdmin.enrollStudent(body2, ctx.user);
|
|
96270
|
+
});
|
|
96271
|
+
unenrollStudent = requireGameManagementAccess(async (ctx) => {
|
|
96272
|
+
const body2 = await parseRequestBody(ctx.request, UnenrollStudentRequestSchema);
|
|
96273
|
+
logger63.debug("Unenrolling student", {
|
|
96274
|
+
requesterId: ctx.user.id,
|
|
96275
|
+
gameId: body2.gameId,
|
|
96276
|
+
courseId: body2.courseId,
|
|
96277
|
+
studentId: body2.studentId
|
|
96278
|
+
});
|
|
96279
|
+
return ctx.services.timebackAdmin.unenrollStudent(body2, ctx.user);
|
|
96280
|
+
});
|
|
96095
96281
|
timeback2 = {
|
|
96096
96282
|
getTodayXp,
|
|
96097
96283
|
getTotalXp,
|
|
@@ -96113,7 +96299,10 @@ var init_timeback_controller = __esm(() => {
|
|
|
96113
96299
|
grantXp,
|
|
96114
96300
|
adjustTime,
|
|
96115
96301
|
adjustMastery,
|
|
96116
|
-
toggleCompletion
|
|
96302
|
+
toggleCompletion,
|
|
96303
|
+
searchStudents,
|
|
96304
|
+
enrollStudent,
|
|
96305
|
+
unenrollStudent
|
|
96117
96306
|
};
|
|
96118
96307
|
});
|
|
96119
96308
|
|