@playcademy/vite-plugin 1.2.1-beta.25 → 1.2.1-beta.26
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 +505 -467
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -24315,7 +24315,7 @@ import path2 from "node:path";
|
|
|
24315
24315
|
// package.json
|
|
24316
24316
|
var package_default = {
|
|
24317
24317
|
name: "@playcademy/vite-plugin",
|
|
24318
|
-
version: "1.2.1-beta.
|
|
24318
|
+
version: "1.2.1-beta.26",
|
|
24319
24319
|
type: "module",
|
|
24320
24320
|
exports: {
|
|
24321
24321
|
".": {
|
|
@@ -24767,6 +24767,7 @@ import { stdout } from "process";
|
|
|
24767
24767
|
import { createHash } from "node:crypto";
|
|
24768
24768
|
import { createHash as createHash2 } from "node:crypto";
|
|
24769
24769
|
import crypto3 from "node:crypto";
|
|
24770
|
+
import * as crypto4 from "node:crypto";
|
|
24770
24771
|
import * as s3 from "fs";
|
|
24771
24772
|
import * as o3 from "path";
|
|
24772
24773
|
import fs22 from "node:fs";
|
|
@@ -24774,10 +24775,9 @@ import { dirname, isAbsolute, join as join4 } from "node:path";
|
|
|
24774
24775
|
import process22 from "process";
|
|
24775
24776
|
import os2 from "os";
|
|
24776
24777
|
import tty from "tty";
|
|
24777
|
-
import { randomUUID } from "crypto";
|
|
24778
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
24778
24779
|
import fs3 from "node:fs";
|
|
24779
24780
|
import fs4 from "node:fs";
|
|
24780
|
-
import * as crypto4 from "node:crypto";
|
|
24781
24781
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
24782
24782
|
var __create2 = Object.create;
|
|
24783
24783
|
var __getProtoOf2 = Object.getPrototypeOf;
|
|
@@ -25921,7 +25921,7 @@ var package_default2;
|
|
|
25921
25921
|
var init_package = __esm(() => {
|
|
25922
25922
|
package_default2 = {
|
|
25923
25923
|
name: "@playcademy/sandbox",
|
|
25924
|
-
version: "0.7.1-beta.
|
|
25924
|
+
version: "0.7.1-beta.24",
|
|
25925
25925
|
description: "Local development server for Playcademy game development",
|
|
25926
25926
|
type: "module",
|
|
25927
25927
|
exports: {
|
|
@@ -63054,67 +63054,6 @@ var init_infra2 = __esm(() => {
|
|
|
63054
63054
|
init_alerts_service();
|
|
63055
63055
|
init_kv_backup_service();
|
|
63056
63056
|
});
|
|
63057
|
-
function createInMemoryDatabaseLock() {
|
|
63058
|
-
const tails = new Map;
|
|
63059
|
-
return async (db2, key, operation) => {
|
|
63060
|
-
const previous = tails.get(key) ?? Promise.resolve();
|
|
63061
|
-
let release;
|
|
63062
|
-
const gate = new Promise((resolve2) => {
|
|
63063
|
-
release = resolve2;
|
|
63064
|
-
});
|
|
63065
|
-
const tail = previous.then(() => gate);
|
|
63066
|
-
tails.set(key, tail);
|
|
63067
|
-
await previous;
|
|
63068
|
-
try {
|
|
63069
|
-
return await operation(db2);
|
|
63070
|
-
} finally {
|
|
63071
|
-
release();
|
|
63072
|
-
if (tails.get(key) === tail) {
|
|
63073
|
-
tails.delete(key);
|
|
63074
|
-
}
|
|
63075
|
-
}
|
|
63076
|
-
};
|
|
63077
|
-
}
|
|
63078
|
-
var LockContentionError;
|
|
63079
|
-
var init_locks = __esm(() => {
|
|
63080
|
-
init_src6();
|
|
63081
|
-
LockContentionError = class LockContentionError2 extends Error {
|
|
63082
|
-
constructor(key) {
|
|
63083
|
-
super(`Lock is already held: ${key}`);
|
|
63084
|
-
this.name = "LockContentionError";
|
|
63085
|
-
}
|
|
63086
|
-
};
|
|
63087
|
-
});
|
|
63088
|
-
function assessmentRuntimeStartLockKey(input) {
|
|
63089
|
-
return JSON.stringify([
|
|
63090
|
-
ASSESSMENT_RUNTIME_LOCK_NAMESPACE,
|
|
63091
|
-
"start",
|
|
63092
|
-
input.studentId,
|
|
63093
|
-
input.activityId,
|
|
63094
|
-
input.purpose,
|
|
63095
|
-
input.courseId
|
|
63096
|
-
]);
|
|
63097
|
-
}
|
|
63098
|
-
function assessmentRuntimeAttemptLockKey(attemptId) {
|
|
63099
|
-
return JSON.stringify([ASSESSMENT_RUNTIME_LOCK_NAMESPACE, "attempt", attemptId]);
|
|
63100
|
-
}
|
|
63101
|
-
function createAssessmentRuntimeLock(lock) {
|
|
63102
|
-
return async (db2, key, operation) => {
|
|
63103
|
-
try {
|
|
63104
|
-
return await lock(db2, key, operation);
|
|
63105
|
-
} catch (error) {
|
|
63106
|
-
if (error instanceof LockContentionError) {
|
|
63107
|
-
throw new ServiceUnavailableError("Another assessment operation is in progress. Try again shortly.", { retryable: true });
|
|
63108
|
-
}
|
|
63109
|
-
throw error;
|
|
63110
|
-
}
|
|
63111
|
-
};
|
|
63112
|
-
}
|
|
63113
|
-
var ASSESSMENT_RUNTIME_LOCK_NAMESPACE = "playcademy-assessment-runtime-v1";
|
|
63114
|
-
var init_assessment_runtime_lock_util = __esm(() => {
|
|
63115
|
-
init_locks();
|
|
63116
|
-
init_errors2();
|
|
63117
|
-
});
|
|
63118
63057
|
function hasPagingOptions(options) {
|
|
63119
63058
|
return options.cursor !== undefined || options.limit !== undefined || options.delimiter !== undefined;
|
|
63120
63059
|
}
|
|
@@ -117910,6 +117849,109 @@ var init_timeback_admin_service = __esm(async () => {
|
|
|
117910
117849
|
init_timeback_mastery_completion_util()
|
|
117911
117850
|
]);
|
|
117912
117851
|
});
|
|
117852
|
+
function createInMemoryDatabaseLock() {
|
|
117853
|
+
const tails = new Map;
|
|
117854
|
+
return async (db2, key, operation) => {
|
|
117855
|
+
const previous = tails.get(key) ?? Promise.resolve();
|
|
117856
|
+
let release;
|
|
117857
|
+
const gate = new Promise((resolve2) => {
|
|
117858
|
+
release = resolve2;
|
|
117859
|
+
});
|
|
117860
|
+
const tail = previous.then(() => gate);
|
|
117861
|
+
tails.set(key, tail);
|
|
117862
|
+
await previous;
|
|
117863
|
+
try {
|
|
117864
|
+
return await operation(db2);
|
|
117865
|
+
} finally {
|
|
117866
|
+
release();
|
|
117867
|
+
if (tails.get(key) === tail) {
|
|
117868
|
+
tails.delete(key);
|
|
117869
|
+
}
|
|
117870
|
+
}
|
|
117871
|
+
};
|
|
117872
|
+
}
|
|
117873
|
+
var LockContentionError;
|
|
117874
|
+
var init_locks = __esm(() => {
|
|
117875
|
+
LockContentionError = class LockContentionError2 extends Error {
|
|
117876
|
+
constructor(key) {
|
|
117877
|
+
super(`Lock is already held: ${key}`);
|
|
117878
|
+
this.name = "LockContentionError";
|
|
117879
|
+
}
|
|
117880
|
+
};
|
|
117881
|
+
});
|
|
117882
|
+
function normalizeKeys(keys) {
|
|
117883
|
+
return [...new Set(keys)].toSorted();
|
|
117884
|
+
}
|
|
117885
|
+
function errorCode(error88) {
|
|
117886
|
+
if (typeof error88 !== "object" || error88 === null || !("code" in error88)) {
|
|
117887
|
+
return;
|
|
117888
|
+
}
|
|
117889
|
+
return typeof error88.code === "string" ? error88.code : undefined;
|
|
117890
|
+
}
|
|
117891
|
+
function translateAssessmentRuntimeLockError(error88) {
|
|
117892
|
+
if (error88 instanceof LockContentionError) {
|
|
117893
|
+
throw new ServiceUnavailableError("Another assessment operation is in progress. Try again shortly.", { retryable: true });
|
|
117894
|
+
}
|
|
117895
|
+
if (error88 instanceof AssessmentRuntimeLockTimeoutError) {
|
|
117896
|
+
throw new ServiceUnavailableError("The assessment operation took too long. Try again shortly.", { retryable: true, reason: "LOCK_TIMEOUT" });
|
|
117897
|
+
}
|
|
117898
|
+
const code = errorCode(error88);
|
|
117899
|
+
if (code && POSTGRES_TIMEOUT_ERROR_CODES.has(code)) {
|
|
117900
|
+
throw new ServiceUnavailableError("The assessment operation took too long. Try again shortly.", { retryable: true, reason: "LOCK_TIMEOUT" });
|
|
117901
|
+
}
|
|
117902
|
+
if (code && POSTGRES_CONNECTION_ERROR_CODES.has(code)) {
|
|
117903
|
+
throw new ServiceUnavailableError("The assessment lock connection was interrupted. Try again shortly.", { retryable: true, reason: "LOCK_CONNECTION_LOST" });
|
|
117904
|
+
}
|
|
117905
|
+
throw error88;
|
|
117906
|
+
}
|
|
117907
|
+
function createInMemoryAssessmentRuntimeLock() {
|
|
117908
|
+
const lock = createInMemoryDatabaseLock();
|
|
117909
|
+
return async (db2, keys, operation) => {
|
|
117910
|
+
const ordered = normalizeKeys(keys);
|
|
117911
|
+
function acquire(index2, operationDb) {
|
|
117912
|
+
const key = ordered[index2];
|
|
117913
|
+
return key ? lock(operationDb, key, (nested) => acquire(index2 + 1, nested)) : operation(operationDb);
|
|
117914
|
+
}
|
|
117915
|
+
try {
|
|
117916
|
+
return await acquire(0, db2);
|
|
117917
|
+
} catch (error88) {
|
|
117918
|
+
return translateAssessmentRuntimeLockError(error88);
|
|
117919
|
+
}
|
|
117920
|
+
};
|
|
117921
|
+
}
|
|
117922
|
+
function assessmentRuntimeStartLockKey(input) {
|
|
117923
|
+
return JSON.stringify([
|
|
117924
|
+
ASSESSMENT_RUNTIME_LOCK_NAMESPACE,
|
|
117925
|
+
"start",
|
|
117926
|
+
input.studentId,
|
|
117927
|
+
input.activityId,
|
|
117928
|
+
input.purpose,
|
|
117929
|
+
input.courseId
|
|
117930
|
+
]);
|
|
117931
|
+
}
|
|
117932
|
+
function assessmentRuntimeAttemptLockKey(attemptId) {
|
|
117933
|
+
return JSON.stringify([ASSESSMENT_RUNTIME_LOCK_NAMESPACE, "attempt", attemptId]);
|
|
117934
|
+
}
|
|
117935
|
+
var ASSESSMENT_RUNTIME_LOCK_NAMESPACE = "playcademy-assessment-runtime-v1";
|
|
117936
|
+
var POSTGRES_TIMEOUT_ERROR_CODES;
|
|
117937
|
+
var POSTGRES_CONNECTION_ERROR_CODES;
|
|
117938
|
+
var AssessmentRuntimeLockTimeoutError;
|
|
117939
|
+
var init_assessment_runtime_lock_util = __esm(() => {
|
|
117940
|
+
init_locks();
|
|
117941
|
+
init_errors2();
|
|
117942
|
+
POSTGRES_TIMEOUT_ERROR_CODES = new Set(["25P03", "57014"]);
|
|
117943
|
+
POSTGRES_CONNECTION_ERROR_CODES = new Set([
|
|
117944
|
+
"CONNECTION_CLOSED",
|
|
117945
|
+
"CONNECTION_DESTROYED",
|
|
117946
|
+
"CONNECTION_ENDED"
|
|
117947
|
+
]);
|
|
117948
|
+
AssessmentRuntimeLockTimeoutError = class AssessmentRuntimeLockTimeoutError2 extends Error {
|
|
117949
|
+
constructor(timeoutMs) {
|
|
117950
|
+
super(`Assessment operation exceeded its ${timeoutMs}ms lock deadline`);
|
|
117951
|
+
this.name = "AssessmentRuntimeLockTimeoutError";
|
|
117952
|
+
}
|
|
117953
|
+
};
|
|
117954
|
+
});
|
|
117913
117955
|
function validateAssessmentStatusTransition(current, next) {
|
|
117914
117956
|
if (current === next) {
|
|
117915
117957
|
return;
|
|
@@ -118733,8 +118775,8 @@ class TimebackAssessmentRuntimeService {
|
|
|
118733
118775
|
activityId: input.activityId,
|
|
118734
118776
|
purpose: input.purpose,
|
|
118735
118777
|
courseId: row.courseId
|
|
118736
|
-
}))
|
|
118737
|
-
return this.
|
|
118778
|
+
}));
|
|
118779
|
+
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
118738
118780
|
const context2 = await this.selectIntegration(candidates, studentId, input);
|
|
118739
118781
|
const attemptScope = {
|
|
118740
118782
|
studentId,
|
|
@@ -118751,7 +118793,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118751
118793
|
const attempts = listing.attempts;
|
|
118752
118794
|
const initialResolution = await this.resumeAvailableAttempt(tests, attempts, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeSnapshot(resumeDecision, attempts, context2.integration));
|
|
118753
118795
|
if (initialResolution.kind === "resumed") {
|
|
118754
|
-
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118796
|
+
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
118755
118797
|
return initialResolution.snapshot;
|
|
118756
118798
|
}
|
|
118757
118799
|
let decision = this.requireDecision(initialResolution.decision, input.purpose);
|
|
@@ -118760,7 +118802,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118760
118802
|
const refreshed = refreshedListing.attempts;
|
|
118761
118803
|
const refreshedResolution = await this.resumeAvailableAttempt(tests, refreshed, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeSnapshot(resumeDecision, refreshed, context2.integration));
|
|
118762
118804
|
if (refreshedResolution.kind === "resumed") {
|
|
118763
|
-
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118805
|
+
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
118764
118806
|
return refreshedResolution.snapshot;
|
|
118765
118807
|
}
|
|
118766
118808
|
decision = this.requireDecision(refreshedResolution.decision, input.purpose);
|
|
@@ -118821,7 +118863,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118821
118863
|
grade: context2.integration.grade
|
|
118822
118864
|
})
|
|
118823
118865
|
});
|
|
118824
|
-
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118866
|
+
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
118825
118867
|
return this.snapshot(result, metadata2, assessment, context2.integration);
|
|
118826
118868
|
});
|
|
118827
118869
|
}
|
|
@@ -118838,8 +118880,8 @@ class TimebackAssessmentRuntimeService {
|
|
|
118838
118880
|
activityId: input.activityId,
|
|
118839
118881
|
purpose: input.purpose,
|
|
118840
118882
|
courseId: row.courseId
|
|
118841
|
-
}))
|
|
118842
|
-
return this.
|
|
118883
|
+
}));
|
|
118884
|
+
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
118843
118885
|
const context2 = await this.selectIntegration(candidates, studentId, input);
|
|
118844
118886
|
const attemptScope = {
|
|
118845
118887
|
studentId,
|
|
@@ -118855,7 +118897,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118855
118897
|
]);
|
|
118856
118898
|
const initialResolution = await this.resumeAvailableAttempt(tests, listing.attempts, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeDiagnosticSnapshot(resumeDecision, listing.attempts, context2.integration, db2));
|
|
118857
118899
|
if (initialResolution.kind === "resumed") {
|
|
118858
|
-
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118900
|
+
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
118859
118901
|
return initialResolution.snapshot;
|
|
118860
118902
|
}
|
|
118861
118903
|
let decision = this.requireDecision(initialResolution.decision, input.purpose);
|
|
@@ -118866,7 +118908,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118866
118908
|
const refreshed = refreshedListing.attempts;
|
|
118867
118909
|
const refreshedResolution = await this.resumeAvailableAttempt(tests, refreshed, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeDiagnosticSnapshot(resumeDecision, refreshed, context2.integration, db2));
|
|
118868
118910
|
if (refreshedResolution.kind === "resumed") {
|
|
118869
|
-
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118911
|
+
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
118870
118912
|
return refreshedResolution.snapshot;
|
|
118871
118913
|
}
|
|
118872
118914
|
decision = this.requireDecision(refreshedResolution.decision, input.purpose);
|
|
@@ -118938,7 +118980,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118938
118980
|
grade: context2.integration.grade
|
|
118939
118981
|
})
|
|
118940
118982
|
});
|
|
118941
|
-
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
118983
|
+
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
118942
118984
|
return this.diagnosticSnapshot(result, metadata2, assessment, context2.integration, routing.state);
|
|
118943
118985
|
});
|
|
118944
118986
|
}
|
|
@@ -118957,8 +118999,8 @@ class TimebackAssessmentRuntimeService {
|
|
|
118957
118999
|
activityId: input.activityId,
|
|
118958
119000
|
purpose: input.purpose,
|
|
118959
119001
|
courseId: row.courseId
|
|
118960
|
-
}))
|
|
118961
|
-
return this.
|
|
119002
|
+
}));
|
|
119003
|
+
return this.deps.assessmentRuntimeLock(this.deps.db, lockKeys, async (db2) => {
|
|
118962
119004
|
const context2 = await this.selectIntegration(candidates, studentId, input);
|
|
118963
119005
|
const attemptScope = {
|
|
118964
119006
|
studentId,
|
|
@@ -118975,7 +119017,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118975
119017
|
const attempts = listing.attempts;
|
|
118976
119018
|
const initialResolution = await this.resumeAvailableAttempt(tests, attempts, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeReviewSnapshot(resumeDecision, attempts, listing.reviewChildren, context2));
|
|
118977
119019
|
if (initialResolution.kind === "resumed") {
|
|
118978
|
-
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
119020
|
+
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
118979
119021
|
return initialResolution.snapshot;
|
|
118980
119022
|
}
|
|
118981
119023
|
let decision = initialResolution.decision;
|
|
@@ -118994,7 +119036,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
118994
119036
|
const refreshed = refreshedListing.attempts;
|
|
118995
119037
|
const refreshedResolution = await this.resumeAvailableAttempt(tests, refreshed, requestFingerprint, pendingSupersessions, (resumeDecision) => this.resumeReviewSnapshot(resumeDecision, refreshed, refreshedListing.reviewChildren, context2));
|
|
118996
119038
|
if (refreshedResolution.kind === "resumed") {
|
|
118997
|
-
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
119039
|
+
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
118998
119040
|
return refreshedResolution.snapshot;
|
|
118999
119041
|
}
|
|
119000
119042
|
const refreshedDecision = refreshedResolution.decision;
|
|
@@ -119088,7 +119130,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
119088
119130
|
existingChildResults: refreshedListing.reviewChildren,
|
|
119089
119131
|
lookupMissingResults: false
|
|
119090
119132
|
});
|
|
119091
|
-
await this.persistPendingSupersessionsBestEffort(pendingSupersessions);
|
|
119133
|
+
await this.persistPendingSupersessionsBestEffort(db2, pendingSupersessions);
|
|
119092
119134
|
this.recordReviewPreparationPhase("provisioning", provisioningStartedAt);
|
|
119093
119135
|
this.recordReviewPreparationPhase("total", preparationStartedAt, {
|
|
119094
119136
|
candidateCount: catalog.bank.items.length,
|
|
@@ -119176,7 +119218,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
119176
119218
|
"app.error.message": errorMessage2(error88)
|
|
119177
119219
|
});
|
|
119178
119220
|
}
|
|
119179
|
-
return this.deps.assessmentRuntimeLock(this.deps.db, assessmentRuntimeAttemptLockKey(params.attemptId), async (db2) => {
|
|
119221
|
+
return this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
119180
119222
|
const attempt = await this.authorizeAttempt(params, {
|
|
119181
119223
|
db: db2,
|
|
119182
119224
|
developerAccessValidated: true
|
|
@@ -119224,7 +119266,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
119224
119266
|
let preview = await this.prepareItemSubmission(params, input);
|
|
119225
119267
|
let preparationAttempts = 1;
|
|
119226
119268
|
while (true) {
|
|
119227
|
-
const committed = await this.deps.assessmentRuntimeLock(this.deps.db, assessmentRuntimeAttemptLockKey(params.attemptId), async (db2) => {
|
|
119269
|
+
const committed = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
119228
119270
|
const attempt = await this.authorizeAttempt(params, {
|
|
119229
119271
|
db: db2,
|
|
119230
119272
|
developerAccessValidated: true
|
|
@@ -119320,7 +119362,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
119320
119362
|
let preview = await this.prepareDiagnosticItemSubmission(params, input);
|
|
119321
119363
|
let preparationAttempts = 1;
|
|
119322
119364
|
while (true) {
|
|
119323
|
-
const committed = await this.deps.assessmentRuntimeLock(this.deps.db, assessmentRuntimeAttemptLockKey(params.attemptId), async (db2) => {
|
|
119365
|
+
const committed = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
119324
119366
|
const attempt = await this.authorizeAttempt(params, {
|
|
119325
119367
|
db: db2,
|
|
119326
119368
|
developerAccessValidated: true
|
|
@@ -119654,7 +119696,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
119654
119696
|
}) {
|
|
119655
119697
|
await this.deps.validateDeveloperAccess(params.user, params.gameId);
|
|
119656
119698
|
const preview = await this.prepareSubmission(params, input);
|
|
119657
|
-
const submission = await this.deps.assessmentRuntimeLock(this.deps.db, assessmentRuntimeAttemptLockKey(params.attemptId), async (db2) => {
|
|
119699
|
+
const submission = await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(params.attemptId)], async (db2) => {
|
|
119658
119700
|
const attempt = await this.authorizeAttempt(params, {
|
|
119659
119701
|
db: db2,
|
|
119660
119702
|
developerAccessValidated: true
|
|
@@ -119977,10 +120019,6 @@ class TimebackAssessmentRuntimeService {
|
|
|
119977
120019
|
enrollment: enrollmentByCourse.get(selection.selected.courseId)
|
|
119978
120020
|
};
|
|
119979
120021
|
}
|
|
119980
|
-
async withStartLocks(keys, operation) {
|
|
119981
|
-
const acquire = (index2, db2) => index2 === keys.length ? operation(db2) : this.deps.assessmentRuntimeLock(db2, keys[index2], (nested) => acquire(index2 + 1, nested));
|
|
119982
|
-
return acquire(0, this.deps.db);
|
|
119983
|
-
}
|
|
119984
120022
|
async liveTests(integrationId, purpose, db2 = this.deps.db, requestedStandard, requestedDiagnosticKey) {
|
|
119985
120023
|
const matchesMasteryStandard = requestedStandard ? masteryStandardMatcher(requestedStandard) : undefined;
|
|
119986
120024
|
const rows = await db2.query.gameTimebackAssessmentTests.findMany({
|
|
@@ -120154,9 +120192,9 @@ class TimebackAssessmentRuntimeService {
|
|
|
120154
120192
|
return null;
|
|
120155
120193
|
}
|
|
120156
120194
|
}
|
|
120157
|
-
async persistPendingSupersessions(attemptIds) {
|
|
120195
|
+
async persistPendingSupersessions(db2, attemptIds) {
|
|
120158
120196
|
for (const attemptId of [...attemptIds].toSorted()) {
|
|
120159
|
-
await this.deps.assessmentRuntimeLock(
|
|
120197
|
+
await this.deps.assessmentRuntimeLock(db2, [assessmentRuntimeAttemptLockKey(attemptId)], async () => {
|
|
120160
120198
|
let result;
|
|
120161
120199
|
try {
|
|
120162
120200
|
result = await this.requireClient().api.oneroster.assessmentResults.get(attemptId);
|
|
@@ -120178,12 +120216,12 @@ class TimebackAssessmentRuntimeService {
|
|
|
120178
120216
|
});
|
|
120179
120217
|
}
|
|
120180
120218
|
}
|
|
120181
|
-
async persistPendingSupersessionsBestEffort(attemptIds) {
|
|
120219
|
+
async persistPendingSupersessionsBestEffort(db2, attemptIds) {
|
|
120182
120220
|
if (attemptIds.size === 0) {
|
|
120183
120221
|
return;
|
|
120184
120222
|
}
|
|
120185
120223
|
try {
|
|
120186
|
-
await this.persistPendingSupersessions(attemptIds);
|
|
120224
|
+
await this.persistPendingSupersessions(db2, attemptIds);
|
|
120187
120225
|
} catch (error88) {
|
|
120188
120226
|
addEvent("assessment.attempt_supersession_persist_failed", {
|
|
120189
120227
|
"app.assessment.attempt_ids": [...attemptIds].toSorted().join(","),
|
|
@@ -120809,7 +120847,7 @@ class TimebackAssessmentRuntimeService {
|
|
|
120809
120847
|
async crossAttemptLockBarrier(attemptId) {
|
|
120810
120848
|
for (let attempt = 1;attempt <= TimebackAssessmentRuntimeService.PROJECTION_BARRIER_MAX_ATTEMPTS; attempt += 1) {
|
|
120811
120849
|
try {
|
|
120812
|
-
await this.deps.assessmentRuntimeLock(this.deps.db, assessmentRuntimeAttemptLockKey(attemptId), async () => {
|
|
120850
|
+
await this.deps.assessmentRuntimeLock(this.deps.db, [assessmentRuntimeAttemptLockKey(attemptId)], async () => {
|
|
120813
120851
|
return;
|
|
120814
120852
|
});
|
|
120815
120853
|
return;
|
|
@@ -125298,7 +125336,7 @@ var init_upload_service = __esm(() => {
|
|
|
125298
125336
|
function createPlatformServices(deps) {
|
|
125299
125337
|
const {
|
|
125300
125338
|
db: db2,
|
|
125301
|
-
|
|
125339
|
+
assessmentRuntimeLock,
|
|
125302
125340
|
config: config5,
|
|
125303
125341
|
cloudflare: cloudflare2,
|
|
125304
125342
|
corsKvs,
|
|
@@ -125367,7 +125405,7 @@ function createPlatformServices(deps) {
|
|
|
125367
125405
|
db: db2,
|
|
125368
125406
|
timeback: timebackClient,
|
|
125369
125407
|
validateDeveloperAccess,
|
|
125370
|
-
assessmentRuntimeLock
|
|
125408
|
+
assessmentRuntimeLock
|
|
125371
125409
|
});
|
|
125372
125410
|
return {
|
|
125373
125411
|
bucket,
|
|
@@ -125385,7 +125423,6 @@ function createPlatformServices(deps) {
|
|
|
125385
125423
|
};
|
|
125386
125424
|
}
|
|
125387
125425
|
var init_platform2 = __esm(async () => {
|
|
125388
|
-
init_assessment_runtime_lock_util();
|
|
125389
125426
|
init_bucket_service();
|
|
125390
125427
|
init_database_service();
|
|
125391
125428
|
init_deployment_state_service();
|
|
@@ -126065,7 +126102,7 @@ var init_standalone = __esm(() => {
|
|
|
126065
126102
|
init_verify_service();
|
|
126066
126103
|
});
|
|
126067
126104
|
function createServices(ctx) {
|
|
126068
|
-
const { db: db2,
|
|
126105
|
+
const { db: db2, assessmentRuntimeLock, config: config5, providers, cloudflare: cloudflare2, timeback: timeback2, discord, corsKvs } = ctx;
|
|
126069
126106
|
const { auth: auth2, storage, r2Storage, cache } = providers;
|
|
126070
126107
|
const infra2 = createInfraServices({
|
|
126071
126108
|
db: db2,
|
|
@@ -126087,7 +126124,7 @@ function createServices(ctx) {
|
|
|
126087
126124
|
});
|
|
126088
126125
|
const platform2 = createPlatformServices({
|
|
126089
126126
|
db: db2,
|
|
126090
|
-
|
|
126127
|
+
assessmentRuntimeLock,
|
|
126091
126128
|
config: config5,
|
|
126092
126129
|
cloudflare: cloudflare2,
|
|
126093
126130
|
corsKvs,
|
|
@@ -126114,6 +126151,324 @@ var init_factory = __esm(async () => {
|
|
|
126114
126151
|
init_standalone();
|
|
126115
126152
|
await init_platform2();
|
|
126116
126153
|
});
|
|
126154
|
+
function isAuthenticated(ctx) {
|
|
126155
|
+
return ctx.user != null;
|
|
126156
|
+
}
|
|
126157
|
+
var init_types5 = () => {};
|
|
126158
|
+
function hasGameManagementAccess(user) {
|
|
126159
|
+
return user.role === "admin" || user.role === "teacher" || user.role === "developer" && user.developerStatus === "approved";
|
|
126160
|
+
}
|
|
126161
|
+
function isDashboardWorkerKey(key) {
|
|
126162
|
+
return Boolean(key?.permissions?.[DASHBOARD_PERMISSION_NAMESPACE]);
|
|
126163
|
+
}
|
|
126164
|
+
function workerKeyGrantsGameRead(key, slug2) {
|
|
126165
|
+
if (!isDashboardWorkerKey(key) && !isGameWorkerKey(key)) {
|
|
126166
|
+
return false;
|
|
126167
|
+
}
|
|
126168
|
+
const games2 = key?.permissions?.games;
|
|
126169
|
+
return Boolean(games2?.includes(`read:${slug2}`) || games2?.includes(`write:${slug2}`));
|
|
126170
|
+
}
|
|
126171
|
+
function isGameWorkerKey(key) {
|
|
126172
|
+
return Boolean(key?.name?.startsWith(GAME_WORKER_KEY_PREFIX));
|
|
126173
|
+
}
|
|
126174
|
+
function deniedWorkerKeyRequest(key, method, pathname) {
|
|
126175
|
+
if (isDashboardWorkerKey(key) && !isAllowedDashboardWorkerRequest(method, pathname, key.permissions)) {
|
|
126176
|
+
return DASHBOARD_WORKER_KEY_RESTRICTED;
|
|
126177
|
+
}
|
|
126178
|
+
return null;
|
|
126179
|
+
}
|
|
126180
|
+
function rejectDashboardWorkerKey(ctx) {
|
|
126181
|
+
if (isDashboardWorkerKey(ctx.apiKey)) {
|
|
126182
|
+
throw ApiError.forbidden(DASHBOARD_WORKER_KEY_RESTRICTED);
|
|
126183
|
+
}
|
|
126184
|
+
}
|
|
126185
|
+
function assertAuthenticatedRequest(ctx) {
|
|
126186
|
+
if (!isAuthenticated(ctx)) {
|
|
126187
|
+
throw ApiError.unauthorized("Valid session or bearer token required");
|
|
126188
|
+
}
|
|
126189
|
+
if (ctx.apiKey) {
|
|
126190
|
+
const denied = deniedWorkerKeyRequest(ctx.apiKey, ctx.request.method, ctx.url.pathname);
|
|
126191
|
+
if (denied) {
|
|
126192
|
+
throw ApiError.forbidden(denied);
|
|
126193
|
+
}
|
|
126194
|
+
}
|
|
126195
|
+
}
|
|
126196
|
+
function requireAuth(handler) {
|
|
126197
|
+
return async (ctx) => {
|
|
126198
|
+
assertAuthenticatedRequest(ctx);
|
|
126199
|
+
return handler(ctx);
|
|
126200
|
+
};
|
|
126201
|
+
}
|
|
126202
|
+
function requireNonAnonymous(handler) {
|
|
126203
|
+
return async (ctx) => {
|
|
126204
|
+
assertAuthenticatedRequest(ctx);
|
|
126205
|
+
if (ctx.user.isAnonymous) {
|
|
126206
|
+
throw ApiError.forbidden("This operation is not available for demo/anonymous users");
|
|
126207
|
+
}
|
|
126208
|
+
return handler(ctx);
|
|
126209
|
+
};
|
|
126210
|
+
}
|
|
126211
|
+
function requireAnonymous(handler) {
|
|
126212
|
+
return async (ctx) => {
|
|
126213
|
+
assertAuthenticatedRequest(ctx);
|
|
126214
|
+
if (!ctx.user.isAnonymous) {
|
|
126215
|
+
throw ApiError.forbidden("This operation is only available for demo/anonymous users");
|
|
126216
|
+
}
|
|
126217
|
+
return handler(ctx);
|
|
126218
|
+
};
|
|
126219
|
+
}
|
|
126220
|
+
function requireDeveloper(handler) {
|
|
126221
|
+
return async (ctx) => {
|
|
126222
|
+
assertAuthenticatedRequest(ctx);
|
|
126223
|
+
rejectDashboardWorkerKey(ctx);
|
|
126224
|
+
const isAdmin = ctx.user.role === "admin";
|
|
126225
|
+
const isApprovedDev = ctx.user.role === "developer" && ctx.user.developerStatus === "approved";
|
|
126226
|
+
if (!isAdmin && !isApprovedDev) {
|
|
126227
|
+
throw ApiError.forbidden("Must be an approved developer");
|
|
126228
|
+
}
|
|
126229
|
+
return handler(ctx);
|
|
126230
|
+
};
|
|
126231
|
+
}
|
|
126232
|
+
function requireHumanDeveloper(handler) {
|
|
126233
|
+
return requireDeveloper(async (ctx) => {
|
|
126234
|
+
if (isDashboardWorkerKey(ctx.apiKey) || isGameWorkerKey(ctx.apiKey)) {
|
|
126235
|
+
throw ApiError.forbidden(WORKER_KEY_HUMAN_ONLY);
|
|
126236
|
+
}
|
|
126237
|
+
return handler(ctx);
|
|
126238
|
+
});
|
|
126239
|
+
}
|
|
126240
|
+
function requireGameManagementAccess(handler) {
|
|
126241
|
+
return async (ctx) => {
|
|
126242
|
+
assertAuthenticatedRequest(ctx);
|
|
126243
|
+
rejectDashboardWorkerKey(ctx);
|
|
126244
|
+
if (!hasGameManagementAccess(ctx.user)) {
|
|
126245
|
+
throw ApiError.forbidden("Game management access required");
|
|
126246
|
+
}
|
|
126247
|
+
return handler(ctx);
|
|
126248
|
+
};
|
|
126249
|
+
}
|
|
126250
|
+
var WORKER_KEY_HUMAN_ONLY = "This operation requires your own credential — platform worker keys are refused";
|
|
126251
|
+
var init_auth_util = __esm(() => {
|
|
126252
|
+
init_errors2();
|
|
126253
|
+
init_types5();
|
|
126254
|
+
init_dashboard_util();
|
|
126255
|
+
init_deployment_util();
|
|
126256
|
+
});
|
|
126257
|
+
function defineControllerNames(namespace, handlers) {
|
|
126258
|
+
for (const [key, handler] of Object.entries(handlers)) {
|
|
126259
|
+
Object.defineProperty(handler, "name", {
|
|
126260
|
+
value: `${namespace}.${key}`,
|
|
126261
|
+
configurable: true
|
|
126262
|
+
});
|
|
126263
|
+
}
|
|
126264
|
+
return handlers;
|
|
126265
|
+
}
|
|
126266
|
+
function generateUsername(email5) {
|
|
126267
|
+
const baseUsername = (email5.split("@")[0] || "user").toLowerCase();
|
|
126268
|
+
const cleanUsername = baseUsername.replace(/[^a-z0-9]/g, "");
|
|
126269
|
+
const randomSuffix = Math.random().toString(36).substring(2, 7);
|
|
126270
|
+
return `${cleanUsername}_${randomSuffix}`;
|
|
126271
|
+
}
|
|
126272
|
+
function extractRedirectPath(targetUri, currentHost) {
|
|
126273
|
+
try {
|
|
126274
|
+
const targetUrl = new URL(targetUri);
|
|
126275
|
+
if (targetUrl.hostname === currentHost) {
|
|
126276
|
+
return targetUrl.pathname + targetUrl.search;
|
|
126277
|
+
}
|
|
126278
|
+
} catch {}
|
|
126279
|
+
return "/";
|
|
126280
|
+
}
|
|
126281
|
+
function validateLtiClaims(claims) {
|
|
126282
|
+
const messageType = claims["https://purl.imsglobal.org/spec/lti/claim/message_type"];
|
|
126283
|
+
const version4 = claims["https://purl.imsglobal.org/spec/lti/claim/version"];
|
|
126284
|
+
if (messageType !== "LtiResourceLinkRequest") {
|
|
126285
|
+
return `Invalid LTI message type: ${messageType}`;
|
|
126286
|
+
}
|
|
126287
|
+
if (version4 !== "1.3.0") {
|
|
126288
|
+
return `Unsupported LTI version: ${version4}`;
|
|
126289
|
+
}
|
|
126290
|
+
return null;
|
|
126291
|
+
}
|
|
126292
|
+
var init_lti_util = () => {};
|
|
126293
|
+
async function provisionLtiUser(db2, claims) {
|
|
126294
|
+
const database = db2;
|
|
126295
|
+
const email5 = claims.email;
|
|
126296
|
+
const ltiTimebackId = claims.sub;
|
|
126297
|
+
const providerId = AUTH_PROVIDER_IDS.TIMEBACK_LTI;
|
|
126298
|
+
if (!email5) {
|
|
126299
|
+
throw new ValidationError("Email is required in LTI claims");
|
|
126300
|
+
}
|
|
126301
|
+
const existingAccount = await database.query.accounts.findFirst({
|
|
126302
|
+
where: and(eq(accounts.accountId, ltiTimebackId), eq(accounts.providerId, providerId))
|
|
126303
|
+
});
|
|
126304
|
+
if (existingAccount) {
|
|
126305
|
+
const user = await database.query.users.findFirst({
|
|
126306
|
+
where: eq(users.id, existingAccount.userId)
|
|
126307
|
+
});
|
|
126308
|
+
if (user) {
|
|
126309
|
+
setAttribute("app.lti.provision", "existing_account");
|
|
126310
|
+
return user;
|
|
126311
|
+
}
|
|
126312
|
+
}
|
|
126313
|
+
const existingUser = await database.query.users.findFirst({
|
|
126314
|
+
where: eq(users.email, email5)
|
|
126315
|
+
});
|
|
126316
|
+
if (existingUser) {
|
|
126317
|
+
await database.transaction(async (tx) => {
|
|
126318
|
+
const existingLtiAccount = await tx.query.accounts.findFirst({
|
|
126319
|
+
where: and(eq(accounts.userId, existingUser.id), eq(accounts.providerId, providerId))
|
|
126320
|
+
});
|
|
126321
|
+
if (!existingLtiAccount) {
|
|
126322
|
+
const [account] = await tx.insert(accounts).values({
|
|
126323
|
+
id: crypto4.randomUUID(),
|
|
126324
|
+
userId: existingUser.id,
|
|
126325
|
+
accountId: ltiTimebackId,
|
|
126326
|
+
providerId,
|
|
126327
|
+
accessToken: null,
|
|
126328
|
+
refreshToken: null,
|
|
126329
|
+
accessTokenExpiresAt: null,
|
|
126330
|
+
refreshTokenExpiresAt: null,
|
|
126331
|
+
createdAt: new Date,
|
|
126332
|
+
updatedAt: new Date
|
|
126333
|
+
}).returning({ id: accounts.id });
|
|
126334
|
+
if (!account) {
|
|
126335
|
+
throw new InternalError("Failed to link LTI account");
|
|
126336
|
+
}
|
|
126337
|
+
}
|
|
126338
|
+
});
|
|
126339
|
+
setAttribute("app.lti.provision", "linked_account");
|
|
126340
|
+
return existingUser;
|
|
126341
|
+
}
|
|
126342
|
+
const newUserId = crypto4.randomUUID();
|
|
126343
|
+
const createdUser = await database.transaction(async (tx) => {
|
|
126344
|
+
const [insertedUser] = await tx.insert(users).values({
|
|
126345
|
+
id: newUserId,
|
|
126346
|
+
email: email5,
|
|
126347
|
+
emailVerified: true,
|
|
126348
|
+
username: generateUsername(email5),
|
|
126349
|
+
name: claims.name || claims.given_name || email5.split("@")[0] || "Timeback User",
|
|
126350
|
+
createdAt: new Date,
|
|
126351
|
+
updatedAt: new Date
|
|
126352
|
+
}).returning();
|
|
126353
|
+
if (!insertedUser) {
|
|
126354
|
+
throw new InternalError("Failed to create user");
|
|
126355
|
+
}
|
|
126356
|
+
await tx.insert(accounts).values({
|
|
126357
|
+
id: crypto4.randomUUID(),
|
|
126358
|
+
userId: newUserId,
|
|
126359
|
+
accountId: ltiTimebackId,
|
|
126360
|
+
providerId,
|
|
126361
|
+
accessToken: null,
|
|
126362
|
+
refreshToken: null,
|
|
126363
|
+
accessTokenExpiresAt: null,
|
|
126364
|
+
refreshTokenExpiresAt: null,
|
|
126365
|
+
createdAt: new Date,
|
|
126366
|
+
updatedAt: new Date
|
|
126367
|
+
});
|
|
126368
|
+
setAttribute("app.lti.provision", "created_user");
|
|
126369
|
+
return insertedUser;
|
|
126370
|
+
});
|
|
126371
|
+
return createdUser;
|
|
126372
|
+
}
|
|
126373
|
+
var init_lti_provisioning = __esm(() => {
|
|
126374
|
+
init_drizzle_orm();
|
|
126375
|
+
init_src();
|
|
126376
|
+
init_tables_index();
|
|
126377
|
+
init_spans();
|
|
126378
|
+
init_errors2();
|
|
126379
|
+
init_lti_util();
|
|
126380
|
+
});
|
|
126381
|
+
function requireGameId(gameId) {
|
|
126382
|
+
if (!gameId) {
|
|
126383
|
+
throw ApiError.badRequest("Missing game ID");
|
|
126384
|
+
}
|
|
126385
|
+
if (!isValidUUID(gameId)) {
|
|
126386
|
+
throw ApiError.unprocessableEntity("gameId must be a valid UUID format");
|
|
126387
|
+
}
|
|
126388
|
+
return gameId;
|
|
126389
|
+
}
|
|
126390
|
+
function requireSlug(slug2) {
|
|
126391
|
+
if (!slug2) {
|
|
126392
|
+
throw ApiError.badRequest("Missing game slug");
|
|
126393
|
+
}
|
|
126394
|
+
return slug2;
|
|
126395
|
+
}
|
|
126396
|
+
function requireUserId(userId) {
|
|
126397
|
+
if (!userId) {
|
|
126398
|
+
throw ApiError.badRequest("Missing user ID");
|
|
126399
|
+
}
|
|
126400
|
+
if (!isValidUUID(userId)) {
|
|
126401
|
+
throw ApiError.unprocessableEntity("userId must be a valid UUID format");
|
|
126402
|
+
}
|
|
126403
|
+
return userId;
|
|
126404
|
+
}
|
|
126405
|
+
function parseLimitParam(url4, max) {
|
|
126406
|
+
const raw = url4.searchParams.get("limit");
|
|
126407
|
+
if (raw === null) {
|
|
126408
|
+
return;
|
|
126409
|
+
}
|
|
126410
|
+
const limit = Number(raw);
|
|
126411
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > max) {
|
|
126412
|
+
throw ApiError.badRequest(`limit must be an integer between 1 and ${max}`);
|
|
126413
|
+
}
|
|
126414
|
+
return limit;
|
|
126415
|
+
}
|
|
126416
|
+
var init_params_util = __esm(() => {
|
|
126417
|
+
init_src9();
|
|
126418
|
+
init_errors2();
|
|
126419
|
+
});
|
|
126420
|
+
function formatZodError(error88) {
|
|
126421
|
+
const flat = error88.flatten();
|
|
126422
|
+
const result = {};
|
|
126423
|
+
if (Object.keys(flat.fieldErrors).length > 0) {
|
|
126424
|
+
result.fields = {};
|
|
126425
|
+
for (const [field, messages] of Object.entries(flat.fieldErrors)) {
|
|
126426
|
+
if (messages && messages.length > 0) {
|
|
126427
|
+
result.fields[field] = messages[0];
|
|
126428
|
+
}
|
|
126429
|
+
}
|
|
126430
|
+
}
|
|
126431
|
+
if (flat.formErrors.length > 0) {
|
|
126432
|
+
result.errors = flat.formErrors;
|
|
126433
|
+
}
|
|
126434
|
+
return result;
|
|
126435
|
+
}
|
|
126436
|
+
function recordValidationFailure(details) {
|
|
126437
|
+
setAttribute("app.request.outcome", "validation_failed");
|
|
126438
|
+
addEvent("request.validation_failed", {
|
|
126439
|
+
"app.validation.error": JSON.stringify(details)
|
|
126440
|
+
});
|
|
126441
|
+
}
|
|
126442
|
+
async function parseRequestBody(request, schema2) {
|
|
126443
|
+
try {
|
|
126444
|
+
return schema2.parse(await request.json());
|
|
126445
|
+
} catch (error88) {
|
|
126446
|
+
if (error88 instanceof exports_external.ZodError) {
|
|
126447
|
+
const details = formatZodError(error88);
|
|
126448
|
+
recordValidationFailure(details);
|
|
126449
|
+
throw ApiError.unprocessableEntity("Validation failed", details);
|
|
126450
|
+
}
|
|
126451
|
+
throw ApiError.invalidJsonBody();
|
|
126452
|
+
}
|
|
126453
|
+
}
|
|
126454
|
+
var init_validation_util = __esm(() => {
|
|
126455
|
+
init_esm();
|
|
126456
|
+
init_spans();
|
|
126457
|
+
init_errors2();
|
|
126458
|
+
});
|
|
126459
|
+
var init_utils7 = __esm(() => {
|
|
126460
|
+
init_auth_util();
|
|
126461
|
+
init_assessment_runtime_lock_util();
|
|
126462
|
+
init_dashboard_util();
|
|
126463
|
+
init_deployment_util();
|
|
126464
|
+
init_leaderboard_util();
|
|
126465
|
+
init_lti_util();
|
|
126466
|
+
init_lti_provisioning();
|
|
126467
|
+
init_params_util();
|
|
126468
|
+
init_secrets_util();
|
|
126469
|
+
init_timeback_util();
|
|
126470
|
+
init_validation_util();
|
|
126471
|
+
});
|
|
126117
126472
|
function buildTimebackClient() {
|
|
126118
126473
|
if (!hasTimebackCredentials()) {
|
|
126119
126474
|
return;
|
|
@@ -126486,7 +126841,7 @@ function createSandboxContext(options) {
|
|
|
126486
126841
|
const services = {};
|
|
126487
126842
|
const ctx = {
|
|
126488
126843
|
db: options.db,
|
|
126489
|
-
|
|
126844
|
+
assessmentRuntimeLock: createInMemoryAssessmentRuntimeLock(),
|
|
126490
126845
|
config: config5,
|
|
126491
126846
|
providers,
|
|
126492
126847
|
services,
|
|
@@ -126515,7 +126870,7 @@ var cachedServiceContext = null;
|
|
|
126515
126870
|
var cachedDb = null;
|
|
126516
126871
|
var init_context = __esm(async () => {
|
|
126517
126872
|
init_config2();
|
|
126518
|
-
|
|
126873
|
+
init_utils7();
|
|
126519
126874
|
init_src6();
|
|
126520
126875
|
init_providers();
|
|
126521
126876
|
await __promiseAll([
|
|
@@ -143824,7 +144179,7 @@ var nullableType2;
|
|
|
143824
144179
|
var preprocessType2;
|
|
143825
144180
|
var pipelineType2;
|
|
143826
144181
|
var coerce2;
|
|
143827
|
-
var
|
|
144182
|
+
var init_types6;
|
|
143828
144183
|
var init_external4;
|
|
143829
144184
|
var init_v32;
|
|
143830
144185
|
var init_esm2;
|
|
@@ -144010,7 +144365,7 @@ var prepareMigrationMeta;
|
|
|
144010
144365
|
var schemaRenameKey;
|
|
144011
144366
|
var tableRenameKey;
|
|
144012
144367
|
var columnRenameKey;
|
|
144013
|
-
var
|
|
144368
|
+
var init_utils8;
|
|
144014
144369
|
var import_hanji;
|
|
144015
144370
|
var warning;
|
|
144016
144371
|
var error88;
|
|
@@ -145549,7 +145904,7 @@ var PrimaryKey3;
|
|
|
145549
145904
|
var init_primary_keys3;
|
|
145550
145905
|
var MySqlViewConfig;
|
|
145551
145906
|
var init_view_common32;
|
|
145552
|
-
var
|
|
145907
|
+
var init_utils82;
|
|
145553
145908
|
var _a3222;
|
|
145554
145909
|
var _b239;
|
|
145555
145910
|
var MySqlDeleteBase;
|
|
@@ -146297,7 +146652,7 @@ var sqlitePushIntrospect = async (db2, filters) => {
|
|
|
146297
146652
|
};
|
|
146298
146653
|
var generateDrizzleJson = (imports, prevId, schemaFilters, casing2) => {
|
|
146299
146654
|
const prepared = prepareFromExports(imports);
|
|
146300
|
-
const id =
|
|
146655
|
+
const id = randomUUID2();
|
|
146301
146656
|
const snapshot = generatePgSnapshot(prepared.tables, prepared.enums, prepared.schemas, prepared.sequences, prepared.roles, prepared.policies, prepared.views, prepared.matViews, casing2, schemaFilters);
|
|
146302
146657
|
return fillPgSnapshot({
|
|
146303
146658
|
serialized: snapshot,
|
|
@@ -146346,7 +146701,7 @@ var pushSchema = async (imports, drizzleInstance, schemaFilters, tablesFilter, e
|
|
|
146346
146701
|
var generateSQLiteDrizzleJson = async (imports, prevId, casing2) => {
|
|
146347
146702
|
const { prepareFromExports: prepareFromExports5 } = await Promise.resolve().then(() => (init_sqliteImports(), sqliteImports_exports));
|
|
146348
146703
|
const prepared = prepareFromExports5(imports);
|
|
146349
|
-
const id =
|
|
146704
|
+
const id = randomUUID2();
|
|
146350
146705
|
const snapshot = generateSqliteSnapshot(prepared.tables, prepared.views, casing2);
|
|
146351
146706
|
return {
|
|
146352
146707
|
...snapshot,
|
|
@@ -146397,7 +146752,7 @@ var pushSQLiteSchema = async (imports, drizzleInstance) => {
|
|
|
146397
146752
|
var generateMySQLDrizzleJson = async (imports, prevId, casing2) => {
|
|
146398
146753
|
const { prepareFromExports: prepareFromExports5 } = await Promise.resolve().then(() => (init_mysqlImports(), mysqlImports_exports));
|
|
146399
146754
|
const prepared = prepareFromExports5(imports);
|
|
146400
|
-
const id =
|
|
146755
|
+
const id = randomUUID2();
|
|
146401
146756
|
const snapshot = generateMySqlSnapshot(prepared.tables, prepared.views, casing2);
|
|
146402
146757
|
return {
|
|
146403
146758
|
...snapshot,
|
|
@@ -146447,7 +146802,7 @@ var pushMySQLSchema = async (imports, drizzleInstance, databaseName) => {
|
|
|
146447
146802
|
var generateSingleStoreDrizzleJson = async (imports, prevId, casing2) => {
|
|
146448
146803
|
const { prepareFromExports: prepareFromExports5 } = await Promise.resolve().then(() => (init_singlestoreImports(), singlestoreImports_exports));
|
|
146449
146804
|
const prepared = prepareFromExports5(imports);
|
|
146450
|
-
const id =
|
|
146805
|
+
const id = randomUUID2();
|
|
146451
146806
|
const snapshot = generateSingleStoreSnapshot(prepared.tables, casing2);
|
|
146452
146807
|
return {
|
|
146453
146808
|
...snapshot,
|
|
@@ -150062,7 +150417,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
|
|
|
150062
150417
|
})(errorUtil2 || (errorUtil2 = {}));
|
|
150063
150418
|
}
|
|
150064
150419
|
});
|
|
150065
|
-
|
|
150420
|
+
init_types6 = __esm2({
|
|
150066
150421
|
"../node_modules/.pnpm/zod@3.25.42/node_modules/zod/dist/esm/v3/types.js"() {
|
|
150067
150422
|
init_ZodError2();
|
|
150068
150423
|
init_errors9();
|
|
@@ -153227,7 +153582,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
|
|
|
153227
153582
|
init_parseUtil2();
|
|
153228
153583
|
init_typeAliases2();
|
|
153229
153584
|
init_util5();
|
|
153230
|
-
|
|
153585
|
+
init_types6();
|
|
153231
153586
|
init_ZodError2();
|
|
153232
153587
|
}
|
|
153233
153588
|
});
|
|
@@ -154960,7 +155315,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
|
|
|
154960
155315
|
backwardCompatibleSqliteSchema = unionType2([sqliteSchemaV5, schema3]);
|
|
154961
155316
|
}
|
|
154962
155317
|
});
|
|
154963
|
-
|
|
155318
|
+
init_utils8 = __esm2({
|
|
154964
155319
|
"src/utils.ts"() {
|
|
154965
155320
|
init_views();
|
|
154966
155321
|
init_global2();
|
|
@@ -155012,7 +155367,7 @@ See: https://github.com/isaacs/node-glob/issues/167`);
|
|
|
155012
155367
|
"src/cli/views.ts"() {
|
|
155013
155368
|
init_source();
|
|
155014
155369
|
import_hanji = __toESM5(require_hanji());
|
|
155015
|
-
|
|
155370
|
+
init_utils8();
|
|
155016
155371
|
warning = (msg) => {
|
|
155017
155372
|
(0, import_hanji.render)(`[${source_default.yellow("Warning")}] ${msg}`);
|
|
155018
155373
|
};
|
|
@@ -157720,7 +158075,7 @@ Is ${source_default.bold.blue(this.base.name)} schema created or renamed from an
|
|
|
157720
158075
|
init_pgSchema();
|
|
157721
158076
|
init_singlestoreSchema();
|
|
157722
158077
|
init_sqliteSchema();
|
|
157723
|
-
|
|
158078
|
+
init_utils8();
|
|
157724
158079
|
parseType = (schemaPrefix, type) => {
|
|
157725
158080
|
const pgNativeTypes = [
|
|
157726
158081
|
"uuid",
|
|
@@ -160216,7 +160571,7 @@ ${BREAKPOINT}ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${statement.newC
|
|
|
160216
160571
|
init_source();
|
|
160217
160572
|
init_sqliteSchema();
|
|
160218
160573
|
init_sqlgenerator();
|
|
160219
|
-
|
|
160574
|
+
init_utils8();
|
|
160220
160575
|
_moveDataStatements = (tableName, json42, dataLoss = false) => {
|
|
160221
160576
|
const statements = [];
|
|
160222
160577
|
const newTableName = `__new_${tableName}`;
|
|
@@ -162374,7 +162729,7 @@ ${BREAKPOINT}ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${statement.newC
|
|
|
162374
162729
|
init_singlestoreSchema();
|
|
162375
162730
|
init_sqliteSchema();
|
|
162376
162731
|
init_statementCombiner();
|
|
162377
|
-
|
|
162732
|
+
init_utils8();
|
|
162378
162733
|
makeChanged = (schema5) => {
|
|
162379
162734
|
return objectType2({
|
|
162380
162735
|
type: enumType2(["changed"]),
|
|
@@ -164599,7 +164954,7 @@ ${BREAKPOINT}ALTER TABLE ${tableNameWithSchema} ADD CONSTRAINT "${statement.newC
|
|
|
164599
164954
|
init_pgSchema();
|
|
164600
164955
|
init_sqliteSchema();
|
|
164601
164956
|
init_snapshotsDiffer();
|
|
164602
|
-
|
|
164957
|
+
init_utils8();
|
|
164603
164958
|
init_words();
|
|
164604
164959
|
init_outputs();
|
|
164605
164960
|
init_views();
|
|
@@ -171159,7 +171514,7 @@ params: ${params}`);
|
|
|
171159
171514
|
init_pg_core2();
|
|
171160
171515
|
init_vector32();
|
|
171161
171516
|
init_outputs();
|
|
171162
|
-
|
|
171517
|
+
init_utils8();
|
|
171163
171518
|
init_utils62();
|
|
171164
171519
|
indexName = (tableName, columns2) => {
|
|
171165
171520
|
return `${tableName}_${columns2.join("_")}_index`;
|
|
@@ -175168,7 +175523,7 @@ ORDER BY
|
|
|
175168
175523
|
init_dist9();
|
|
175169
175524
|
init_sqlite_core();
|
|
175170
175525
|
init_outputs();
|
|
175171
|
-
|
|
175526
|
+
init_utils8();
|
|
175172
175527
|
init_utils62();
|
|
175173
175528
|
generateSqliteSnapshot = (tables, views, casing2) => {
|
|
175174
175529
|
const dialect6 = new SQLiteSyncDialect({ casing: casing2 });
|
|
@@ -177121,7 +177476,7 @@ ${withStyle.errorWarning(`We've found duplicated view name across ${source_defau
|
|
|
177121
177476
|
MySqlViewConfig = Symbol.for("drizzle:MySqlViewConfig");
|
|
177122
177477
|
}
|
|
177123
177478
|
});
|
|
177124
|
-
|
|
177479
|
+
init_utils82 = __esm2({
|
|
177125
177480
|
"../drizzle-orm/dist/mysql-core/utils.js"() {
|
|
177126
177481
|
init_entity2();
|
|
177127
177482
|
init_dist9();
|
|
@@ -177143,7 +177498,7 @@ ${withStyle.errorWarning(`We've found duplicated view name across ${source_defau
|
|
|
177143
177498
|
init_query_promise2();
|
|
177144
177499
|
init_selection_proxy2();
|
|
177145
177500
|
init_table8();
|
|
177146
|
-
|
|
177501
|
+
init_utils82();
|
|
177147
177502
|
MySqlDeleteBase = class extends (_b239 = QueryPromise2, _a3222 = entityKind2, _b239) {
|
|
177148
177503
|
constructor(table62, session3, dialect6, withList) {
|
|
177149
177504
|
super();
|
|
@@ -177991,7 +178346,7 @@ ${withStyle.errorWarning(`We've found duplicated view name across ${source_defau
|
|
|
177991
178346
|
init_table8();
|
|
177992
178347
|
init_utils22();
|
|
177993
178348
|
init_view_common3();
|
|
177994
|
-
|
|
178349
|
+
init_utils82();
|
|
177995
178350
|
init_view_base3();
|
|
177996
178351
|
_a325 = entityKind2;
|
|
177997
178352
|
MySqlSelectBuilder = class {
|
|
@@ -178390,7 +178745,7 @@ ${withStyle.errorWarning(`We've found duplicated view name across ${source_defau
|
|
|
178390
178745
|
init_sql5();
|
|
178391
178746
|
init_table8();
|
|
178392
178747
|
init_utils22();
|
|
178393
|
-
|
|
178748
|
+
init_utils82();
|
|
178394
178749
|
init_query_builder4();
|
|
178395
178750
|
_a329 = entityKind2;
|
|
178396
178751
|
MySqlInsertBuilder = class {
|
|
@@ -178494,7 +178849,7 @@ ${withStyle.errorWarning(`We've found duplicated view name across ${source_defau
|
|
|
178494
178849
|
init_selection_proxy2();
|
|
178495
178850
|
init_table8();
|
|
178496
178851
|
init_utils22();
|
|
178497
|
-
|
|
178852
|
+
init_utils82();
|
|
178498
178853
|
_a331 = entityKind2;
|
|
178499
178854
|
MySqlUpdateBuilder = class {
|
|
178500
178855
|
constructor(table62, session3, dialect6, withList) {
|
|
@@ -179030,7 +179385,7 @@ ${withStyle.errorWarning(`We've found duplicated view name across ${source_defau
|
|
|
179030
179385
|
init_subquery4();
|
|
179031
179386
|
init_table42();
|
|
179032
179387
|
init_unique_constraint3();
|
|
179033
|
-
|
|
179388
|
+
init_utils82();
|
|
179034
179389
|
init_view_common32();
|
|
179035
179390
|
init_view3();
|
|
179036
179391
|
}
|
|
@@ -179041,7 +179396,7 @@ ${withStyle.errorWarning(`We've found duplicated view name across ${source_defau
|
|
|
179041
179396
|
init_dist9();
|
|
179042
179397
|
init_mysql_core();
|
|
179043
179398
|
init_outputs();
|
|
179044
|
-
|
|
179399
|
+
init_utils8();
|
|
179045
179400
|
init_utils62();
|
|
179046
179401
|
handleEnumType = (type) => {
|
|
179047
179402
|
let str = type.split("(")[1];
|
|
@@ -183475,7 +183830,7 @@ The unique index ${source_default.underline.blue(name22)} on the ${source_defaul
|
|
|
183475
183830
|
import_hanji10 = __toESM5(require_hanji());
|
|
183476
183831
|
init_sqlgenerator();
|
|
183477
183832
|
init_singlestoreSchema();
|
|
183478
|
-
|
|
183833
|
+
init_utils8();
|
|
183479
183834
|
init_selector_ui();
|
|
183480
183835
|
init_outputs();
|
|
183481
183836
|
filterStatements2 = (statements, currentSchema, prevSchema) => {
|
|
@@ -183738,7 +184093,7 @@ The unique index ${source_default.underline.blue(name22)} on the ${source_defaul
|
|
|
183738
184093
|
init_sqlgenerator();
|
|
183739
184094
|
init_selector_ui();
|
|
183740
184095
|
init_pgSchema();
|
|
183741
|
-
|
|
184096
|
+
init_utils8();
|
|
183742
184097
|
import_hanji6 = __toESM5(require_hanji());
|
|
183743
184098
|
init_mjs();
|
|
183744
184099
|
init_global2();
|
|
@@ -184601,323 +184956,6 @@ var init_manifest = __esm(async () => {
|
|
|
184601
184956
|
});
|
|
184602
184957
|
});
|
|
184603
184958
|
});
|
|
184604
|
-
function isAuthenticated(ctx) {
|
|
184605
|
-
return ctx.user != null;
|
|
184606
|
-
}
|
|
184607
|
-
var init_types6 = () => {};
|
|
184608
|
-
function hasGameManagementAccess(user) {
|
|
184609
|
-
return user.role === "admin" || user.role === "teacher" || user.role === "developer" && user.developerStatus === "approved";
|
|
184610
|
-
}
|
|
184611
|
-
function isDashboardWorkerKey(key) {
|
|
184612
|
-
return Boolean(key?.permissions?.[DASHBOARD_PERMISSION_NAMESPACE]);
|
|
184613
|
-
}
|
|
184614
|
-
function workerKeyGrantsGameRead(key, slug2) {
|
|
184615
|
-
if (!isDashboardWorkerKey(key) && !isGameWorkerKey(key)) {
|
|
184616
|
-
return false;
|
|
184617
|
-
}
|
|
184618
|
-
const games2 = key?.permissions?.games;
|
|
184619
|
-
return Boolean(games2?.includes(`read:${slug2}`) || games2?.includes(`write:${slug2}`));
|
|
184620
|
-
}
|
|
184621
|
-
function isGameWorkerKey(key) {
|
|
184622
|
-
return Boolean(key?.name?.startsWith(GAME_WORKER_KEY_PREFIX));
|
|
184623
|
-
}
|
|
184624
|
-
function deniedWorkerKeyRequest(key, method, pathname) {
|
|
184625
|
-
if (isDashboardWorkerKey(key) && !isAllowedDashboardWorkerRequest(method, pathname, key.permissions)) {
|
|
184626
|
-
return DASHBOARD_WORKER_KEY_RESTRICTED;
|
|
184627
|
-
}
|
|
184628
|
-
return null;
|
|
184629
|
-
}
|
|
184630
|
-
function rejectDashboardWorkerKey(ctx) {
|
|
184631
|
-
if (isDashboardWorkerKey(ctx.apiKey)) {
|
|
184632
|
-
throw ApiError.forbidden(DASHBOARD_WORKER_KEY_RESTRICTED);
|
|
184633
|
-
}
|
|
184634
|
-
}
|
|
184635
|
-
function assertAuthenticatedRequest(ctx) {
|
|
184636
|
-
if (!isAuthenticated(ctx)) {
|
|
184637
|
-
throw ApiError.unauthorized("Valid session or bearer token required");
|
|
184638
|
-
}
|
|
184639
|
-
if (ctx.apiKey) {
|
|
184640
|
-
const denied = deniedWorkerKeyRequest(ctx.apiKey, ctx.request.method, ctx.url.pathname);
|
|
184641
|
-
if (denied) {
|
|
184642
|
-
throw ApiError.forbidden(denied);
|
|
184643
|
-
}
|
|
184644
|
-
}
|
|
184645
|
-
}
|
|
184646
|
-
function requireAuth(handler) {
|
|
184647
|
-
return async (ctx) => {
|
|
184648
|
-
assertAuthenticatedRequest(ctx);
|
|
184649
|
-
return handler(ctx);
|
|
184650
|
-
};
|
|
184651
|
-
}
|
|
184652
|
-
function requireNonAnonymous(handler) {
|
|
184653
|
-
return async (ctx) => {
|
|
184654
|
-
assertAuthenticatedRequest(ctx);
|
|
184655
|
-
if (ctx.user.isAnonymous) {
|
|
184656
|
-
throw ApiError.forbidden("This operation is not available for demo/anonymous users");
|
|
184657
|
-
}
|
|
184658
|
-
return handler(ctx);
|
|
184659
|
-
};
|
|
184660
|
-
}
|
|
184661
|
-
function requireAnonymous(handler) {
|
|
184662
|
-
return async (ctx) => {
|
|
184663
|
-
assertAuthenticatedRequest(ctx);
|
|
184664
|
-
if (!ctx.user.isAnonymous) {
|
|
184665
|
-
throw ApiError.forbidden("This operation is only available for demo/anonymous users");
|
|
184666
|
-
}
|
|
184667
|
-
return handler(ctx);
|
|
184668
|
-
};
|
|
184669
|
-
}
|
|
184670
|
-
function requireDeveloper(handler) {
|
|
184671
|
-
return async (ctx) => {
|
|
184672
|
-
assertAuthenticatedRequest(ctx);
|
|
184673
|
-
rejectDashboardWorkerKey(ctx);
|
|
184674
|
-
const isAdmin = ctx.user.role === "admin";
|
|
184675
|
-
const isApprovedDev = ctx.user.role === "developer" && ctx.user.developerStatus === "approved";
|
|
184676
|
-
if (!isAdmin && !isApprovedDev) {
|
|
184677
|
-
throw ApiError.forbidden("Must be an approved developer");
|
|
184678
|
-
}
|
|
184679
|
-
return handler(ctx);
|
|
184680
|
-
};
|
|
184681
|
-
}
|
|
184682
|
-
function requireHumanDeveloper(handler) {
|
|
184683
|
-
return requireDeveloper(async (ctx) => {
|
|
184684
|
-
if (isDashboardWorkerKey(ctx.apiKey) || isGameWorkerKey(ctx.apiKey)) {
|
|
184685
|
-
throw ApiError.forbidden(WORKER_KEY_HUMAN_ONLY);
|
|
184686
|
-
}
|
|
184687
|
-
return handler(ctx);
|
|
184688
|
-
});
|
|
184689
|
-
}
|
|
184690
|
-
function requireGameManagementAccess(handler) {
|
|
184691
|
-
return async (ctx) => {
|
|
184692
|
-
assertAuthenticatedRequest(ctx);
|
|
184693
|
-
rejectDashboardWorkerKey(ctx);
|
|
184694
|
-
if (!hasGameManagementAccess(ctx.user)) {
|
|
184695
|
-
throw ApiError.forbidden("Game management access required");
|
|
184696
|
-
}
|
|
184697
|
-
return handler(ctx);
|
|
184698
|
-
};
|
|
184699
|
-
}
|
|
184700
|
-
var WORKER_KEY_HUMAN_ONLY = "This operation requires your own credential — platform worker keys are refused";
|
|
184701
|
-
var init_auth_util = __esm(() => {
|
|
184702
|
-
init_errors2();
|
|
184703
|
-
init_types6();
|
|
184704
|
-
init_dashboard_util();
|
|
184705
|
-
init_deployment_util();
|
|
184706
|
-
});
|
|
184707
|
-
function defineControllerNames(namespace, handlers) {
|
|
184708
|
-
for (const [key, handler] of Object.entries(handlers)) {
|
|
184709
|
-
Object.defineProperty(handler, "name", {
|
|
184710
|
-
value: `${namespace}.${key}`,
|
|
184711
|
-
configurable: true
|
|
184712
|
-
});
|
|
184713
|
-
}
|
|
184714
|
-
return handlers;
|
|
184715
|
-
}
|
|
184716
|
-
function generateUsername(email5) {
|
|
184717
|
-
const baseUsername = (email5.split("@")[0] || "user").toLowerCase();
|
|
184718
|
-
const cleanUsername = baseUsername.replace(/[^a-z0-9]/g, "");
|
|
184719
|
-
const randomSuffix = Math.random().toString(36).substring(2, 7);
|
|
184720
|
-
return `${cleanUsername}_${randomSuffix}`;
|
|
184721
|
-
}
|
|
184722
|
-
function extractRedirectPath(targetUri, currentHost) {
|
|
184723
|
-
try {
|
|
184724
|
-
const targetUrl = new URL(targetUri);
|
|
184725
|
-
if (targetUrl.hostname === currentHost) {
|
|
184726
|
-
return targetUrl.pathname + targetUrl.search;
|
|
184727
|
-
}
|
|
184728
|
-
} catch {}
|
|
184729
|
-
return "/";
|
|
184730
|
-
}
|
|
184731
|
-
function validateLtiClaims(claims) {
|
|
184732
|
-
const messageType = claims["https://purl.imsglobal.org/spec/lti/claim/message_type"];
|
|
184733
|
-
const version6 = claims["https://purl.imsglobal.org/spec/lti/claim/version"];
|
|
184734
|
-
if (messageType !== "LtiResourceLinkRequest") {
|
|
184735
|
-
return `Invalid LTI message type: ${messageType}`;
|
|
184736
|
-
}
|
|
184737
|
-
if (version6 !== "1.3.0") {
|
|
184738
|
-
return `Unsupported LTI version: ${version6}`;
|
|
184739
|
-
}
|
|
184740
|
-
return null;
|
|
184741
|
-
}
|
|
184742
|
-
var init_lti_util = () => {};
|
|
184743
|
-
async function provisionLtiUser(db2, claims) {
|
|
184744
|
-
const database = db2;
|
|
184745
|
-
const email5 = claims.email;
|
|
184746
|
-
const ltiTimebackId = claims.sub;
|
|
184747
|
-
const providerId = AUTH_PROVIDER_IDS.TIMEBACK_LTI;
|
|
184748
|
-
if (!email5) {
|
|
184749
|
-
throw new ValidationError("Email is required in LTI claims");
|
|
184750
|
-
}
|
|
184751
|
-
const existingAccount = await database.query.accounts.findFirst({
|
|
184752
|
-
where: and(eq(accounts.accountId, ltiTimebackId), eq(accounts.providerId, providerId))
|
|
184753
|
-
});
|
|
184754
|
-
if (existingAccount) {
|
|
184755
|
-
const user = await database.query.users.findFirst({
|
|
184756
|
-
where: eq(users.id, existingAccount.userId)
|
|
184757
|
-
});
|
|
184758
|
-
if (user) {
|
|
184759
|
-
setAttribute("app.lti.provision", "existing_account");
|
|
184760
|
-
return user;
|
|
184761
|
-
}
|
|
184762
|
-
}
|
|
184763
|
-
const existingUser = await database.query.users.findFirst({
|
|
184764
|
-
where: eq(users.email, email5)
|
|
184765
|
-
});
|
|
184766
|
-
if (existingUser) {
|
|
184767
|
-
await database.transaction(async (tx) => {
|
|
184768
|
-
const existingLtiAccount = await tx.query.accounts.findFirst({
|
|
184769
|
-
where: and(eq(accounts.userId, existingUser.id), eq(accounts.providerId, providerId))
|
|
184770
|
-
});
|
|
184771
|
-
if (!existingLtiAccount) {
|
|
184772
|
-
const [account] = await tx.insert(accounts).values({
|
|
184773
|
-
id: crypto4.randomUUID(),
|
|
184774
|
-
userId: existingUser.id,
|
|
184775
|
-
accountId: ltiTimebackId,
|
|
184776
|
-
providerId,
|
|
184777
|
-
accessToken: null,
|
|
184778
|
-
refreshToken: null,
|
|
184779
|
-
accessTokenExpiresAt: null,
|
|
184780
|
-
refreshTokenExpiresAt: null,
|
|
184781
|
-
createdAt: new Date,
|
|
184782
|
-
updatedAt: new Date
|
|
184783
|
-
}).returning({ id: accounts.id });
|
|
184784
|
-
if (!account) {
|
|
184785
|
-
throw new InternalError("Failed to link LTI account");
|
|
184786
|
-
}
|
|
184787
|
-
}
|
|
184788
|
-
});
|
|
184789
|
-
setAttribute("app.lti.provision", "linked_account");
|
|
184790
|
-
return existingUser;
|
|
184791
|
-
}
|
|
184792
|
-
const newUserId = crypto4.randomUUID();
|
|
184793
|
-
const createdUser = await database.transaction(async (tx) => {
|
|
184794
|
-
const [insertedUser] = await tx.insert(users).values({
|
|
184795
|
-
id: newUserId,
|
|
184796
|
-
email: email5,
|
|
184797
|
-
emailVerified: true,
|
|
184798
|
-
username: generateUsername(email5),
|
|
184799
|
-
name: claims.name || claims.given_name || email5.split("@")[0] || "Timeback User",
|
|
184800
|
-
createdAt: new Date,
|
|
184801
|
-
updatedAt: new Date
|
|
184802
|
-
}).returning();
|
|
184803
|
-
if (!insertedUser) {
|
|
184804
|
-
throw new InternalError("Failed to create user");
|
|
184805
|
-
}
|
|
184806
|
-
await tx.insert(accounts).values({
|
|
184807
|
-
id: crypto4.randomUUID(),
|
|
184808
|
-
userId: newUserId,
|
|
184809
|
-
accountId: ltiTimebackId,
|
|
184810
|
-
providerId,
|
|
184811
|
-
accessToken: null,
|
|
184812
|
-
refreshToken: null,
|
|
184813
|
-
accessTokenExpiresAt: null,
|
|
184814
|
-
refreshTokenExpiresAt: null,
|
|
184815
|
-
createdAt: new Date,
|
|
184816
|
-
updatedAt: new Date
|
|
184817
|
-
});
|
|
184818
|
-
setAttribute("app.lti.provision", "created_user");
|
|
184819
|
-
return insertedUser;
|
|
184820
|
-
});
|
|
184821
|
-
return createdUser;
|
|
184822
|
-
}
|
|
184823
|
-
var init_lti_provisioning = __esm(() => {
|
|
184824
|
-
init_drizzle_orm();
|
|
184825
|
-
init_src();
|
|
184826
|
-
init_tables_index();
|
|
184827
|
-
init_spans();
|
|
184828
|
-
init_errors2();
|
|
184829
|
-
init_lti_util();
|
|
184830
|
-
});
|
|
184831
|
-
function requireGameId(gameId) {
|
|
184832
|
-
if (!gameId) {
|
|
184833
|
-
throw ApiError.badRequest("Missing game ID");
|
|
184834
|
-
}
|
|
184835
|
-
if (!isValidUUID(gameId)) {
|
|
184836
|
-
throw ApiError.unprocessableEntity("gameId must be a valid UUID format");
|
|
184837
|
-
}
|
|
184838
|
-
return gameId;
|
|
184839
|
-
}
|
|
184840
|
-
function requireSlug(slug2) {
|
|
184841
|
-
if (!slug2) {
|
|
184842
|
-
throw ApiError.badRequest("Missing game slug");
|
|
184843
|
-
}
|
|
184844
|
-
return slug2;
|
|
184845
|
-
}
|
|
184846
|
-
function requireUserId(userId) {
|
|
184847
|
-
if (!userId) {
|
|
184848
|
-
throw ApiError.badRequest("Missing user ID");
|
|
184849
|
-
}
|
|
184850
|
-
if (!isValidUUID(userId)) {
|
|
184851
|
-
throw ApiError.unprocessableEntity("userId must be a valid UUID format");
|
|
184852
|
-
}
|
|
184853
|
-
return userId;
|
|
184854
|
-
}
|
|
184855
|
-
function parseLimitParam(url4, max2) {
|
|
184856
|
-
const raw2 = url4.searchParams.get("limit");
|
|
184857
|
-
if (raw2 === null) {
|
|
184858
|
-
return;
|
|
184859
|
-
}
|
|
184860
|
-
const limit = Number(raw2);
|
|
184861
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > max2) {
|
|
184862
|
-
throw ApiError.badRequest(`limit must be an integer between 1 and ${max2}`);
|
|
184863
|
-
}
|
|
184864
|
-
return limit;
|
|
184865
|
-
}
|
|
184866
|
-
var init_params_util = __esm(() => {
|
|
184867
|
-
init_src9();
|
|
184868
|
-
init_errors2();
|
|
184869
|
-
});
|
|
184870
|
-
function formatZodError(error89) {
|
|
184871
|
-
const flat = error89.flatten();
|
|
184872
|
-
const result = {};
|
|
184873
|
-
if (Object.keys(flat.fieldErrors).length > 0) {
|
|
184874
|
-
result.fields = {};
|
|
184875
|
-
for (const [field, messages] of Object.entries(flat.fieldErrors)) {
|
|
184876
|
-
if (messages && messages.length > 0) {
|
|
184877
|
-
result.fields[field] = messages[0];
|
|
184878
|
-
}
|
|
184879
|
-
}
|
|
184880
|
-
}
|
|
184881
|
-
if (flat.formErrors.length > 0) {
|
|
184882
|
-
result.errors = flat.formErrors;
|
|
184883
|
-
}
|
|
184884
|
-
return result;
|
|
184885
|
-
}
|
|
184886
|
-
function recordValidationFailure(details) {
|
|
184887
|
-
setAttribute("app.request.outcome", "validation_failed");
|
|
184888
|
-
addEvent("request.validation_failed", {
|
|
184889
|
-
"app.validation.error": JSON.stringify(details)
|
|
184890
|
-
});
|
|
184891
|
-
}
|
|
184892
|
-
async function parseRequestBody(request, schema4) {
|
|
184893
|
-
try {
|
|
184894
|
-
return schema4.parse(await request.json());
|
|
184895
|
-
} catch (error89) {
|
|
184896
|
-
if (error89 instanceof exports_external.ZodError) {
|
|
184897
|
-
const details = formatZodError(error89);
|
|
184898
|
-
recordValidationFailure(details);
|
|
184899
|
-
throw ApiError.unprocessableEntity("Validation failed", details);
|
|
184900
|
-
}
|
|
184901
|
-
throw ApiError.invalidJsonBody();
|
|
184902
|
-
}
|
|
184903
|
-
}
|
|
184904
|
-
var init_validation_util = __esm(() => {
|
|
184905
|
-
init_esm();
|
|
184906
|
-
init_spans();
|
|
184907
|
-
init_errors2();
|
|
184908
|
-
});
|
|
184909
|
-
var init_utils11 = __esm(() => {
|
|
184910
|
-
init_auth_util();
|
|
184911
|
-
init_dashboard_util();
|
|
184912
|
-
init_deployment_util();
|
|
184913
|
-
init_leaderboard_util();
|
|
184914
|
-
init_lti_util();
|
|
184915
|
-
init_lti_provisioning();
|
|
184916
|
-
init_params_util();
|
|
184917
|
-
init_secrets_util();
|
|
184918
|
-
init_timeback_util();
|
|
184919
|
-
init_validation_util();
|
|
184920
|
-
});
|
|
184921
184959
|
var listFiles;
|
|
184922
184960
|
var getFile;
|
|
184923
184961
|
var putFile;
|
|
@@ -184926,7 +184964,7 @@ var initiateUpload;
|
|
|
184926
184964
|
var bucket;
|
|
184927
184965
|
var init_bucket_controller = __esm(() => {
|
|
184928
184966
|
init_errors2();
|
|
184929
|
-
|
|
184967
|
+
init_utils7();
|
|
184930
184968
|
listFiles = requireDeveloper(async (ctx) => {
|
|
184931
184969
|
const slug2 = ctx.params.slug;
|
|
184932
184970
|
if (!slug2) {
|
|
@@ -185025,7 +185063,7 @@ var dashboard2;
|
|
|
185025
185063
|
var init_dashboard_controller = __esm(() => {
|
|
185026
185064
|
init_schemas_index();
|
|
185027
185065
|
init_errors2();
|
|
185028
|
-
|
|
185066
|
+
init_utils7();
|
|
185029
185067
|
listUsers = requireHumanDeveloper(async (ctx) => {
|
|
185030
185068
|
const slug2 = requireSlug(ctx.params.slug);
|
|
185031
185069
|
const users2 = await ctx.services.dashboard.listUsers(slug2, ctx.user);
|
|
@@ -185096,7 +185134,7 @@ var init_database_controller = __esm(() => {
|
|
|
185096
185134
|
init_esm();
|
|
185097
185135
|
init_schemas_index();
|
|
185098
185136
|
init_errors2();
|
|
185099
|
-
|
|
185137
|
+
init_utils7();
|
|
185100
185138
|
reset = requireDeveloper(async (ctx) => {
|
|
185101
185139
|
const slug2 = ctx.params.slug;
|
|
185102
185140
|
if (!slug2) {
|
|
@@ -185157,7 +185195,7 @@ var init_deploy_controller = __esm(() => {
|
|
|
185157
185195
|
init_esm();
|
|
185158
185196
|
init_schemas_index();
|
|
185159
185197
|
init_errors2();
|
|
185160
|
-
|
|
185198
|
+
init_utils7();
|
|
185161
185199
|
deploy = defineControllerNames("deploy", {
|
|
185162
185200
|
createJob: requireDeveloper(createJob),
|
|
185163
185201
|
getJob: requireDeveloper(getJob)
|
|
@@ -185175,7 +185213,7 @@ var deploymentState;
|
|
|
185175
185213
|
var init_deployment_state_controller = __esm(() => {
|
|
185176
185214
|
init_schemas_index();
|
|
185177
185215
|
init_errors2();
|
|
185178
|
-
|
|
185216
|
+
init_utils7();
|
|
185179
185217
|
get = requireDeveloper(async (ctx) => {
|
|
185180
185218
|
const slug2 = requireSlug(ctx.params.slug);
|
|
185181
185219
|
const include = ctx.url.searchParams.getAll("include").flatMap((value) => value.split(",")).filter(Boolean);
|
|
@@ -185245,7 +185283,7 @@ var apply;
|
|
|
185245
185283
|
var getStatus;
|
|
185246
185284
|
var developer;
|
|
185247
185285
|
var init_developer_controller = __esm(() => {
|
|
185248
|
-
|
|
185286
|
+
init_utils7();
|
|
185249
185287
|
apply = requireNonAnonymous(async (ctx) => {
|
|
185250
185288
|
await ctx.services.developer.apply(ctx.user);
|
|
185251
185289
|
});
|
|
@@ -185268,7 +185306,7 @@ var init_domain_controller = __esm(() => {
|
|
|
185268
185306
|
init_schemas_index();
|
|
185269
185307
|
init_config2();
|
|
185270
185308
|
init_errors2();
|
|
185271
|
-
|
|
185309
|
+
init_utils7();
|
|
185272
185310
|
add = requireDeveloper(async (ctx) => {
|
|
185273
185311
|
const slug2 = ctx.params.slug;
|
|
185274
185312
|
if (!slug2) {
|
|
@@ -185345,7 +185383,7 @@ var gameMembers2;
|
|
|
185345
185383
|
var init_game_member_controller = __esm(() => {
|
|
185346
185384
|
init_schemas_index();
|
|
185347
185385
|
init_errors2();
|
|
185348
|
-
|
|
185386
|
+
init_utils7();
|
|
185349
185387
|
listMembers = requireNonAnonymous(async (ctx) => {
|
|
185350
185388
|
const gameId = requireGameId(ctx.params.gameId);
|
|
185351
185389
|
return ctx.services.gameMember.list(gameId, ctx.user);
|
|
@@ -185394,7 +185432,7 @@ var init_game_controller = __esm(() => {
|
|
|
185394
185432
|
init_esm();
|
|
185395
185433
|
init_schemas_index();
|
|
185396
185434
|
init_errors2();
|
|
185397
|
-
|
|
185435
|
+
init_utils7();
|
|
185398
185436
|
init_auth_util();
|
|
185399
185437
|
list2 = requireNonAnonymous(async (ctx) => ctx.services.game.list(ctx.user));
|
|
185400
185438
|
listAccessible = requireNonAnonymous(async (ctx) => ctx.services.game.listAccessible(ctx.user));
|
|
@@ -185471,7 +185509,7 @@ var init_kv_controller = __esm(() => {
|
|
|
185471
185509
|
init_esm();
|
|
185472
185510
|
init_schemas_index();
|
|
185473
185511
|
init_errors2();
|
|
185474
|
-
|
|
185512
|
+
init_utils7();
|
|
185475
185513
|
listKeys = requireDeveloper(async (ctx) => {
|
|
185476
185514
|
const slug2 = ctx.params.slug;
|
|
185477
185515
|
if (!slug2) {
|
|
@@ -185579,7 +185617,7 @@ var init_leaderboard_controller = __esm(() => {
|
|
|
185579
185617
|
init_esm();
|
|
185580
185618
|
init_schemas_index();
|
|
185581
185619
|
init_errors2();
|
|
185582
|
-
|
|
185620
|
+
init_utils7();
|
|
185583
185621
|
submitScore = requireAuth(async (ctx) => {
|
|
185584
185622
|
const gameId = ctx.params.gameId;
|
|
185585
185623
|
if (!gameId) {
|
|
@@ -185687,7 +185725,7 @@ var generateToken;
|
|
|
185687
185725
|
var logs;
|
|
185688
185726
|
var init_logs_controller = __esm(() => {
|
|
185689
185727
|
init_errors2();
|
|
185690
|
-
|
|
185728
|
+
init_utils7();
|
|
185691
185729
|
generateToken = requireDeveloper(async (ctx) => {
|
|
185692
185730
|
const slug2 = ctx.params.slug;
|
|
185693
185731
|
if (!slug2) {
|
|
@@ -185721,7 +185759,7 @@ var init_logs_controller = __esm(() => {
|
|
|
185721
185759
|
var getStatus3;
|
|
185722
185760
|
var lti;
|
|
185723
185761
|
var init_lti_controller = __esm(() => {
|
|
185724
|
-
|
|
185762
|
+
init_utils7();
|
|
185725
185763
|
getStatus3 = requireNonAnonymous(async (ctx) => ctx.services.lti.getStatus(ctx.user));
|
|
185726
185764
|
lti = defineControllerNames("lti", {
|
|
185727
185765
|
getStatus: getStatus3
|
|
@@ -185736,7 +185774,7 @@ var init_secrets_controller = __esm(() => {
|
|
|
185736
185774
|
init_esm();
|
|
185737
185775
|
init_schemas_index();
|
|
185738
185776
|
init_errors2();
|
|
185739
|
-
|
|
185777
|
+
init_utils7();
|
|
185740
185778
|
listKeys2 = requireDeveloper(async (ctx) => {
|
|
185741
185779
|
const slug2 = ctx.params.slug;
|
|
185742
185780
|
if (!slug2) {
|
|
@@ -185798,7 +185836,7 @@ var init_seed_controller = __esm(() => {
|
|
|
185798
185836
|
init_esm();
|
|
185799
185837
|
init_schemas_index();
|
|
185800
185838
|
init_errors2();
|
|
185801
|
-
|
|
185839
|
+
init_utils7();
|
|
185802
185840
|
seed2 = requireDeveloper(async (ctx) => {
|
|
185803
185841
|
const slug2 = ctx.params.slug;
|
|
185804
185842
|
if (!slug2) {
|
|
@@ -185830,7 +185868,7 @@ var init_session_controller = __esm(() => {
|
|
|
185830
185868
|
init_spans();
|
|
185831
185869
|
init_tunnel();
|
|
185832
185870
|
init_errors2();
|
|
185833
|
-
|
|
185871
|
+
init_utils7();
|
|
185834
185872
|
MintTokenBody = exports_external.object({
|
|
185835
185873
|
reason: exports_external.enum(["initial", "refresh_scheduled", "refresh_stale"]).optional()
|
|
185836
185874
|
});
|
|
@@ -185947,7 +185985,7 @@ var init_timeback_controller = __esm(() => {
|
|
|
185947
185985
|
init_src9();
|
|
185948
185986
|
init_timeback3();
|
|
185949
185987
|
init_errors2();
|
|
185950
|
-
|
|
185988
|
+
init_utils7();
|
|
185951
185989
|
populateStudent = requireNonAnonymous(async (ctx) => {
|
|
185952
185990
|
let providedNames;
|
|
185953
185991
|
try {
|
|
@@ -186781,7 +186819,7 @@ var init_upload_controller = __esm(() => {
|
|
|
186781
186819
|
init_esm();
|
|
186782
186820
|
init_schemas_index();
|
|
186783
186821
|
init_errors2();
|
|
186784
|
-
|
|
186822
|
+
init_utils7();
|
|
186785
186823
|
initiate = requireDeveloper(async (ctx) => {
|
|
186786
186824
|
let body2;
|
|
186787
186825
|
try {
|
|
@@ -186808,7 +186846,7 @@ var users2;
|
|
|
186808
186846
|
var init_user_controller = __esm(() => {
|
|
186809
186847
|
init_src();
|
|
186810
186848
|
init_schemas_index();
|
|
186811
|
-
|
|
186849
|
+
init_utils7();
|
|
186812
186850
|
getMe = requireNonAnonymous(async (ctx) => ctx.services.user.getMe(ctx.user, ctx.gameId, ctx.request.headers.get(PLAYCADEMY_BROWSER_TIME_ZONE_HEADER)));
|
|
186813
186851
|
getDemoProfile = requireAnonymous(async (ctx) => ctx.services.user.getDemoProfile(ctx.user.id));
|
|
186814
186852
|
updateDemoProfile = requireAnonymous(async (ctx) => {
|
|
@@ -186841,7 +186879,7 @@ var init_verify_controller = __esm(() => {
|
|
|
186841
186879
|
init_esm();
|
|
186842
186880
|
init_schemas_index();
|
|
186843
186881
|
init_errors2();
|
|
186844
|
-
|
|
186882
|
+
init_utils7();
|
|
186845
186883
|
verify = defineControllerNames("verify", {
|
|
186846
186884
|
verifyToken
|
|
186847
186885
|
});
|
|
@@ -187022,7 +187060,7 @@ async function buildMockUserResponse(db2, user, gameId, observedTimeZone) {
|
|
|
187022
187060
|
var LEARNING_TIME_ZONE_REFRESH_INTERVAL_MS2;
|
|
187023
187061
|
var init_timeback7 = __esm(() => {
|
|
187024
187062
|
init_drizzle_orm();
|
|
187025
|
-
|
|
187063
|
+
init_utils7();
|
|
187026
187064
|
init_src();
|
|
187027
187065
|
init_tables_index();
|
|
187028
187066
|
init_src6();
|
|
@@ -187433,7 +187471,7 @@ var init_seed2 = __esm(async () => {
|
|
|
187433
187471
|
init_dist7();
|
|
187434
187472
|
init_esm();
|
|
187435
187473
|
init_errors2();
|
|
187436
|
-
|
|
187474
|
+
init_utils7();
|
|
187437
187475
|
init_schemas_index();
|
|
187438
187476
|
await init_api3();
|
|
187439
187477
|
gameSeedRouter = new Hono2;
|
|
@@ -187663,7 +187701,7 @@ var init_timeback8 = __esm(async () => {
|
|
|
187663
187701
|
init_dist7();
|
|
187664
187702
|
init_controllers();
|
|
187665
187703
|
init_errors2();
|
|
187666
|
-
|
|
187704
|
+
init_utils7();
|
|
187667
187705
|
init_src();
|
|
187668
187706
|
init_schemas_index();
|
|
187669
187707
|
init_timeback3();
|
|
@@ -187902,7 +187940,7 @@ var init_lti = __esm(async () => {
|
|
|
187902
187940
|
init_drizzle_orm();
|
|
187903
187941
|
init_dist7();
|
|
187904
187942
|
init_controllers();
|
|
187905
|
-
|
|
187943
|
+
init_utils7();
|
|
187906
187944
|
init_tables_index();
|
|
187907
187945
|
init_src6();
|
|
187908
187946
|
init_constants();
|